精华内容
下载资源
问答
  • 阿里笔试题之写出程序输出结果: package com.patrick.bishi; public class TestVar { public static int k = 0; public static TestVar t1 = new TestVar("t1"); public static TestVar t2 = new TestVar(...

    阿里笔试题之写出程序输出结果:

    package com.patrick.bishi;
    
    public class TestVar {
    	public static int k = 0;
    	public static TestVar t1 = new TestVar("t1");
    	public static TestVar t2 = new TestVar("t2");
    	public static int i = print("i");
    	public static int n = 99;
    	public int j = print("j");
    	{
    		print("构造");
    	}
    	static {
    		print("静态");
    	}
    
    	public TestVar(String str) {
    		System.out.println((++k) + ":" + str + "	i=" + i + "	n=" + n);
    		++i;
    		++n;
    	}
    
    	public static int print(String str) {
    		System.out.println((++k) + ":" + str + "	i=" + i + "	n=" + n);
    		++n;
    		return ++i;
    	}
    
    	public static void main(String[] args) {
    		TestVar t = new TestVar("init");
    	}
    }
    
    结果:

    1:j	i=0	n=0
    2:构造	i=1	n=1
    3:t1	i=2	n=2
    4:j	i=3	n=3
    5:构造	i=4	n=4
    6:t2	i=5	n=5
    7:i	i=6	n=6
    8:静态	i=7	n=99
    9:j	i=8	n=100
    10:构造	i=9	n=101
    11:init	i=10	n=102

    分析:

            初始化一个类(main方法执行之前),主要是对静态成员变量的初始化,过程分为两步:1)按照静态成员变量的定义顺序在类内部声明成员变量;2)按类中成员变量的初始化顺序初始化。

            本例中,先声明成员变量k,t1,t2,i,n,此时,k=i=n=0;t1=t2=null,然后按顺序初始化这几个成员变量,k复制0,t1初始化,执行构造函数,构造函数执行需要先对对象的成员属性先进行初始化,因此执行 j 的初始化,再执行对象的代码块,再是构造函数本身,同理t2初始化,接下来再是 i 和  n 的初始化,然后到main方法中的对象 t 的初始化,对象的初始化同理t1的初始化。

    参考http://www.cnblogs.com/lmtoo/archive/2012/04/08/2437918.html,得到此例转换成class的类似代码:

    package com.patrick.bishi;
    
    public class TestVarClass {
    
    	public static int k = 0;
    	public static TestVarClass t1 = null;
    	public static TestVarClass t2 = null;
    	public static int i = 0;
    	public static int n = 0;
    	static {
    		k = 0;
    		t1 = new TestVarClass("t1");
    		t2 = new TestVarClass("t2");
    		i = print("i");
    		n = 99;
    		print("静态");
    	}
    
    	int j = 0;
    
    	public TestVarClass(String str) {
    		j = 0;
    		{
    			j = print("j");
    			print("构造");
    		}
    		System.out.println((++k) + ":" + str + "	i=" + i + "	n=" + n);
    		++i;
    		++n;
    	}
    
    	public static int print(String str) {
    		System.out.println((++k) + ":" + str + "	i=" + i + "	n=" + n);
    		++n;
    		return ++i;
    	}
    
    	public static void main(String[] args) {
    		TestVarClass t = new TestVarClass("init");
    	}
    
    }
    



    展开全文
  • java面试宝典》三 初始化和类实例化顺序

    千次阅读 多人点赞 2020-01-15 17:55:25
    现侧重于java底层学习和算法结构学习,希望自己能改变这种现状。 为什么大厂面试,更侧重于java原理底层的提问,因为通过底层的提问,他能看出一个人的学习能力,看看这个人的可培养潜力。随着springboot的流行,大部分的...

    前言

    社长,一个爱学习,爱分享的程序猿,始终相信,付出总会有回报的。知识改变命运,学习成就未来。爱拼才会赢!
    程序猿学社的GitHub,已整理成相关技术专刊,欢迎Star:
    https://github.com/ITfqyd/cxyxs

    社长,4年api搬运工程师,之前做的都是一些框架的搬运工作,做的时间越长,越发感觉自己技术越菜,有同感的社友,可以在下方留言。现侧重于java底层学习和算法结构学习,希望自己能改变这种现状。
    为什么大厂面试,更侧重于java原理底层的提问,因为通过底层的提问,他能看出一个人的学习能力,看看这个人的可培养潜力。随着springboot的流行,大部分的开发,起步就是springboot。也不看看springboot为什么简单?他底层是如果实现的。为什么同样出来4年,工资差别都很大?这些问题都值得我们深思(对社长自己说的)。
    api工程师,顶天了,最多达到某些公司中级工资上限,很难突破高级这道坎。希望我们大家成就更好的自己。回想起往事,不会后悔。

    面试的时候,经常会遇到这样的笔试题:给你两个类的代码,它们之间是继承的,每个类中有一些静态方法,静态代码块,非静态代码块,静态变量。

    面试官为什么要这样考?

    实际上就是考察我们对类初始化基础知识的掌握情况。

    对象初始化顺序:

    1.首先,初始化父类中的静态成员变量和静态代码块,按照在程序中出现的顺序初始化;
    2.然后,初始化子类中的静态成员变量和静态代码块,按照在程序中出现的顺序初始化;
    3.其次,初始化父类的普通成员变量和代码块,再执行父类的构造方法;
    4.最后,初始化子类的普通成员变量和代码块,再执行子类的构造方法;

    类初始化过程:

    1.一个类初始化,实际上就是初始化<clinit>方法。
    注意:<clinit>方法,可能不存在,需要有静态方法或者静态代码块。
    2.一个子类要想初始化,需要先初始化他的父类。

    类实例化过程:

    类实例化就是初始化<init>方法

    1. 有几个构造方法,就有<init>方法
    2. 非静态方法和非静态代码块根据代码从上到下的顺序,依次执行。
    3. 构造器最后才调用

    <clinit>方法,什么鬼?社长你在给我开玩笑吗?

    在我们编写的代码中,不会存在这个方法,但是我们可以通过查看我们java文件对应的class可以发现是能找到这个方法的。

     

    为了证明论证只有静态代码块或者静态方法才有<clinit>方法

    public class Test1 {
        private int a=1;
        public static void main(String[] args) {
    
        }
    }
    

     


    作为一个Java开发者,对技术的追求而不仅仅停留在会用API,如果只是会用,那一辈子就是一个api程序猿,也就是大家所谓的搬砖的,我们应该深入底层,我们大家都知道我们编写的java文件,会编译成class文件,而又有多少人,去看过class文件长什么样子的。实际上class文件就是字节码文件,这涉及汇编的一些知识。后续,社长会整理出如何解析class字节码的相关文章。这里只是了解即可,知道有这么一回事。

     

     

    难度系数:1 星

    package com.fyqd.test.classload;
    
    /**
     * Description:动物园
     * Author: 程序猿学社
     * Date:  2020/1/14 9:35
     * Modified By:
     */
    public class Zoo {
        public static String staticPk = staticPk();
        public String pk = pk();
    
        static {
            System.out.println("父类静态代码块!");
        }
    
        {
            System.out.println("父类非静态代码块");
        }
    
        public static String staticPk() {
            System.out.println("父类静态方法!");
            return "PK";
        };
    
        public  String pk() {
            System.out.println("父类普通方法!");
            return "PK";
        };
    
        public Zoo() {
           System.out.println("父类构造器");
        }
    }
    

    测试类

    public class Main {
        public static void main(String[] args) {
            Zoo zoo= new Zoo();
        }
    }
    

    各位社友觉得输出到控制台的结果应该是多少?

     


    结论;我们可以发现运行的顺序排序 静态>非静态>结构。如果是属于同一个等级,例如静态方法和静态代码块,则根据代码的从上到下的顺序来执行。

     

    难度系数:2 星

    package com.fyqd.test.classload;
    
    /**
     * Description:动物园
     * Author: 程序猿学社
     * Date:  2020/1/14 9:35
     * Modified By:
     */
    public class Zoo {
        public static String staticPk = staticPk();
        public String pk = pk();
    
        static {
            System.out.println("父类静态代码块!");
        }
    
        {
            System.out.println("父类非静态代码块");
        }
    
        public static String staticPk() {
            System.out.println("父类静态方法!");
            return "PK";
        };
    
        public  String pk() {
            System.out.println("父类普通方法!");
            return "PK";
        };
    
        public Zoo() {
           System.out.println("父类构造器");
        }
    }
    package com.fyqd.test.classload;
    
    /**
     * Description:  big
     * Author: 程序猿学社
     * Date:  2020/1/14 9:35
     * Modified By:
     */
    public class Pig extends  Zoo{
        public static String staticPk = staticPk();
        public String pk = pk();
    
        static {
            System.out.println("子类静态代码块!");
        }
    
        {
            System.out.println("子类非静态代码块");
        }
    
        public static String staticPk() {
            System.out.println("子类静态方法!");
            return "PK";
        };
    
        public  String pk() {
            System.out.println("子类普通方法!");
            return "PK";
        };
    
        public Pig() {
            System.out.println("子类构造器");
        }
    }
    

    测试类:

    package com.fyqd.test.classload;
    
    /**
     * Description:
     * Author: wude
     * Date:  2020/1/14 9:55
     * Modified By:
     */
    public class Main {
        public static void main(String[] args) {
            Pig pig = new Pig();
        }
    }
    

    有一些社友反馈,社长之前出的题,太简单了,社长出了一道2星级别的题目,大家可以先不看答案,看看输出的控制台的结果是多少?能答对的社友,首先得恭喜下,从青铜,晋级到白银分段。

     


    眼尖的社友,可能发现一个问题。
    社长,社长,按你说的对象初始化顺序第三条,这里的打印应该是父类普通方法,父类非静态代码块才对。

     

     

    社长跟大家说说,为什么会有这种问题出现?是谁在搞事情?

    实际上是方法重写在搞事情,通过main方法,我们可以发现new的对象是子类,也就是说当前的this的指向就是子类。而这里本应该要调用父类的方法,由于被子类重写了,通过对象的多态性可知,这里打印的就是子类普通方法。

    那些方法不能被重写?

    final方法
    静态方法
    private修饰的方法。

    难度3星 黄金段位

    package com.fyqd.test.classload;
    
    /**
     * Description:  pig
     * Author: 程序猿学社
     * Date:  2020/1/14 9:35
     * Modified By:
     */
    public class Pig extends  Zoo{
        public static String staticPk = staticPk();
        public String pk = pk();
    
        static {
            System.out.println("子类静态代码块!");
        }
    
        {
            System.out.println("子类非静态代码块");
        }
    
        public static String staticPk() {
            System.out.println("子类静态方法!");
            return "PK";
        };
        @Override
        public  String pk() {
            System.out.println("子类普通方法!");
            return "PK";
        };
    
        public Pig() {
            System.out.println("子类构造器");
        }
    }
    package com.fyqd.test.classload;
    
    /**
     * Description:动物园
     * Author: 程序猿学社
     * Date:  2020/1/14 9:35
     * Modified By:
     */
    public class Zoo {
        public static String staticPk = staticPk();
        public String pk = pk();
    
        static {
            System.out.println("父类静态代码块!");
        }
    
        {
            System.out.println("父类非静态代码块");
        }
    
        public static String staticPk() {
            System.out.println("父类静态方法!");
            return "PK";
        };
    
        public  String pk() {
            System.out.println("父类普通方法!");
            return "PK";
        };
    
        public Zoo() {
           System.out.println("父类构造器");
        }
    }
    public class Main {
        public static void main(String[] args) {
            Pig pig = new Pig();
            System.out.println("---------");
            pig = new Pig();
        }
    }
    

    各位社友,再看看如上的代码,输出到控制台的结果应该是?

     

     

    各位社友觉得这道理相对于前一道题,主要是考的那些方面?

    实际上,就是考大家对类的初始化了解多少。实际上类初始化的时候,就会执行<clinit>方法,而<clinit>方法,就是通过静态代码块,静态变量,静态常量方法等组成。

    后记

    程序猿学社的GitHub,欢迎Star:
    https://github.com/ITfqyd/cxyxs
    觉得有用,可以点赞,关注,评论,留言四连发。

     

    展开全文
  • 类实例化顺序一 概述 阿里巴巴一道笔试题加载及初始化详解2.1 源码到字节码2.1.1 结构简介2.1.2 字节码反汇编工具2.2 加载机制2.2.1 加载2.2.2 链接2.2.3 初始化2.3 初始化过程【重点】2.3.1 方法详解...

    一 概述 阿里巴巴一道笔试题

    面试时,经常会遇到看代码写运算结果的题目,里面涉及静态变量,静态代码块,静态方法,实例变量,实例代码块,构造方法等的代码,如下:

    请说程序执行运算结果。

    public class Programer {
        public static int salary = getSalary(); //静态变量
        private int workAge = getWorkAge();//实例变量
    
        static {//静态代码块
            System.out.println(1); 
        }
    
        private static int getSalary() { //静态方法
            System.out.println(2);
            return 20000;
        }
    
        public Programer() { //构造方法
            System.out.println(3);
        }
    
        {//实例代码块
            System.out.println(4); 
        }
    
        private int getWorkAge() { //非静态方法
            System.out.println(5);
            return 10;
        }
    
    
        public static void main(String[] args) {//主方法
            new Programer();
          	new Programer();
            System.out.println(6);
        }
    }
    
    

    参考答案:

    2
    1
    5
    4
    3
    5
    4
    3
    6
    

    相信大部分同学根据自身的经验,是能轻松解题的。但在实际的面试中题目可能变幻莫测,如果对类的实例化顺序不太熟悉,解决起来估计还是有一定难度的。

    因此今天就对类实例化做一个专题讲解,我们从写的java源代码到字节码,再从字节码执行流程反推到java源代码来讲解相关代码的执行流程。

    二 类加载及初始化详解

    2.1 源码到字节码

    2.1.1 类结构简介

    javac Student.java--->Student.class
    

    在这里插入图片描述

    使用工具javac编译的过程中,其实会经历非常复杂的过程,我们不用去深入研究。我们只要关心编译之后的class字节码文件。

    这个class字节码文件中含有哪些数据呢,如何查看呢?我们可以从官方的文档中找到答案。

    首先,class文件是二进制文件,直接使用文本工具读取是看不到信息的。我们可以使用专门的软件来查看class文件中的字节信息。例如:winhex, sublime

    查看Student.class字节码信息如下:
    在这里插入图片描述
    如果要看懂上述文件,我们需要查阅官方文档说明:
    https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
    在这里插入图片描述

    2.1.2 字节码反汇编工具

    查看字节码文件如果直接使用二进制工具去读,可读性极差。我们可以借助一些工具帮我们查看类中的信息,而且还能看到反编译后的汇编指令。 通过汇编代码,我们可以深入的了解java代码的工作机制。

    1. javap

    javap是JDK自身携带的反编译工具,我们可以直接使用,很方便:

    在cmd或者在IDEA的Terminal中可以查看javap的帮助信息:

    javap -help
    

    在这里插入图片描述
    可以根据自己的需要给javap加入参数使用,例如:

    javap -p -v  xxxx.class  // 显示所有类和成员的
    

    这个是最直接,最方便的查看工具了,但是可读性没有一些插件好。
    在这里插入图片描述
    jclasslib:比较强大,可以查看类中的所有信息

    ASMPlugin:主要可以用来查看汇编指令,格式会比较友好。

    先去打开设置中的plugins,安装插件,完成后需要重启。

    • jclasslib插件使用:在这里插入图片描述
    • 显示效果
      在这里插入图片描述
      ASM Bytecode Viewer

    直接在源码文件中鼠标右键,选择ASM Bytecode Viewer 就可以了
    在这里插入图片描述
    界面:在这里插入图片描述

    当然插件有很多,功能大致一样,关键是能够灵活使用,看懂插件的内容。后续的一些内容,我们将会结合工具使用进行讲解相关知识点。

    2.2 类加载机制

    在这里插入图片描述
    Java虚拟机会动态的加载、链接与初始化类和接口。

    JVM专题课程: http://yun.itheima.com/course/584.html
    步骤:

    2.2.1 加载

    加载是根据特定的名称查找类或者接口类型的二进制表示,并由此二进制形式来创建类或接口的所对应Class对象的过程。

    1. 通过一个类的全限定名获取定义此类的二进制字节流。
    2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
    3. 在Java堆中生成一个代表这个类的java.lang.Class对象, 作为对方法区中这些数据的访问入口

    2.2.2 链接

    链接过过程中要做的三步:

    1. 验证:保证被加载类的正确性

      文件格式验证

      元数据验证

      字节码验证

      符号引用验证

    2. 准备

      为类的静态变量分配内存, 并将其初始化为默认值

      例如:

      class A{
        static int a=10;
      }
      
      准备阶段会为a静态变量分配4个字节的空间,并初始值为0
    3. 解析

      把类中的符号引用转换为直接引用

      类中的符号引用就是class字节码中的原始信息,比如变量名,方法名,类名等,需要解析成为运行时内存中相关的地址引用,方便使用。

      例如:调用一个方法时,在java语言层面就是一个方法名这个符号,但是在底层应该要根据这个符号找到内存中的直接地址来调用。

    2.2.3 初始化

    对类的静态变量, 静态代码块执行初始化操作

    class A{
      static int a = 10;
    }
    
    到了初始化阶段,a的值就可以赋值为10

    这个阶段其实是<clinit>方法执行过程的体现,请看2.3.1节

    2.3 类初始化过程【重点】

    一个类要实例化,必须先要让自己先初始化,类初始化过程主要是对静态成分的初始化。如下:

    public class Student{
      public static String name="itheima"; //静态变量
      static{//静态代码块
        name = "itcast";
      }
    }
    

    我们使用反编译工具将Student类字节码反编译,如下:

    // class version 53.0 (53)
    // access flags 0x21
    public class Student {
    
      public static Ljava/lang/String; name
    
      public <init>()V   
       L0
        LINENUMBER 1 L0
        ALOAD 0
        INVOKESPECIAL java/lang/Object.<init> ()V
        RETURN
       L1
        LOCALVARIABLE this LStudent; L0 L1 0
        MAXSTACK = 1
        MAXLOCALS = 1
    
      static <clinit>()V
       L0
        LINENUMBER 2 L0
        LDC "itheima"
        PUTSTATIC Student.name : Ljava/lang/String;
       L1
        LINENUMBER 4 L1
        LDC "itcast"
        PUTSTATIC Student.name : Ljava/lang/String;
       L2
        LINENUMBER 5 L2
        RETURN
        MAXSTACK = 1
        MAXLOCALS = 0
    }
    
    

    2.3.1 方法详解

    当一个类编译之后,字节码文件中会产生一个类构造器方法<cinit>(),而这个方法中的逻辑就是当前类中的静态变量和静态代码块的初始化逻辑整合

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KZxcgaam-1587961908980)(imgs/image-20200409173545310.png)]

    2.3.2 静态变量和静态代码块初始化顺序

    1. 静态变量直接赋值底层原理:

      当字节码在加载过程中的链接阶段时,有三个步骤,分别是验证,准备,解析。在准备阶段,为类的静态变量分配内存, 并将其初始化为默认值。只有到了初始化阶段才会正式赋值。

      public static int a = 10;
      //在链接的准备阶段:开辟空间存储a的数据,初始为0
      //在出初始化阶段:将静态变量a赋值为10
      
    2. 静态代码块

      静态代码块的逻辑也将会在类的初始化阶段执行。

    实践:

    静态变量的赋值和静态代码块中的逻辑,都会整合到<cinit>方法中执行,那么问题来了,如果多个静态代码块和静态变量的赋值,那么他的初始化顺序又会是怎样的呢?例如:

    public class Student {
        static int height;//0
        static int age = 10;
    
        static {
            age = 20;
        }
    
        static {
            name = "Jack";
        }
    
        static String name = "Rose";
    
        public static void main(String[] args) {
            System.out.println(height);//0
            System.out.println(name);//Rose
            System.out.println(age);//20
        }
    }
    

    要解答这个问题,你要观察编译后的字节码中<cinit>方法中具体指令就可以了。如下:

    在这里插入图片描述

    指令解析:

      static <clinit>()V
       L0
        LINENUMBER 3 L0							//L0 对应 源码中第3行
        BIPUSH 10										//将单字节的常量值10推入到栈顶
        PUTSTATIC Student.age : I		//将栈顶的值10取出赋值给 Student.age
       L1
        LINENUMBER 6 L1
        BIPUSH 20										
        PUTSTATIC Student.age : I		
       L2
        LINENUMBER 10 L2
        LDC "Jack"									//将字符串常量“Jack”推入栈顶
        PUTSTATIC Student.name : Ljava/lang/String;		
       L3
        LINENUMBER 13 L3
        LDC "Rose"
        PUTSTATIC Student.name : Ljava/lang/String;
        RETURN											//结束
        MAXSTACK = 1
        MAXLOCALS = 0
    }
    

    结论:

    1. 如果静态变量只有定义,没有做赋值初始化,那么只有默认值,在<cinit>方法中不会看到赋值的指令。
    2. 静态代码块和静态变量直接赋值初始化顺序是按照编码顺序从上到下完成的。静态变量的值以最后一次赋值为准。

    如果没有静态变量的直接赋值,也没有静态代码块,那么就不会产生<cinit>方法了,这个可以在反编译工具中验证。

    2.3.3 继承中类初始化分析

    当一个类存在父类时,一定是先对父类进行初始化然后再初始化子类的。

    看下面的代码,说出执行顺序。

    class Fu {
      
        static int a = getNum1();
    
        static {
            System.out.println("1");
        }
    
        private static int getNum1() {
            System.out.println("2");
            return 10;
        }
    }
    
    class Zi extends Fu {
        static int b = getNum2();
    
        static {
            System.out.println("3");
        }
      
        public static int getNum2() {
            System.out.println("4");
            return 20;
        }
    
        public static void main(String[] args) {
    			//main方法执行
        }
    }
    

    答案:

    2
    1
    4
    3
    

    三 对象创建和初始化过程

    定义一个Teacher类,里面含有实例变量,实例代码块,构造方法。定义另外一个测试类Test,在main方法中创建Teacher对象,研究其过程。

    public class Teacher {
        int age = 10;
     
        {
            age = 20;
        }
    
        public Teacher() {
    
        }
    
        public Teacher(int age) {
            this.age = age;
        }
    }
    
    class Test{
        public static void main(String[] args) {
            Teacher t = new Teacher();
        }
    }
    
    

    3.1 new对象底层字节码指令分析

    就一句创建对象的语句在底层字节码指令是怎样体现的呢?

    Teacher t = new Teacher();
    

    如下:
    在这里插入图片描述

    public static main([Ljava/lang/String;)V
    L0
    		LINENUMBER 18 L0
    		NEW Teacher 							//创建Teacher对象,并将引用值压入栈顶
    		DUP												//复制栈顶的引用,重新入栈(操作数栈存在连个相同引用)
    		INVOKESPECIAL Teacher.<init> ()V			//调用无参构造器进行初始化
    		ASTORE 1								//将栈顶引用值赋值给变量表中第二个变量
    L1
        LINENUMBER 19 L1
        RETURN
    L2
    		//本地变量表
        LOCALVARIABLE args [Ljava/lang/String; L0 L2 0    
        LOCALVARIABLE t LTeacher; L1 L2 1
        MAXSTACK = 2
        MAXLOCALS = 2
    

    虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程,类中静态相关成分就在这个过程中完成初始化,这个过程可以体现在<cinit>方法中(请看2.3章节)。

    ​ 在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。对象内存分配同时,实例变量也会被赋予默认值(零值)。

    ​ 在内存分配完成之后,Java虚拟机就会开始对新创建的对象按照程序员的意志进行初始化。在Java对象初始化过程中,主要涉及三种执行对象初始化的结构,分别是 实例变量初始化实例代码块初始化 以及 构造函数初始化,不管是哪一种方式的初始化,编译之后都会在方法 <init>中统一执行。

    3.2 对象初始化过程详解【重点】

    3.2.1 <init>方法详解
    在这里插入图片描述
    图中可以看出,开辟对象空间所用的指令是 new 完成的。当程序员给实例变量直接赋值,或者使用实例代码块,构造方法去给实例变量初始化值,编译器最后都会统一的放到<init>方法完成。

    <init> 方法 和 java中的构造方法是一一对应的,有多少个构造方法就有多少个<init>方法,请看示例:

    public class A {
        int c;
    
        {
            b = 200;
        }
    
        int b = 20;
    
        int a = 10;
    
        public A() { }
    
        public A(int c) {
            this.c = c;
        }
    }
    
    

    反编译字节码,观察<init>方法
    在这里插入图片描述
    我们从反编译后的结果可以推导出以下结论:

    1. 每一个构造方法都会对应一个<init>方法
    2. 每个<init>方法内部都会先执行父类的<init>方法

    3.2.2 实例变量初始化顺序分析

    我们继续研究3.2.1节中的代码,观察下实例变量直接赋值,及在实例代码块中赋值,在构造方法中赋值这三种方式顺序特点。
    在这里插入图片描述
    可以得到以下结论:

    • 执行完父类的<init>方法后按编码顺序,从上到下再执行直接赋值或者实例代码块,最后执行构造方法内部的赋值语句。

    • <init>方法的模型:
      在这里插入图片描述

    3.2.3 继承中实例变量初始化顺序

    从上一节中模型图中还可以看出,父类中的实例变量赋值,实例代码块逻辑,构造方法执行是优先于子类完成的,如下:
    在这里插入图片描述

    四 类实例化顺序总结

    通过前两章的学习,我们大致理清类实例化一般的过程了,分成两个阶段:

    4.1 类初始化阶段

    • 类初始化阶段底层有<cinit>方法完成,<cinit>方法是有静态变量赋值语句和静态代码块的结合而形成的。
    • 静态变量赋值语句和静态代码块的执行顺序有其本身编码顺序决定,自上往下执行。
    • 如果父类存在静态资源的初始化,那么父类会优先与子类完成执行<cinit>方法。因为子类是依赖父类而存在的。
    • 一个类中如果没有静态变量的赋值也没有静态代码块,不会存在<cinit>方法

    4.2 对象的创建和初始化阶段

    • 对象的创建及初始化阶段,对象空间的开辟是由new指令完成的。对应空间中实例变量的初始化则由<init>方法完成的。

    • <init>方法由实例变量赋值语句,实例代码块,构造方法结合而成,每个java中的构造方法都对应了一个<init>方法。

    • <init>方法中先执行父类的<init>方法,然后执行本类的<init>方法剩下的内容。如下图:

    • 在这里插入图片描述

    4.3 类初始化和对象初始化顺序探究

    大部分情况下,我们都会看到一个现象,类初始化会优先于对象的初始化。例如,

    public class A {
      //静态变量
        static int a = getA();
    
        private static int getA() {
            System.out.print(1);
            return 10;
        }
    	//静态代码块
        static {
            System.out.print(2);
            a = 20;
        }
      
      //非静态
        int b = getB();
        private int getB() {
            System.out.print(3);
            return 20;
        }
    	//实例代码块
        {
            System.out.print(4);
            b = 40;
        }
    	//构造方法
        public A() {
            System.out.print(5);
        }
    	
        public static void main(String[] args) {
            System.out.print(6);
            new A();
        }
    
    }
    
    
    

    先完成类初始化,然后执行main方法,main方法中创建对象初始化对象。

    我们很轻松就能得到结果: 126345

    然而这不是一个绝对的情况,类初始和实例化可能会混合在一起完成。如下:

    增加一个静态变量,类型是本身,并创建对象直接赋值。

    public class A {
        //静态变量
        static int a = getA();
    
        static A obj = new A();//******新加入********
    
    
        private static int getA() {
            System.out.print(1);
            return 10;
        }
    
        //静态代码块
        static {
            System.out.print(2);
            a = 20;
        }
    
        //非静态
        int b = getB();
    
        private int getB() {
            System.out.print(3);
            return 20;
        }
    
        //实例代码块
        {
            System.out.print(4);
            b = 40;
        }
    
        //构造方法
        public A() {
            System.out.print(5);
        }
    
        public static void main(String[] args) {
            System.out.print(6);
            new A();
        }
    
    }
    
    
    

    当类加载后完成第二个阶段链接,其实就可以投入使用了。在初始化阶段中,如果涉及到本类的对象实例化也是可以完成的。我们可以从底层代码论证:
    在这里插入图片描述

    五 经典笔试案例

    5.1 阿里经典笔试讲解

    阅读代码,分析出打印结果:

    public class Test1 {
        public static int k = 0;
        public static Test1 t1 = new Test1("t1");
        public static Test1 t2 = new Test1("t2");
        public static int i = print("i");//3
        public static int n = 99;
        public int j = print("j");
    
        static {
            print("静态块");
        }
      
        public Test1(String str) {
            System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
            ++i;
            ++n;
        }
    
        {
            print("构造块");
        }
    
        public static int print(String str) {
            System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
            ++n;
            return ++i;
        }
    
        public static void main(String[] args) {
            Test1 t = new Test1("init");
        }
    }
    

    参考答案

    1:j i=0 n=0
    2:构造块 i=1 n=1
    3:t1 i=2 n=2
    4:j i=3 n=3
    5:构造块 i=4 n=4
    6:t2 i=5 n=5
    7:i i=6 n=6
    8:静态块 i=7 n=99
    9:j i=8 n=100
    10:构造块 i=9 n=101
    11:init i=10 n=102
    
    展开全文
  • 实例化顺序:包括 1.父类静态数据,构造函数,字段;2.子类静态数据,构造函数,字段等, 当我们new一个对象的时候,实例化的顺序是怎么样的呢? OK.还是上代码比较实在(我就是个实在的人~~ 哈哈) 我们...

    类的实例化顺序:包括 1.父类静态数据,构造函数,字段;2.子类静态数据,构造函数,字段等, 当我们new一个对象的时候,类实例化的顺序是怎么样的呢?

    OK.还是上代码比较实在(我就是个实在的人~~ 哈哈)

    我们先新建一个父类,里面包括静态数据,构造函数,字段,方法等...

    /**
     * @author Lee
     * @// TODO 2018/7/18-13:13
     * @description
     */
    public class FatherClazz {
        int one = 1;
    
        int two = getTwo();
    
        // 静态代码块
        static {
            System.out.println("父类静态代码块被实例化了...");
        }
    
    
        {
            int three = 3;
            System.out.println("FatherOne:" + one + "," + "FatherTwo:" + two + "," + "FatherThree" + three);
        }
    
        // 构造函数
        public FatherClazz() {
            this(4);
            System.out.println("父类无参构造函数...");
        }
    
        public FatherClazz(int num) {
            System.out.println("父类带参数的构造函数..." + num);
        }
    
        int getTwo() {
            System.out.println("父类getTwo方法...");
            return 2;
        }
    
        public void methodFirst() {
            System.out.println("Hi,我是methodFirst...");
        }
    
    }
    

    新建一个ChildClazz继承FatherClazz~

    /**
     * @author Lee
     * @// TODO 2018/7/18-13:24
     * @description
     */
    public class ChildClazz extends FatherClazz {
        int childOne = 11;
        int childTwo = getChildTwo();
    
        {
            int childThree = 33;
            System.out.println("childOne:" +childOne +"," + "childTwo: " +childTwo + "," + "childThree:" +childThree);
        }
    
        public ChildClazz(){
            this(88);
            System.out.println("childClazz's construct function!");
        }
    
        public ChildClazz(int num) {
            System.out.println("childClazz's construct function with variable : " + num);
        }
    
        {
            System.out.println("childClazz is starting...");
        }
    
        public int getChildTwo() {
            System.out.println("Hi, I'm childClazz's getTwo method ...");
            return 22;
        }
    
        static {
            System.out.println("childClazz static code is running ...");
        }
    
        @Override
        public void methodFirst() {
            System.out.println("method is childClazz");
            super.methodFirst();
        }
    }
    
    

    好了,还剩一步,来写一个main方法测试下

    /**
     * @author Lee
     * @// TODO 2018/7/18-13:33
     * @description 测试类的实例化顺序
     */
    public class NewClazz {
        public static void main(String[] args) {
            System.out.println("main app is running ");
            ChildClazz childClazz = new ChildClazz();
            childClazz.methodFirst();
        }
    }
    
    

    走你~~ (由于截图不大美观,我这就复制控制台的输出信息了···)

    main app is running 
    父类静态代码块被实例化了...
    childClazz static code is running ...
    父类getTwo方法...
    FatherOne:1,FatherTwo:2,FatherThree3
    父类带参数的构造函数...4
    父类无参构造函数...
    Hi, I'm childClazz's getTwo method ...
    childOne:11,childTwo: 22,childThree:33
    childClazz is starting...
    childClazz's construct function with variable : 88
    childClazz's construct function!
    method is childClazz
    Hi,我是methodFirst...
    
    

    OK,来分析下程序输出的结果: 1,首先会执行类中static代码块(不管代码块是否在类的开头还是末尾处),如果这个类有父类,同样会优先查找父类中的static代码块,然后执行当前类的static。

    2,然后从父类的第一行开始执行,直至代码末尾处,中间不管是有赋值还是method调用,都会按顺序一一执行(method),普通代码块{ }...

    3,其次是父类的构造函数,执行带参数或不带参数的构造函数,依赖于实例化的类的构造函数有没有super父类的带参或不带参的构造函数(可以把上述ChildClazz构造方法中的this(88)替换成super(88)来测试)。

    4,然后会从子类(当前类)的第一行开始执行,直至代码末尾处,中间不管是有赋值还是method调用,都会按顺序一一执行(method),普通代码块{ }...

    5,接着会是子类(当前类)的构造函数,按顺序执行。

    6,最后是类方法的调用执行,如果子类覆盖了父类的method,执行时会先执行子类覆盖的method,method内如果有super.method(),才会调用父类的同名method,否则不会。

    别人总结的:先静态、先父后子。 先静态:父静态 > 子静态 。优先级:父类 > 子类 , 静态代码块 > 非静态代码块 > 构造函数。

    转载于:https://my.oschina.net/liululee/blog/1858733

    展开全文
  • 实例化顺序你还不知道吗?
  • 本文主要向大家介绍了JAVA语言中实例化过程变量的初始化顺序讲解,附常见笔试程序阅读分析,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。是在任何static成员被访问时加载的(构造器也是...
  • 这其实是去年校招时我遇到的一道阿里巴巴的笔试题(承认有点久远了-。-),嗯,如果我没记错的话,当时是作为Java方向的一道选做大题。当然题意没有这么直白,题目只要求你写出程序运行后所有System.out.println的输出...
  • Java基础常见笔试题总结

    万次阅读 多人点赞 2018-01-30 21:32:31
    以下是自己总结的一些Java常见的基础知识,答案仅供参考,如有异议请指出。一直保持更新状态。 1.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”? Java虚拟机是一个可以执行Java字节码的虚拟机...
  • 就是父子的初始化顺序,比如new一个子类的实例对象,我只知道先执行父类的静态代码和构造函数,在执行子类的静态代码和构造函数。至于,子类的静态代码先执行还是父类的构造函数先执行自己就搞不清楚了,至于成员...
  • 我这里只讨论我们在笔试题中比较关心的、影响程序输出的部分。加载:在准备阶段,static变量在方法区被分配内存,然后内存被初始零值(注意和static变量初始的区别)。在初始阶段,执行构造器&lt;...
  • java笔试题整理

    2021-03-11 15:26:25
    exit()是system的方法,如system.exit();如果某个方法是静态的,它的行为就不具有多态性。后面没有括号,方法必须要有返回值。如果没有返回值,要写void构造函数不具有多态性普通的方法是可以和同名的,和...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,962
精华内容 2,784
关键字:

java类的实例化顺序笔试题

java 订阅