Java基础深度总结:关键字-static

冒个险吧!人生本来就是一场探险, 最有成就的是那些敢于尝试的人。

1.静态变量与实例变量(重点)
  • 静态变量:又称为类变量,指的是被static修饰的成员变量。静态变量是属于类的,他被该类的所有对象共享,在内存中只存在一份,存在于JVM的方法区中,是线程共享的,并且在JVM加载类时,会为该类的静态变量分配内存,并随着类的卸载而销毁,一般通过类名直接访问。
  • 实例变量: 指没有被static修饰的成员变量。实例变量是属于这个类的实例对象的,因此在内存中创建几个对象,就会几份实力变量,存在于堆中,实例变量随着对象的创建而创建,随着对象的销毁而销毁,必须通过对象访问。
public class A {

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

    public static void main(String[] args) {
        // int x = A.x;  // 报错
        A a = new A();
        int x = a.x;
        int y = a.y; //通过对象访问
        int y2 = A.y; //通过类名访问
    }
}

Tips:

  • 静态变量虽然独立于对象,但只要访问权限足够,静态变量和静态方法也是可以根据对象访问的,也就是说静态变量不仅可以通过类名访问,也可通过具体的对象访问,但一般不建议这么做。
  • static不能修饰局部变量。
  • 可以看出来static关键字可以改变变量的存储位置。
2.静态方法

由static修饰的方法就是静态方法,它随着类的加载而存在,不依赖于任何对象。

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
    }
}
  • 静态方法必须有实现,也就是说他不能是抽象方法。
  • 静态方法只能访问所属类的静态变量和静态方法。
  • 静态变量中不能有this和super关键字,因为这两个关键字与具体的对象关联。
3.静态代码块(重点)

代码块的分类:

  • 构造代码块:在类中定义,每次创建对象都会调用一次。
  • 静态代码块:被static修饰的构造代码块,静态代码块的代码会在类的加载时被运行,且只会执行一次。
  • 普通代码块:在方法体中定义,完成一定逻辑,可以控制变量的生命周期,提高内存利用率。
public class Test {

    {
        System.out.println("构造代码块");
    }

    static {
        System.out.println("静态代码块");
    }

	public void sayHello(){
		{
			System.out.println("普通代码块");
		}
	}
}

执行顺序

静态代码块 > 构造代码块 > 构造函数

public class StaticCodeBlockTest {

    public StaticCodeBlockTest() {
        System.out.println("StaticCodeBlockTest:构造函数");
    }

    {
        System.out.println("StaticCodeBlockTest:构造代码块");
    }

    static {
        System.out.println("StaticCodeBlockTest:静态代码块");
    }

    public static void main(String[] args) {
        StaticCodeBlockTest2 test2 = new StaticCodeBlockTest2();
        System.out.println("----------");
        StaticCodeBlockTest test = new StaticCodeBlockTest();
    }
}

class StaticCodeBlockTest2 {

    public StaticCodeBlockTest2() {
        System.out.println("StaticCodeBlockTest2:构造函数");
    }

    {
        System.out.println("StaticCodeBlockTest2:构造代码块");
    }

    static {
        System.out.println("StaticCodeBlockTest2:静态代码块");
    }
}

输出结果:
StaticCodeBlockTest:静态代码块
StaticCodeBlockTest2:静态代码块
StaticCodeBlockTest2:构造代码块
StaticCodeBlockTest2:构造函数
----------
StaticCodeBlockTest:构造代码块
StaticCodeBlockTest:构造函数

解析:

  • main函数作为入口,因此会先加载StaticCodeBlockTest 类,所以执行了StaticCodeBlockTest 的静态代码块,输出:StaticCodeBlockTest:静态代码块
  • 接着主函数里要创建一个StaticCodeBlockTest2的对象,发现StaticCodeBlockTest2类还没有加载,所以先加载StaticCodeBlockTest2,就执行了StaticCodeBlockTest2的静态代码块,输出:StaticCodeBlockTest2:静态代码块
  • 在创建StaticCodeBlockTest2对象时,构造代码块优先于构造器,所以先输出StaticCodeBlockTest2:构造代码块,再输出StaticCodeBlockTest2:构造函数
  • 接着又要创建StaticCodeBlockTest对象,发现StaticCodeBlockTest类已经被加载了,所以先调用StaticCodeBlockTest的构造代码块,然后执行构造函数,所以先输出StaticCodeBlockTest:构造代码块,再输出StaticCodeBlockTest:构造函数

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

父类静态代码块 > 子类静态代码块 > 父类构造代码块 > 父类构造器 > 子类构造代码块 > 子类构造器

public class StaticCodeBlockExtendTest {

    public static void main(String[] args) {
        Zi z = new Zi();
    }
}

class Ye{

    public Ye() {
        System.out.println("Ye:构造函数");
    }

    {
        System.out.println("Ye:构造代码块");
    }

    static {
        System.out.println("Ye:静态代码块");
    }
}

class Fu extends Ye{

    public Fu() {
        System.out.println("Fu:构造函数");
    }

    {
        System.out.println("Fu:构造代码块");
    }

    static {
        System.out.println("Fu:静态代码块");
    }
}

class Zi extends Fu{

    public Zi() {
        System.out.println("Zi:构造函数");
    }

    {
        System.out.println("Zi:构造代码块");
    }

    static {
        System.out.println("Zi:静态代码块");
    }
}
输出:
Ye:静态代码块
Fu:静态代码块
Zi:静态代码块
Ye:构造代码块
Ye:构造函数
Fu:构造代码块
Fu:构造函数
Zi:构造代码块
Zi:构造函数

解析:

  • 在创建子类对象时,会先去加载子类,而加载子类必须先加载父类;
  • 在创建子类对象时,会先调用父类的构造方法,而构造代码块优先于构造方法;

静态域初始化顺序

静态语句块之间的初始化顺序、静态变量和静态语句块之间的初始化顺序 取决于它们在代码中的书写顺序,也就说,静态域按顺序初始化。这是因为在类的初始化阶段,编译器会根据程序员书写顺序,自动收集所有类变量的赋值操作和静态语句块,合并成一个 < clinit >()方法,然后执行。

public class StaticCodeBlockBetweenTest {

    public static StaticCodeBlockBetweenTest a = new StaticCodeBlockBetweenTest();

    {
        System.out.println("StaticCodeBlockBetweenTest:构造代码块");
    }

    static {
        System.out.println("StaticCodeBlockBetweenTest:静态代码块");
    }


    public StaticCodeBlockBetweenTest() {
        System.out.println("StaticCodeBlockBetweenTest:构造函数");
    }

    public static void main(String[] args) {
    }

}
输出:
StaticCodeBlockBetweenTest:构造代码块
StaticCodeBlockBetweenTest:构造函数
StaticCodeBlockBetweenTest:静态代码块

解析:

  • main函数作为入口,因此先要加载StaticCodeBlockBetweenTest类,由于静态变量在静态代码块之前,所以先初始化静态变量。
  • 所以先初始化静态变量时,要创建StaticCodeBlockBetweenTest对象,由于构造代码块优先于构造函数,所以先输出StaticCodeBlockBetweenTest:构造代码块,再输出StaticCodeBlockBetweenTest:构造函数
  • 然后执行静态代码块中的内容,输出StaticCodeBlockBetweenTest:静态代码块,至此StaticCodeBlockBetweenTest类加载完毕。

PS:构造代码块和构造方法在实例化对象的时候也会合并为 < init >() 方法,并且构造代码块中的内容在构造方法中的内容前面。

4.静态内部类

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

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
        InnerClass innerClass = new OuterClass.new InnerClass();
        OuterClass.StaticInnerClass staticInnerClass = new OuterClass.StaticInnerClass();
    }
}

tips:

  • 静态内部类不能访问外部类中的非静态变量和方法。
  • 静态内部类的实例化可以通过外部类.静态内部类的形式进行。
5.静态导包

jdk1.5新特性,使用 import static 可以导入某个类中的指定静态资源,不用再指明 ClassName,从而简化代码,但可读性大大降低。

6.使用static的注意事项
  • 静态只能调用静态,而非静态可以调用静态。那是因为静态资源随类的加载而加载,此时还没有创建对象。
  • 静态资源占用内存,并且内存一般不会释放
7.static 与 final (重点)

static 与 final 经常连用,用来声明一个对象共享的常量。static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问。

  • static final修饰的成员变量,必须声明的同时初始化或在静态代码块中初始化,不可被改变。
  • static final也可以修饰方法,表示该方法不能重写,可以通过类名直接调用。
  • static final常量会在编译时被替换。
public class StaticAndFinalTest {

    public static void main(String[] args) {
        System.out.println(Foo.num);
    }
}

class Foo{

    static {
        System.out.println("Foo:静态代码块");
    }

    public static final int num = 100;
}

输出:
100

解析:main方法中直接调用Foo类的 static final 变量,即使静态代码块在静态变量之前,也不会先执行静态代码块,因为 static final常量会被编译器直接替换,所以不需要加载Foo类,也就不会执行静态代码块。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值