Java中的static讨论
首先
这篇文章是2017年MicroAd圣诞日历第18天的文章。
我今想讨论一下在Java中关于静态(static)的一些冗长的话题,这可能是平时你并不太注意或关注的。如果我们现在不谈论它,以后可能就再也没有机会了。
目标
再小的 static,也是 static,
在写简单的小测验的同时,偷偷整理一下 Java 中的 static 特性,再确认一遍。
静态字段
首先,static这个词在中文中意思是”静态”。所谓”动态”指的是,无论创建多少个实例,类中的这个变量都只有一个。我们可以通过给字段添加static来共享信息和资源,在多个实例之间共享。可以通过类名.字段名来调用,即使通过创建实例调用也可以访问相同的内容。
通过使用final static进行常量定义,可以防止每次创建实例时相同的值被复制到对象中,从而节省内存。
public final static String KEY = "happy-toilet";
这是一个可以在任何地方召唤的Happy Toilet的恒定值。
静态方法(类方法)
静态方法不属于类的实例,而是属于类本身。
可以通过类名.方法名进行调用,如果是函数式接口的方法引用,则可以直接通过类名::方法名进行调用。
静态成员与静态字段一起被称为静态成员。
没有static修饰的一般方法被称为非静态方法或实例方法。
以下是Java8之后,函数式接口方法引用中静态方法和实例方法调用的区别。
public class MethodReference {
public static void main(String[] args) {
// static メソッド
wakeUp(MethodReference::printWakeUp);
System.out.print(" and ");
// インスタンスメソッド
MethodReference mr = new MethodReference();
mr.goToilet(mr::printGoToilet);
}
interface Human {
void doSomething();
}
static void wakeUp(Human human) {
human.doSomething();
}
static void printWakeUp() {
String wakeUp = "wake up";
System.out.print(wakeUp);
}
void goToilet(Human human) {
human.doSomething();
}
void printGoToilet() {
String toilet = "happy toilet";
System.out.print(toilet);
}
}
醒來並開心地使用廁所的結果。
无法重写
public class StaticPractice {
public static void main(String[] args) {
Human developer = new Developer();
System.out.println(developer.getMorning());
}
}
class Human {
static String morning = "Happy Toilet";
static String getMorning() {
return morning;
}
}
class Developer extends Human {
static String morning = "Happy Coding";
static String getMorning() {
return morning;
}
}
当然的,结果是 Happy Toilet。
将“Developer developer = new Developer();”修改为“一応 Happy Coding 是可以的”。
无法被override,所以我们必须创建一个实例来表示这个概念,并从该实例中调用方法,但归根结底,静态方法本来就是这样的。
Human.getMorning();
Developer.getMorning();
在没有经过实例来进行的情况下,最好通过类本身来进行引用。
实际上,将上述代码直接移植到 IntelliJ 中试试看。
请注意,被类本身没有引用的静态方法。
(特别感谢 @kawamnv さん)
公共的静态的主方法(String [] args)
当调用主方法时,由于内存中尚不存在实例,所以即使主方法所在的类尚未被实例化,也需要执行。
静态类(静态内部类)
在类中有三种类型的内部类:成员类、局部类和匿名类。
静态成员类是其中的一种,声明位置在类块中(与字段和方法的位置相同)。
然而严格来说,静态成员类很难称为内部类,更正确的表达应该是它是一个完全不同的类。
当我们将包裹内部类的类称为外部类时,外部类与其实例的关系相对较弱,并且可以访问外部类的静态成员。
与其他静态元素一样,可以通过外部类名.成员类名来使用。
class Outer { // 外部クラス
// メンバクラス➀ static クラス
static class StaticInner {
private Integer in;
public Integer getIn() {
return in;
}
public void setIn(Integer in) {
this.in = in;
}
}
// メンバクラス➁ 厳密的なメンバクラス
class Inner {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}
}
下面是在无关的类和主方法中调用的方式。
class Execute {
public static void main(String[] args) {
// static クラス
Outer.StaticInner staticInner = new Outer.StaticInner();
// 厳密的なメンバクラス
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
}
}
我在上述例子中加入了一个例子,严格的成员类与外部类的实例有着强烈的关联,因此如果没有外部类的实例,就无法创建新的实例。由于它不是静态的,所以也可以访问非静态成员。
静态初始化程序
static {
// 処理
}
静态初始化块是在类加载(加载.class文件)时执行一次的块。
它用于编写在实例化某个类之前或在主方法之前要调用执行的处理。
当然,只能对静态对象进行编写。
我曾经思考过这是否有用途。
public final static String VALUE;
static {
VALUE = makeValue();
}
/**
* 特定条件下で値を動的に生成する
* @return value 生成した値
*/
private static String makeValue() {
// omit
}
前几天,我突然有机会尝试了这种用法。
因为我想在实例化之前的早期阶段,创建一个新值并在整个过程中重复使用它,所以正好这样做非常适合。
构造函数和静态初始化器
class Something {
void printA() {
System.out.print("t");
}
void printB() {
System.out.print("e");
}
Something () {
System.out.print("l");
printB();
}
static {
System.out.print("i");
}
}
class StaticTest {
static {
System.out.print("T");
}
public static void main(String [] args) {
System.out.print("o");
new Something().printA();
}
}
答案只需要一种选项:最终结果是厕所。
静态初始化程序可以在实例化之前被调用,早于构造函数。
静态导入声明
Java5以后引入了该功能。
以下是前次发布时用于创建约束注释的源代码。
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = {CustomSizeValidator.class})
public @interface CustomSize {
// omit
}
实际上,由于ElementType和RetentionPolicy是具有较长包名的枚举类型,因此我们可以像上面那样简洁地编写代码。
如果是外部类的静态成员,则不一定非要使用枚举,可以使用其他方式进行利用。
import static java.lang.System.*;
import static java.lang.Math.*;
结束了
早上第一个如厕的时间,重新确认基础文法非常重要(小声说)。
以上