Java看看关于代码块的这些知识,你掌握了多少?

Posted 是瑶瑶子啦

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java看看关于代码块的这些知识,你掌握了多少?相关的知识,希望对你有一定的参考价值。

  • 作者:努力学习的大一在校计算机专业学生,热爱学习和创作。目前在学习和分享:算法、数据结构、Java等相关知识。
  • 博主主页: @是瑶瑶子啦
  • 所属专栏: Java岛冒险记【从小白到大佬之路】;该专栏专注于Java相关知识,持续更新,每一篇内容优质,浅显易懂,不失深度!
  • 近期目标:写好专栏的每一篇文章

🙆‍♀️前言

一个类是由很多成员所构成,大致分为以下成员。在前面的学习中,我们学习了成员变量和成员方法。今天我们学习类中的另一个成员:代码块

目录

🌸一、基本介绍

简单来说,代码块就是用花括号 包围起来的一块代码。

语法格式

[修饰符] 
	代码

我们看到,就格式而言,代码块和方法(method)有点类似。

代码块vs方法

首先,代码块没有方法名,没有参数列表,没有返回值。这是肉眼可见,与方法不同的点。

其次,在调用上,既然都是数据和操作数据组成的代码,那么都是写来被执行的。对于方法而言,必须要被调用,方法体中的代码才可被执行。那对于静态代码块呢?静态代码块中的代码何时被执行?要弄清楚这个,以及代码块执行的作用,我们先学习代码块的分类,因为不同的代码块有区别,不可一概而论。

🌺二、代码块分类&作用

首先我们可以根据修饰符的不同,即有无static修饰。分为以下两种:

  • 静态代码块
  • 构造代码块

Tips:这里我们讨论的代码块,位置都是位于类中的,不去考虑位于方法中的代码块。

2.1:静态代码块

所谓静态代码块,就是被static所修饰的代码块
eg:

class Person 
    //实例成员变量
    private String name;
    private int age;
    private String sex;
    //静态成员变量
    private static int countNum;

    static 
        countNum = 0;
        System.out.println("countNum初始化完毕");
    

  • 作用:初始化静态成员属性
  • 执行时机:随着类的加载而执行

2.2:构造代码块

构造代码块,是指没有被static修饰的代码块,也叫作实例代码块
eg:

class Person 
    //实例成员变量
    private String name;
    private int age;
    private String sex;
    //静态成员变量
    private static int countNum;

    //静态代码块
    static 
        countNum = 0;
        System.out.println("countNum初始化完毕");
    

    //构造代码块
    
        this.name = "yaoyao";
        this.age = 18;
        this.sex = "female";
    

  • 作用:初始化实例成员变量
  • 执行时机:对象创建后,调用本类构造器前
  • 和构造器的关系:
 public Person(String name, int age, String sex) 
        //super()
        //调用本类的构造代码块
        
        //以下是本类构造器的实现
        this.name = name;
        this.age = age;
        this.sex = sex;
    

可以看到,当创建对象时,会调用构造器,但构造器中其实还有隐藏的:super()(即调用父类构造器,进行初始化),调用本类构造代码块,最后再执行本类构造器中的初始化代码。

如果对于代码块的执行顺序,还是不是很清晰,那么下面这一Part,会带你清晰的掌握,什么样的代码块在什么时候执行。

💐三:执行顺序再深入

当我们创建一个有继承关系的子类时,内存中,代码执行情况是怎样的呢?

Inheritance Inheritance classA classB classC
public class A 
    static 
        System.out.println("A类静态代码块被执行");
    

    
        System.out.println("A类构造代码块被执行");
    

    public A() 
        //super();
        //调用A类普通代码块
        System.out.println("A类构造方法被调用");
    


public class B extends A
    static 
        System.out.println("B类静态代码块被执行");
    

    
        System.out.println("B类构造代码块被执行");
    

    public B() 
        //super();
        //调用A类普通代码块
        System.out.println("B类构造方法被调用");
    

public class C extends B
    static 
        System.out.println("C类静态代码块被执行");
    

    
        System.out.println("C类构造代码块被执行");
    

    public C() 
        //super();
        //调用A类普通代码块
        System.out.println("C类构造方法被调用");
    

以下是测试类,用来测试子类C被创建时,代码块、构造方法执行顺序

public class Test 
    public static void main(String[] args) 
        C c = new C();
        //A类静态代码块被执行
        //B类静态代码块被执行
        //C类静态代码块被执行
        //A类构造代码块被执行
        //A类构造方法被调用
        //B类构造代码块被执行
        //B的构造方法被调用
        //C类构造代码块被执行
        //C类构造方法被调用
    

总结

  • 继承查找关系,从顶级父类开始,从上往下逐级加载
    • 父类静态代码块执行
    • 子类静态代码块执行
  • 调用子类构造器
    • 执行子类构造器方法体之前隐含的super()(初始化父类,调用父类构造器)
    • 执行子类构造代码块
    • 执行子类构造器方法体

关于静态代码块的执行顺序,很简单的一道题,应该所有人都会吧?

之前面试的时候做过代码块和构造方法的执行顺序,当时虽然半蒙半猜作对了,但是对这个还不是特别的了解,所以就想看看今天能不能彻底搞懂,即帮助大家、也帮助自己。

简单题

在 Java 中有静态代码块、非静态代码块(构造代码块)、普通代码块,还有静态变量、成员变量,来做道题看看你对这些代码块和变量赋值的执行顺序是否真的了解了?

上代码

public class Address {
    private String province;

    public Address(String province) {
        this.province=province;
        System.out.println("-- Address 的构造方法:province="+this.province+"");
    }

}
public class User {
    private static final Address address1=new Address("guangdong");
    private Address address2=new Address("guangxi");

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

    {
        System.out.println("-- User 的非静态代码块1--");
    }

    public User() {
        {
            System.out.println("-- User 的普通代码块1--");
        }
        System.out.println("-- User 的构造方法--");
        {
            System.out.println("-- User 的普通代码块2--");
        }
    }

    private static final Address address3=new Address("hubei");
    private Address address4=new Address("hunan");

    {
        System.out.println("-- User 的非静态代码块2--");
    }

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

}
public class StaticCodeTest {
    /**
     * 静态代码块、非静态代码块、构造方法、静态变量、成员变量的运执行顺序。
     */
    public static void main(String[] args) {
        new User();
    }
}

可以先写下自己的答案哦。

来对答案吧!

-- Address 的构造方法:province=guangdong
-- User 的静态代码块1--
-- Address 的构造方法:province=hubei
-- User 的静态代码块2--
-- Address 的构造方法:province=guangxi
-- User 的非静态代码块1--
-- Address 的构造方法:province=hunan
-- User 的非静态代码块2--
-- User 的普通代码块1--
-- User 的构造方法--
-- User 的普通代码块2--普通代码块2--

可以看到,静态代码块和静态变量的赋值是最快执行的,执行的顺序是按照在类中写的顺序来执行的,然后就是 User 的成员变量的赋值、User 的非静态代码块、最后再是构造方法,普通代码块实际上跟普通的代码差不多了哈哈。

好了,今天就到这里了,<typo data-origin="下课" ignoretag="true">下课</typo>。

复杂一丢丢的题

啊喂,就这么简单?我都不好意思发出来了,上面的题目几乎学过 Java 的都能做出来,来来来,加点难度,不是还有一个继承嘛,我给你们加上。

public class SonUser extends User {

    private static final Address address1=new Address("beijing");
    private  Address address2=new Address("nanjing");

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

    {
        System.out.println("-- SonUser 的非静态代码块1--");
    }
    public SonUser(){
        {
            System.out.println("-- SonUser 的普通代码块1--");
        }
        System.out.println("-- SonUser 的构造方法--");
        {
            System.out.println("-- SonUser 的普通代码块2--");
        }
    }
    {
        System.out.println("-- SonUser 的非静态代码块2--");
    }

    static {
        System.out.println("-- SonUser 的静态代码块2--");
    }
}
public class StaticCodeTest {
    public static void main(String[] args) {
        new SonUser();
    }
}

这个答案是有点长了。不先写好答案也行吧,来一起看看。

-- Address 的构造方法:province=guangdong
-- User 的静态代码块1--
-- Address 的构造方法:province=hubei
-- User 的静态代码块2--
-- Address 的构造方法:province=beijing
-- SonUser 的静态代码块1--
-- SonUser 的静态代码块2--
-- Address 的构造方法:province=guangxi
-- User 的非静态代码块1--
-- Address 的构造方法:province=hunan
-- User 的非静态代码块2--
-- User 的普通代码块1--
-- User 的构造方法--
-- User 的普通代码块2--
-- Address 的构造方法:province=nanjing
-- SonUser 的非静态代码块1--
-- SonUser 的非静态代码块2--
-- SonUser 的普通代码块1--
-- SonUser 的构造方法--
-- SonUser 的普通代码块2--SonUser 的普通代码块2--

可以看到有关于静态的(包含父类和子类)基本上都是在一开始就执行了,静态变量以及静态代码块,只不过父类的会比子类的更快执行,在执行完静态代码块后,就会去执行父类的变量

静态变量赋值和静态代码块,可以看作是一段静态代码的打包,按照代码顺序打包到一起,而成员变量的赋值非静态代码块也打一个包,前者我们可以称作静态代码包,后者我们可以称作非静态代码包。

在类加载时就执行静态代码包,这个只执行一次;在每次声明(new)一个对象的时候就会执行非静态代码包,然后再执行构造方法,注意这里的非静态代码包会每次都执行,构造方法就看每次调用的是那个,就执行那个构造方法。

一起来看看 Byte Code

这里的静态代码包和非静态代码包虽名称是我编的,但是确实存在这个东西。

让我们看看 User 类的字节码。

public class test/other/entity/User {

  // compiled from: User.java

  // access flags 0x1A
  private final static Ltest/other/entity/Address; address1

  // access flags 0x2
  private Ltest/other/entity/Address; address2

  // access flags 0x1A
  private final static Ltest/other/entity/Address; address3

  // access flags 0x2
  private Ltest/other/entity/Address; address4

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 25 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 10 L1
    ALOAD 0
    NEW test/other/entity/Address
    DUP
    LDC "guangxi"
    INVOKESPECIAL test/other/entity/Address.<init> (Ljava/lang/String;)V
    PUTFIELD test/other/entity/User.address2 : Ltest/other/entity/Address;
   L2
    LINENUMBER 22 L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "-- User 的非静态代码块1--"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L3
    LINENUMBER 36 L3
    ALOAD 0
    NEW test/other/entity/Address
    DUP
    LDC "hunan"
    INVOKESPECIAL test/other/entity/Address.<init> (Ljava/lang/String;)V
    PUTFIELD test/other/entity/User.address4 : Ltest/other/entity/Address;
   L4
    LINENUMBER 39 L4
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "-- User 的非静态代码块2--"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L5
    LINENUMBER 27 L5
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "-- User 的普通代码块1--"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L6
    LINENUMBER 29 L6
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "-- User 的构造方法--"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L7
    LINENUMBER 31 L7
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "-- User 的普通代码块2--"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L8
    LINENUMBER 33 L8
    RETURN
   L9
    LOCALVARIABLE this Ltest/other/entity/User; L0 L9 0
    MAXSTACK = 4
    MAXLOCALS = 1

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 9 L0
    NEW test/other/entity/Address
    DUP
    LDC "guangdong"
    INVOKESPECIAL test/other/entity/Address.<init> (Ljava/lang/String;)V
    PUTSTATIC test/other/entity/User.address1 : Ltest/other/entity/Address;
   L1
    LINENUMBER 18 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "-- User 的静态代码块1--"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L2
    LINENUMBER 35 L2
    NEW test/other/entity/Address
    DUP
    LDC "hubei"
    INVOKESPECIAL test/other/entity/Address.<init> (Ljava/lang/String;)V
    PUTSTATIC test/other/entity/User.address3 : Ltest/other/entity/Address;
   L3
    LINENUMBER 43 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "-- User 的静态代码块2--"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L4
    LINENUMBER 44 L4
    RETURN
    MAXSTACK = 3
    MAXLOCALS = 0
}

确实是吧,静态代码包就是 static <clinit>()V,而非静态代码块就是 public <init>()V。而 clinit 方法就是在这个类被加载进内存就会执行的方法,init 方法就是每次调用构造方法会调用的方法。

我看到了这一行代码INVOKESPECIAL java/lang/Object.<init> ()V,嗯哼,就是这个方法会去触发父类的构造方法啊,索嘎,所以子类会调用父类的构造方法,然后就会触发父类的加载,然后就触发了父类的静态代码包,父类的构造方法又会在第一行去触发爷类的构造方法,然后又触发爷类的加载,然后就触发了爷类的静态代码包,然后爷类。。。。。无限循环到 Object 类,就开始执行爷类的非静态代码包、父类的非静态代码包、子类的。。。

可以和递归一起联想。

我也想把父类的构造方法放在最下面,但是却只看到了 IDEA 的无情爆红,当然也是执行不了的。如果不加 super() 那就会默认加一个空参数的父类构造方法,也就是super()。

以上是关于Java看看关于代码块的这些知识,你掌握了多少?的主要内容,如果未能解决你的问题,请参考以下文章

Java8新特性关于并行流与串行流,你必须掌握这些!!

关于静态代码块的执行顺序,很简单的一道题,应该所有人都会吧?

JVM关于JVM,你需要掌握这些 | 一文彻底吃透JVM系列

JVM关于JVM,你需要掌握这些 | 一文彻底吃透JVM系列

关于Java面试,你应该准备这些知识点

UIKit框架使用总结--看看你掌握了多少