static

1 静态变量

  • 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。
  • 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
  • static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。static成员变量的初始化顺序按照定义的顺序进行初始化。所以我们一般在这两种情况下使用静态变量:对象之间共享数据、访问方便。
1
2
3
4
5
6
7
8
9
10
11
12
public class A {

private int x; // 实例变量
private static int y; // 静态变量

public static void main(String[] args) {
// int x = A.x; // Non-static field 'x' cannot be referenced from a static context
A a = new A();
int x = a.x;
int y = A.y;
}
}

2 静态方法

静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。

1
2
3
4
5
public abstract class A {
public static void func1(){
}
// public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static'
}

只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字,因为这两个关键字与具体对象关联。

1
2
3
4
5
6
7
8
9
10
11
public class A {

private static int x;
private int y;

public static void func1(){
int a = x;
// int b = y; // Non-static field 'y' cannot be referenced from a static context
// int b = this.y; // 'A.this' cannot be referenced from a static context
}
}

3 静态语句块

静态语句块在类初始化时运行一次。

1
2
3
4
5
6
7
8
9
10
public class A {
static {
System.out.println("123");
}

public static void main(String[] args) {
A a1 = new A();
A a2 = new A();
}
}
1
123

4 静态内部类

非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class OuterClass {

class InnerClass {
}

static class StaticInnerClass {
}

public static void main(String[] args) {
// InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
OuterClass outerClass = new OuterClass();
InnerClass innerClass = outerClass.new InnerClass();
StaticInnerClass staticInnerClass = new StaticInnerClass();
}
}

静态内部类不能访问外部类的非静态的变量和方法。

  内部类一般情况下使用不是特别多,如果需要在外部类里面定义一个内部类,通常是基于外部类和内部类有很强关联的前提下才去这么使用。

  在说静态内部类的使用场景之前,我们先来看一下静态内部类和非静态内部类的区别:

  非静态内部类对象持有外部类对象的引用(编译器会隐式地将外部类对象的引用作为内部类的构造器参数);而静态内部类对象不会持有外部类对象的引用

  由于非静态内部类的实例创建需要有外部类对象的引用,所以非静态内部类对象的创建必须依托于外部类的实例;而静态内部类的实例创建只需依托外部类;

  并且由于非静态内部类对象持有了外部类对象的引用,因此非静态内部类可以访问外部类的非静态成员;而静态内部类只能访问外部类的静态成员;

  两者的根本性区别其实也决定了用static去修饰内部类的真正意图:

  • 内部类需要脱离外部类对象来创建实例
  • 避免内部类使用过程中出现内存溢出

  

第一种是目前静态内部类使用比较多的场景,比如JDK集合中的Entry、builder设计模式。

  HashMap Entry:

  builder设计模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class Person {
private String name;
private int age;

private Person(Builder builder) {
this.name = builder.name;
this.age = builder.age;
}

public static class Builder {

private String name;
private int age;

public Builder() {
}

public Builder name(String name) {
this.name = name;
return this;
}
public Builder age(int age) {
this.age=age;
return this;
}

public Person build() {
return new Person(this);
}
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

// 在需要创建Person对象的时候
Person person = new Person.Builder().name("张三").age(17).build();

第二种情况一般出现在多线程场景下,非静态内部类可能会引发内存溢出的问题,比如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
public class Task {

public void onCreate() {
// 匿名内部类, 会持有Task实例的引用
new Thread() {
public void run() {
//...耗时操作
};
}.start();
}
}

 上面这段代码中的:

1
2
3
4
5
new Thread() {
public void run() {
//...耗时操作
};
}.start();

  声明并创建了一个匿名内部类对象,该对象持有外部类Task实例的引用,如果在在run方法中做的是耗时操作,将会导致外部类Task的实例迟迟不能被回收,如果Task对象创建过多,会引发内存溢出。

​ 优化方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Task {

public void onCreate() {
SubTask subTask = new SubTask();
subTask.start();
}

static class SubTask extends Thread {
@Override
public void run() {
//...耗时操作
}

}
}

5 静态导包

在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。

1
import static com.xxx.ClassName.*

6 初始化顺序

静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。

1
public static String staticField = "静态变量";
1
2
3
static {
System.out.println("静态语句块");
}
1
public String field = "实例变量";
1
2
3
{
System.out.println("普通语句块");
}

最后才是构造函数的初始化。

1
2
3
public InitialOrderTest() {
System.out.println("构造函数");
}

存在继承的情况下,初始化顺序为:

  • 父类(静态变量、静态语句块)
  • 子类(静态变量、静态语句块)
  • 父类(实例变量、普通语句块)
  • 父类(构造函数)
  • 子类(实例变量、普通语句块)
  • 子类(构造函数)

关于静态方法的思考

面相对象的各种对象之间存在依赖和集成关系,根据依赖和继承关系可以绘制面相对象的拓扑。两个对象可能关联同一个关键对象,当其中一个对象修改了关键对象的状态,另一个对象也会感知到这个关键对象的状态变化。

但是静态方法是无状态的,如果静态方法调用N次,其内部创建的对象关系就会生成N次。

静态方法是系统的启动放发,main方法也是静态方法。如果一个模块相对独立,应该也使用静态方法启动。调用者无需创建对象,只需要调用该模块的静态方法,在该静态方法中组建该模块的内部对象拓扑关系。实现一定程度的相互隔离。

关于父类和子类的作用的说明。记得在写毕设的时候,自己把父类当做工具类,子类在调用很多方法的时候,为了少写代码,就直接调用父类中的方法,然后导致父类中的流程函数,会调用子类中的方法,子类中的函数又会调用父类中的方法,非常凌乱,两个类相互冗余。当时也在思考,这些工具函数写在父类中供所有的子类调用与写一个util类有什么区别?

现在发现,应该遵循一些默认的编码规则,父类用来构建整体的流程,而子类用来完善丰富一些子流程。相当于父类在构建主流程的时候,空出一些细节实现的部分,让子类来完善。而不是写一写类似的工具函数,让子类来调用,子类能够看到更加全面丰富的流程,那么父类就没有存在的必要了,父类的作用可能就是一个接口了,只提供一些对外的方法声明。

综上所属:

  • 接口:只提供对外的方法声明
  • 父类:提供完整的流程,由父类调用未经实现的抽象方法完成整体流程的构建。
  • 子类:提供丰富的细节实现,由子类实现抽象方法的细节。
  • 工具类:提供给所有子类的通用的处理函数。一般是静态函数