static关键字
静态变量
- 静态变量:又称做类变量,也就是这个变量属于整个类,而不属于单个实例。类所有的实例共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份,当系统第一次加载类时,系统为静态变量分配内存,直到该类不在使用,内存才会被回收,生命周期几乎等同于该类的生命周期。
- 实例变量:每创建一个实例就会产生一个实例变量,与该实例同生共死。
public class A {
private int x; //实例变量
private static int y; //静态变量
public static void main(String[] args) {
A a = new A();
//实例变量,只能通过实例来访问
System.out.println(a.x); //0
//静态变量属于类本身,所以既可以通过实例也可以通过类本身来访问
System.out.println(a.y); //0
System.out.println(A.y); //0
//更改静态变量和实例变量的值
a.x = 10;
a.y = 5;
A b = new A();
//静态变量只创建一次,被所有实例共享,实例变量仅属于实例本身
System.out.println(b.x); //0
System.out.println(b.y); //5
}
}
静态方法
静态方法:又称做类方法,在类加载的时候就存在了,它不依赖于任何实例,可以通过类名来访问。静态方法必须有实现,不能是抽象方法,只能访问所属类的静态成员和静态方法,不能出现 this 和 super 关键字,因为该方法不依赖任何实例就可以访问,而实例变量或者方法必须先创建实例才能访问。
public class A {
private int x; //实例变量
private static int y; //静态变量
//main方法本身就是静态方法
public static void main(String[] args) {
//int a = x; 报错
int b = y;
System.out.println(b); //0
}
}
静态代码块
静态代码块:仅在类第一次被加载时运行一次的代码块,可用于对静态变量初始化。
public class A {
static {
System.out.println("hello word!");
}
public static void main(String[] args) {
A a = new A();
A b = new A();
}
}
//输出:hello word!
静态内部类
静态内部类:非静态内部类必须依赖外部类的实例,而静态类不需要,直接通过外部类名来创建。静态内部类不能访问外部类的非静态的变量和方法。
public class A {
public class InnerClass{
}
public static class StaticInnerClass {
}
public static void main(String[] args) {
A a = new A();
//非静态内部类的创建,必须依赖外部类的实例
InnerClass innerClass = a.new InnerClass();
//静态内部类的创建
StaticInnerClass staticInnerClass = new A.StaticInnerClass();
}
}
初始化顺序
静态变量声明和静态语句块优先于实例变量声明和普通语句块,静态变量声明和静态语句块的初始化顺序取决于它们在代码中的顺序,最后才是构造函数的初始化。
存在继承情况下的初始化顺序,括号内容的执行顺序取决于在代码的顺序:
- 父类(静态变量声明、静态语句块)
- 子类(静态变量声明、静态语句块)
- 父类(实例变量声明、普通语句块)
- 父类(构造方法)
- 子类(实例变量声明、普通语句块)
- 子类(构造方法)
顺序规则:第一次加载类(还未创建对象)时,从最顶层父类开始执行静态变量声明、静态语句块(执行顺序与代码顺序有关);当真正创建对象时,从最顶层父类开始执行实例变量声明、普通语句块和构造方法(前面两个先执行,执行顺序与代码顺序有关,构造方法最后执行),只有父类成员初始化完毕,才会初始化子类成员。
class Father {
{
System.out.println("father普通代码块");
}
static {
System.out.println("father静态代码块");
}
public Father() {
System.out.println("father构造方法");
}
}
public class Son extends Father{
{
System.out.println("son普通代码块");
}
static {
System.out.println("son静态代码块");
}
public Son() {
System.out.println("som构造方法");
}
public static void main(String[] args) {
new Son();
}
}
/*输出:
father静态代码块
son静态代码块
father普通代码块
father构造方法
son普通代码块
som构造方法
*/
final关键字
数据
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
- 对于基本类型,final 使数值不变;
- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
final int x = 1;
//x = 2; //报错,基本类型不能更改
final A a = new A();
a.c = 1; //正常运行,引用变量不能更改引用对象,但可以更改对象数据
数据可分为成员变量和局部变量,两种都可以加 final。
成员变量
final 修饰的成员变量必须显示赋初始值。
不管是静态变量还是实例变量,在分配内存时,会先分配默认值(0,u0000,false 或 null)。当执行静态代码块或者静态变量声明时可对静态变量显示赋初始值;当执行普通代码块、实例变量声明或者构造器时可对实例变量显示赋初始值。如果变量没有显式赋初始值,变量初始值使用默认值。而被 final 修饰的成员变量使用默认值将毫无意义,所有规定 final 修饰的成员变量必须显示赋初始值。
public class A{
static {
//此处代码块比声明执行顺序更早,所以A.a还未赋初始值,为0
//A.b由final修饰,必须有初始值,所以此处先查询声明是否赋初始值,再运行,为10
System.out.println(A.a); //0
System.out.println(A.b); //10
}
private static int a = 10;
private static final int b = 10;
private final int c;
//private final int d; //没有赋初始值报错
public A() {
c = 10;
}
public static void main(String[] args) {
new A();
}
}
局部变量
public void test(final int a) {
//a = 5; //报错,不能对final修饰的形参赋值
}
public static void main(String[] args) {
final String str = "hello";
//str = "Java"; //报错,不能再赋值
final double d;
d = 5.6; //正常运行,局部变量没有默认值,必须显示赋初始值
}
方法
被 final 修饰的方法不能被子类重写。private 方法被隐式地指定为 final,如果在子类中定义的方法和父类中的一个 private 方法签名相同,不是重写父类方法,而是定义了一个新的方法。
class B {
public final void f1() {
}
private void f2() {
}
}
public class A extends B {
public void f1() { //报错
}
private void f2() { //正常
}
}
类
被 final 修饰的类不能被继承。
final class B {
}
public class A extends B { //报错
}