代码块详解

匿名代码块

代码块又称初始化块,属于类中的成员(是类的一部分),类似于方法:将逻辑语句封装在方法体中。通过{}包围起来
但是和方法不同,匿名代码块没有方法名,返回类型,参数。只有方法体,而且不用通过对象或类显示调用,而是加载类时,或创建对象时隐式调用

基本语法

[修饰符]{

}

  • 修饰符可选,要写的话只能写static。就是静态代码块,不写就是匿名代码块,静态代码块只会执行一次,匿名代码块每创建对象时就会执行一次
  • 代码块分两种,使用static修饰的是静态代码块,不写就是匿名代码块或者叫普通代码块
  • 结尾;号可以写也可以不写

代码块的好处

  1. 可以将匿名代码块理解成另外一种形式的构造器(或者对构造器的补充),可以做最初始化的操作
  2. 当多个构造器中都有重复的语句(不与对象相关),就可以抽象到匿名代码块中
    代码块的演示:
public class Movie {
}
class testMovie{
    private String name;
    private String director;

    public testMovie(String name) {
        System.out.println("电影开始了");
        System.out.println("放广告");
        this.name = name;
    }
    public testMovie(String name, String director) {
        System.out.println("电影开始了");
        System.out.println("放广告");
        this.name = name;
        this.director = director;
    }
}

当两个构造器中都有重复且与对象不相关的语句时,就可以抽象到匿名代码块中

class testMovie{
    private String name;
    private String director;
    {
        System.out.println("电影开始了");
        System.out.println("放广告");
    }
    public testMovie(String name) {
        this.name = name;
    }
    public testMovie(String name, String director) {
        this.name = name;
        this.director = director;
    }
}

实例化两个对象看看效果:

public class Movie {
    public static void main(String[] args) {
        testMovie m1 = new testMovie("三体");
        testMovie m2 = new testMovie("海贼王","尾田");
    }
}

电影开始了
放广告
电影开始了
放广告

静态代码块

  • 匿名代码块有static修饰被称之为静态代码块,作用也是对类进行初始化。静态代码块随着类的加载而执行,且只会执行一次,如果是匿名普通代码块(没有修饰符的代码块),每创建一个对象就会执行一次

类什么时候会加载

类加载并不指的是实例化对象,除了实例化对象时会对类进行加载,在调用类的静态成员时也会加载类
加载类的三个场景
1. 实例化对象时(new)
2. 创建子类对象时,父类也会被加载
3. 使用类的静态成员时(静态属性,静态方法)

场景演示:
1.实例化对象时

public class test {
    public static void main(String[] args) {
        new AA();
    }
}
class AA{
    static{
        System.out.println("AA的静态代码块被执行");
    }
}

输出:AA的静态代码块被执行

2.创建子类对象时,父类也会被加载

public class test {
    public static void main(String[] args) {
        new AAson();
    }
}
class AA{
    static{
        System.out.println("AA的静态代码块被执行");
    }
}
class AAson extends AA{
    static {
        System.out.println("AAson子类的静态代码块被执行");
    }
}

输出:
AA的静态代码块被执行
AAson子类的静态代码块被执行
可以看到是先加载父类再加载子类的,就像先有父亲才有儿子,所以当需要加载一个子类时,父类必须先加载。

3.使用类的静态成员时(静态属性,静态方法)

public class test {
    public static void main(String[] args) {
        System.out.println(AA.num);
    }
}
class AA{
    static int num = 99;
    static{
        System.out.println("AA的静态代码块被执行");
    }
}

输出:
AA的静态代码块被执行
99
第三点补充:当使用类的静态成员时,会加载类从而执行静态代码块。但是由于不是实例化对象所以普通代块不会被执行

public class test {
    public static void main(String[] args) {
        System.out.println(AA.num);
    }
}
class AA{
    static int num = 99;
    {
        System.out.println("AA的普通代码块被调用");
    }
    static{
        System.out.println("AA的静态代码块被执行");
    }
}

输出:
AA的静态代码块被执行
99

小结

根据上述结论可以得出普通代码块和静态代码块的区别

  1. 执行的场景不同:
    普通代码块只在实例化对象的时候执行
    静态代码块只要加载类就会执行
    (需要注意类加载和实例化对象的区别)
  2. 执行次数不同
    普通代码块每当实例化新对象时就会执行一次
    静态代码块只会执行一次
  3. 执行顺序不同
    静态代码块优先普通代码块执行

难点分析

  • 小问题:当父类同时有静态代码块和普通代码块,子类也有静态代码块和普通代码块。在main方法中实例化一个子类对象会分别执行哪些语句?
public class test {
    public static void main(String[] args) {
        new AAson();
    }
}
class AA{
    {
        System.out.println("AA的普通代码块被调用");
    }
    static{
        System.out.println("AA的静态代码块被执行");
    }
}
class AAson extends AA{
    {
        System.out.println("AAson的普通代码块被调用");
    }
    static {
        System.out.println("AAson子类的静态代码块被执行");
    }
}

输出:
AA的静态代码块被执行
AAson子类的静态代码块被执行
AA的普通代码块被调用
AAson的普通代码块被调用

单类的执行顺序

要弄清楚为什么会这样输出,首先我们得弄明白当没有继承关系时类执行的顺序是怎样的。
首先 静态代码块优先于普通代码块执行这是毋庸置疑的
当一个类中有静态代码块和静态属性初始化时,两者会按照定义的顺序从上往下依次执行,它们是平等的。待静态代码块和静态初始化执行完,就开始执行普通代码块和普通属性初始化
因此完整顺序为:

  1. 执行静态代码块和静态属性的初始化(按照定义顺序依次执行)
  2. 执行普通代码块和普通属性的初始化(按照定义顺序依次执行)
  3. 执行构造器

代码演示:

public class test {
    public static void main(String[] args) {
        new AA();
    }
}
class AA{
    static int num = getNum();
    int num1 = getNum1();
    {
        System.out.println("AA的普通代码块被执行");
    }
    static{
        System.out.println("AA的静态代码块被执行");
    }
    public static int getNum(){
        System.out.println("静态属性初始化被执行");
        return 50;
    }
    public  int getNum1(){
        System.out.println("普通属性初始化被执行");
        return 50;
        }
}

根据上述顺序依次分析执行的顺序为:
首先寻找静态成员 :
1.发现定义了静态属性 num 且调用了静态方法getNum返回一个值进行初始化,所以第一个输出静态方法里的:静态属性初始化被执行
2.然后继续往下寻找静态成员发现了静态代码块:执行输出里面的:AA的静态代码块被执行
3.接着往下寻找静态成员,发现只剩下一个静态方法了,但是方法没被调用就不执行,所以静态成员结束
开始从头寻找普通成员
4.找到普通属性 num1 调用普通方法getNum1初始化,执行输出里面的:普通属性初始化被执行
5.接着寻找普通成员,找到普通代码块,执行输出:AA的普通代码块被调用
6.接着寻早发现只有普通方法了。结束 调用默认的无参构造代码块

因此运行程序会输出:
静态属性初始化被执行
AA的静态代码块被执行
普通属性初始化被执行
AA的普通代码块被执行

根据上面的案例,其实可以总结成一个规律:
首先执行静态代码块和静态成员初始化
然后执行构造器,但是构造器有默认的super调用父类的构造器,还有隐式的调用普通代码块和初始化普通属性
public 类名(){
//super();
//调用普通代码块/初始化普通属性
…构造器语法
}

总结:先执行所有静态内容再执行构造器,而构造器里有默认的super()和调用普通代码块

子类的执行顺序(重中重)

根据上面的分析其实已经可以得出开头小问题为什么是那样执行的。
看以下代码分析输出的是什么?

public class test {
    public static void main(String[] args) {
        new BB();
    }
}
class AA{
    static int num = getNum();
    int num1 = getNum1();
    {
        System.out.println("AA的普通代码块被执行");
    }
    static{
        System.out.println("AA的静态代码块被执行");
    }
    public static int getNum(){
        System.out.println("AA的静态属性初始化被执行");
        return 50;
    }
    public  int getNum1(){
        System.out.println("AA的普通属性初始化被执行");
        return 50;
        }
        public AA(){
            System.out.println("AA的无参构造器执行");
        }
}
class BB extends AA{
    static int b1 = getB1();
    int b2 = getB2();
    public static int getB1(){
        System.out.println("BB的静态属性初始化");
        return 50;
    }
    public  int getB2(){
        System.out.println("BB的普通属性初始化");
        return 40;
    }
    static{
        System.out.println("BB的静态代码块执行");
    }
    {
        System.out.println("BB的普通代码块执行");
    }
    public BB(){
        System.out.println("BB的无参构造器执行");
    }
}

(需仔细理解)
首先执行所有静态内容(子类和父类都是)
由于首先加载父类再加载子类所以从父类开始找:
1.按照顺序查找发现静态属性执行:AA的静态属性初始化被执行
2.静态代码块执行:AA的静态代码块被执行
父类的静态内容执行完毕
开始加载子类
3.从子类开始查找静态内容,发现静态属性:BB的静态属性初始化
3.然后找到下面的静态代码块:BB的静态代码块执行
此时所有静态内容执行完毕,开始创建实例对象
进入BB的构造器
4.BB的构造器中隐式的有super()和调用BB的普通代码块/初始化普通属性
5.根据super()进入父类AA的构造器,AA的构造器也有super()和调用BB的普通代码块/初始化普通属性
6.但是AA的super()是Object的什么也不会输出,所以忽略即可
7.接着执行AA构造器中调用普通代码块和初始化普通属性(按照顺序)
8.所以从上往下查找发现普通属性定义且初始化:AA的普通属性初始化被执行
9.接着发现普通代码块:AA的普通代码块被执行
10.没有普通内容可以执行后,接着执行AA构造器的语句:AA的无参构造器执行
11.至此AA的构造器执行完毕返回BB的构造器执行super()下面的调用BB的普通代码块/初始化普通属性
12.按照顺序依次初始化普通属性和调用普通代码块(平等关系,按照顺序)
13.b1的初始化:BB的普通属性初始化
14.执行BB的普通代码块:BB的普通代码块执行
15.普通内容全部执行完毕后,执行BB构造器的内容:BB的无参构造器执行

至此终于程序执行完毕
所以按照分析会依次输出:
AA的静态属性初始化被执行
AA的静态代码块被执行
BB的静态属性初始化
BB的静态代码块执行
AA的普通属性初始化被执行
AA的普通代码块被执行
AA的无参构造器执行
BB的普通属性初始化
BB的普通代码块执行
BB的无参构造器执行
在这里插入图片描述
分析完全正确
因此可以得出类加载和实例化对象的基本区别和大致概念
类加载:加载类的信息
实例化对象:类加载+运行构造器
也可以得出当实例化对象时的执行顺序:

1.父类的静态代码块和静态属性初始化
2.子类的静态代码块和静态属性初始化
3.父类的普通代码块和普通属性初始化
4.父类的构造器代码块
5.子类的普通代码块和普通属性初始化
6.子类的构造器代码块

ps:代码块和属性初始化是平等关系谁先定义就谁先

代码块分静态和普通,规则也和之前说的静态和非静态一样
静态代码块:只能调用静态成员
非静态代码块:都可以调用

代码块练习

1.下列代码会输出什么?

public class test1 {
    public static void main(String[] args) {
        System.out.println("total = "+Person.total);
        System.out.println("total = "+Person.total);
    }
}
class Person{
    public static int total;
    static {
        total = 100 ;
        System.out.println("in static block");
    }
}

分析:
输出类名.静态属性,

  1. 首先加载Person类
  2. 执行静态内容 对total初始化
  3. 然后执行静态代码块 :给total赋值且输出in static block
  4. 然后返回main方法,输出total的值 total = 100
  5. 接着下面还是输出total的值,但是由于上面已经加载过Person类了,且静态代码块只会执行一次。所以不会再次执行静态代码块
  6. 再输出一个 total = 100

所以输出内容如下:
in static block
total = 100
total = 100

2.看看下面输出什么?

public class test1 {
    Sample s1 = new Sample("s1成员初始化");
    static Sample s2 = new Sample("s2静态成员初始化");
    static {
        System.out.println("static代码块执行");
        if(s2 == null){
            System.out.println("s2 is null");
        }
    }
    test1(){
        System.out.println("test1的无参构造器被调用");
    }
    public static void main(String[] args) {
        test1 a = new test1();
    }
}
class Sample{
    Sample(String s){
        System.out.println(s);
        System.out.println("Sample的有参构造器被调用");
    }
    Sample(){
        System.out.println("Sample的无参构造器被调用");
    }
}

1.进入main方法,发现要实例化一个test1的对象,因此先加载test1类
2.开始查找test1类的静态内容:发现类变量s2,由于初始化是Sample类的实例化,所以先去加载Sample类
3.发现Sample类没有静态内容和普通内容,所以直接执行Sample的有参构造器输出:s2静态成员初始化 和 Sample的有参构造器被调用
4.接着回到test1继续寻找下面的静态内容,执行静态代码块输出:static代码块执行。s2!= null,所以语句不输出
5.静态内容全部执行完毕,开始执行普通内容
6.初始化普通变量 s1,调用Sample的普通内容,但是它没有所以直接执行构造器输出:s1成员初始化 和 Sample的有参构造器被调用
7.普通内容也执行完毕后,再是test1的构造器输出:test1的无参构造器被调用

所以程序运行后依次输出:

s2静态成员初始化
Sample的有参构造器被调用
static代码块执行
s1成员初始化
Sample的有参构造器被调用
test1的无参构造器被调用


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值