final:
1.修饰数据
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
- 对于基本类型,final使数值不变;
- 对于引用类型,final使引用不变,也就是不能引用其他对象,但是被其引用的对象本身是可以修改的。
final int x = 1; x = 2; // cannot assign value to final variable ‘x‘ final A y = new A(); y.a = 1;
2.修饰方法
声明方法不能被子类覆盖,不能与abstract共存。
注:private方法隐式地被指定为final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是覆盖基类方法,而是重载了。
3.修饰类
声明类不允许被继承,不能与abstract共存。
static:
1.静态变量
静态变量在内存中只存在一份,只在类第一次使用时初始化一次。
- 静态变量:类所有的实例都共享静态变量,可以直接通过类名来访问;
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
public class A { private int x; // 实例变量 public static int y; // 静态变量 public static void main(String[] args){ System.out.println(A.y); A a = new A(); System.out.println(a.x); } }
2.静态方法
静态方法在类的加载时候就存在了,它不依赖于任何实例,所以 static 方法必须实现,也就是说它不能是抽象方法(abstract)。
静态方法可以调用静态变量,不可调用类的实例变量,因为静态方法随着类的加载就出现了,而实例变量依赖于具体实例。
public static void run(){ System.out.println("hello"); }
3.静态代码块
静态代码块随着类加载而加载有多个静态代码块的,按代码块前后顺序加载一个代码块,只执行一次。
static{ System.out.println("hehe"); }
4.初始化顺序
静态数据>>普通代码块、实例变量>>构造函数
ps:静态变量和静态语句块哪个先运行取决于它们在代码中的顺序。
存在继承的情况下,初始化顺序:
- 父类(静态变量、静态语句块块)
- 子类(静态变量、静态语句块)
- 父类(实例变量、普通语句块)
- 父类(构造函数)
- 子类(实例变量、普通语句块)
- 子类(构造函数)
笔试题:下面代码的输出为?(关于静态变量的初始化和赋值)
public class SingleTon { private static SingleTon singleTon = new SingleTon(); public static int count1; public static int count2 = 0; private SingleTon(){ count1++; count2++; } public static SingleTon getSingleTon(){ return singleTon; } public static void main(String[] args) { SingleTon singleTon = SingleTon.getSingleTon(); System.out.println(singleTon.count1); System.out.println(singleTon.count2); } }
答案是:1 0
解析:1.SingleTon singleTon = SingleTon.getSingleTon(); 调用了类方法,触发类的初始化;
2.类加载时,JVM为类的静态变量分配内存并赋默认值:singleTon = null; count1 = 0; count2 = 0;
3.类初始化,JVM为静态变量赋值以及执行静态代码块,当给singleTon赋值new SingleTon()时触发类构造器,调用类的构造方法后count1 = 1,count2 = 1;
4.JVM继续给count1和count2赋值,count1无赋值操作, count2 = 0;
总结:静态变量的声明和赋值是分开的