• 代码块详解


    匿名代码块

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

    基本语法

    [修饰符]{

    }

    • 修饰符可选,要写的话只能写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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

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

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

    静态代码块

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

    类什么时候会加载

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

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

    public class test {
        public static void main(String[] args) {
            new AA();
        }
    }
    class AA{
        static{
            System.out.println("AA的静态代码块被执行");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    输出: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子类的静态代码块被执行");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    输出:
    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的静态代码块被执行");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    输出:
    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的静态代码块被执行");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    输出:
    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子类的静态代码块被执行");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    输出:
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    根据上述顺序依次分析执行的顺序为:
    首先寻找静态成员 :
    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
    • 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

    (需仔细理解)
    首先执行所有静态内容(子类和父类都是)
    由于首先加载父类再加载子类所以从父类开始找:
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    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的无参构造器被调用


  • 相关阅读:
    贝叶斯建模:从先验合理性到后验分布
    ipc----共享内存
    891. 子序列宽度之和(每日一难phase3-4)
    亚马逊、ebay、沃尔玛卖家打造爆款如何利用测评提高转化率?
    可自定义评教系统(教学质量评估系统)设计与实现(SSM)毕业论文+设计源码+mysql文件
    短信验证码
    分布式事务Seata源码解析九:分支事务如何注册到全局事务
    【web3py】批量创建eth账号
    语法练习:front_back
    WSL2环境下Debian 12的Docker安装与配置
  • 原文地址:https://blog.csdn.net/WINorYU/article/details/127675186