网站规划的认识,东莞网站设,部门将网站建设的需求,园区智慧管理平台文章目录 一、赋值顺序#xff08;1#xff09;赋值的位置及顺序#xff08;2#xff09;举例#xff08;3#xff09;字节码文件#xff08;4#xff09;进一步探索#xff08;5#xff09;最终赋值顺序#xff08;6#xff09;实际开发如何选 二、(超纲)关于字节… 文章目录 一、赋值顺序1赋值的位置及顺序2举例3字节码文件4进一步探索5最终赋值顺序6实际开发如何选 二、(超纲)关于字节码文件中的init三、面试题1面试题12面试题23面试题34面试题4 一、赋值顺序
1赋值的位置及顺序
可以给类的非静态的属性即实例变量赋值的位置有
① 默认初始化
② 显式初始化
⑤ 代码块中初始化
③ 构造器中初始化
#############################
④ 有了对象以后通过对象.属性或对象.方法的方法进行赋值
造对象之前叫初始化造对象之后叫赋值
执行的先后顺序
① - ② - ③ - ④
⑤ 代码块中初始化应该放在哪 2举例
【举例】
先看一段代码
package yuyi06;/*** ClassName: FieldTest* Package: yuyi06* Description:** Author 雨翼轻尘* Create 2023/11/19 0019 16:25*/
public class FieldTest {public static void main(String[] args) {Order o1new Order();System.out.println(o1.orderId); //1}
}class Order{int orderId1;}输出结果 这个很简单显而易见。
现在整一个代码块看一下它和显式赋值谁先谁后。
public class FieldTest {public static void main(String[] args) {Order o1new Order();System.out.println(o1.orderId); //2}
}class Order{int orderId1;{orderId2;}}输出结果 那么一定是先有1后有2。所以代码块初始化肯定是在显示初始化之后。 接下来是构造器和代码块。
创建一个空参构造器那么在创建对象的时候一定会调用它。
ublic class FieldTest {public static void main(String[] args) {Order o1new Order();System.out.println(o1.orderId); //3}
}class Order{int orderId1;{orderId2;}public Order(){orderId3;}}输出结果 结果是3所以3将2覆盖了。所以代码块初始化在构造器初始化前面。
所以目前来看执行顺序是这样的① - ② - ⑤ - ③ - ④
3字节码文件
将光标放在Order类中看一下字节码文件。
插件在这里 先运行然后重新编译一下确保生成的字节码文件和代码一致。 然后点击这个即可 构造器会以init方法的方式呈现在字节码文件中如下 看一下代码 方法里面对应的是个栈帧栈帧里面会放局部变量
aload_0 就是指局部变量第0个位置–this表示当前正在创建的对象通过aload_0调用现在的方法。
如下 画个图解释一下Code的意思 4进一步探索
根据上面得出来的结论代码块赋值在显式赋值之后那么将它们俩的代码换个位置呢
如下
public class FieldTest {public static void main(String[] args) {Order o1new Order();System.out.println(o1.orderId); }
}class Order{{orderId2;}int orderId1;public Order(){//orderId3;}}输出结果 怎么是1了呢肯定是先有2后有1。
看字节码文件 这样来看代码块赋值又先于了显示赋值。
刚才的① - ② - ⑤ - ③ - ④ 明显不太对。
②和⑤就是看谁先声明谁就先执行。
所以应该是这样的① - ②/⑤ - ③ - ④ 为啥将代码块写在显示赋值上面不会报错这时候变量还未声明啊
其实这个地方一直有个误区举个例子 可以看到在eat()方法中可以调用sleep()方法。
若是按照刚才的说法先有eat()后有sleep()怎么一上来就可以sleep()此时sleep()还没有声明啊但是怎么没有报错
我们只需要考虑编译的时候会看到eat()里面调用了sleep()方法这个方法找一下有没有发现有那能确保调用sleep()的时候内存中有吗
其实在加载类的时候将类放入了方法区其实sleep()也好eat()也好方法都加载了的。
所以只需要确保调用这个方法之前这个方法加载了就行。
回到这里
//代码块赋值
{orderId2;
}//显示赋值
int orderId1;现在这种情况也可以用类似的方式去解释以后再说类加载的详细过程现在就说最核心的点。
orderId在整个类的加载中有一个过程在其中某一个环节就已经将orderId给加载了而且还给了一个默认赋值0这个时候orderId属性就已经有了。在后续的环节中才开始做显示赋值和代码块的赋值。
现在是先有代码块的赋值那么就将orderId改为2后面又显示赋值将它改为1。 一般习惯将代码块写显示赋值的下面 5最终赋值顺序
可以给类的非静态的属性即实例变量赋值的位置有
① 默认初始化
② 显式初始化 或 ⑤ 代码块中初始化
③ 构造器中初始化
#############################
④ 有了对象以后通过对象.属性或对象.方法的方法进行赋值
造对象之前叫初始化造对象之后叫赋值
执行的先后顺序
① - ②/⑤ - ③ - ④
6实际开发如何选 给实例变量赋值的位置很多开发中如何选
显示赋值比较适合于每个对象的属性值相同的场景。构造器中赋值比较适合于每个对象的属性值不相同的场景通过形参的方式给它赋值。非静态代码块用的比较少在构造器里面基本能完成。静态代码块静态与类相关属性不会选择在构造器与对象相关中赋值。静态的变量要么默认赋值要么显示赋值要么代码块中赋值。
二、(超纲)关于字节码文件中的
(超纲)关于字节码文件中的的简单说明(通过插件jclasslib bytecode viewer查看)
刚才查看字节码文件的时候可以看到这里做个简单说明便于大家理解。
说明
①init方法在字节码文件中可以看到。每个方法都对应着一个类的构造器。类中声明了几个构造器就会有几个
既然构造器和一 一对应在字节码文件中也看不到“构造器”这一项。 所以构造器就是以方法的形式呈现在字节码文件中的。
比如这里声明了俩构造器
class Order{{orderId2;}int orderId1;public Order(){//orderId3;}public Order(int orderId){this.orderIdorderId;}public void eat(){sleep();}public void sleep(){}}看一下字节码文件有两个如下 点开第二个一起来看一下它的Code 角标为1的值 所以通过第二个有参构造器去造对象的时候也会有显示赋值和代码块的执行然后才是构造器。对应字节码文件中就是方法。
②编写的代码中的构造器在编译以后就会以init方法的方式呈现。方法和构造器不是一回事
③init方法内部的代码包含了实例变量的显示赋值、代码块中的赋值和构造器中的代码。
④init方法用来初始化当前创建的对象的信息的。 构造器和方法不是一回事字节码文件中没有“构造器”是以方法的形式呈现的。 三、面试题
1面试题1
下面代码输出结果是
package yuyi06;//技巧由父及子静态先行。class Root{//静态代码块static{System.out.println(Root的静态初始化块);}//非静态代码块{System.out.println(Root的普通初始化块);}//构造器public Root(){super();System.out.println(Root的无参数的构造器);}
}class Mid extends Root{static{System.out.println(Mid的静态初始化块);}{System.out.println(Mid的普通初始化块);}public Mid(){System.out.println(Mid的无参数的构造器);}public Mid(String msg){//通过this调用同一类中重载的构造器this();System.out.println(Mid的带参数构造器其参数值 msg);}
}class Leaf extends Mid{static{System.out.println(Leaf的静态初始化块);}{System.out.println(Leaf的普通初始化块);}public Leaf(){//通过super调用父类中有一个字符串参数的构造器super(雨翼轻尘);System.out.println(Leaf的构造器);}
}public class LeafTest{public static void main(String[] args){new Leaf(); //涉及到当前类以及它的父类、父类的父类的加载包括相应功能的执行// System.out.println();// new Leaf();}
}分析
new Leaf(); 涉及到当前类以及它的父类、父类的父类的加载包括相应功能的执行。
分析先后执行的顺序。
上面的类中分别都有静态代码块、非静态代码块和构造器。
首先应该是静态代码块进行类加载的时候一定先加载父类的然后才是子类。
之前说的方法的重写一定是先有父类的方法才能覆盖它。先加载父类
当我们通过leaf()造对象首先会通过super()找到父类。没有写也是super
画个图看一下逻辑 所以最先加载的类是Object只不过改不了代码也没有输出语句
所以看似好像没加载其实是先加载它其次是Root类然后就是Root类里面的static代码块下面的非静态代码块和无参构造器就别先执行了因为要先将类的加载都执行了。
如下 所以看一下执行结果前面三行是“静态初始化块” 类的加载就完成了。
下面才涉及造对象。
静态加载之后先去new了一个leaf()然后执行super()一直到最上层的Root类先考虑它的构造器的加载涉及到非静态结构的加载然后才是子类代码块的执行又早于构造器所以会先输出代码块中的内容。
刚才说到调用的过程如下 输出的话就是反过来 运行结果如下 技巧由父及子静态先行。先加载父类后加载子类静态结构早于非静态init方法的非静态代码块的执行又早于构造器的执行 方法包括代码块的每个构造器都默认调用父类的构造器。
方法不是通过对象.去调用的而是自动执行的。
2面试题2
下面代码输出结果是
class HelloA {public HelloA() {System.out.println(HelloA);}{System.out.println(Im A Class);}static {System.out.println(static A);}
}class HelloB extends HelloA {public HelloB() {System.out.println(HelloB);}{System.out.println(Im B Class);}static {System.out.println(static B);}}public class Test01 {public static void main(String[] args) {new HelloB();}
}分析
画个图演示一下
执行输出顺序①-②-③-④-⑤-⑥
先将类的加载搞定。
HelloA中有静态先调用静态输出“static A”
然后回到HelloA中调用静态输出“static B”。
然后考虑当前要创建的对象的构造器HelloB()此时第一行会调用super()
调用HelloA()构造器。
再HelloA()构造器中有非静态代码块先执行它输出“I’m A Class”
然后输出构造器中“HelloA”。
super()执行结束之后回到HelloB()此时HelloB类中也有非静态代码块
所以先输出代码块中“I’m B Class”最后输出HelloB()构造器中“HelloB”。
代码运行结果 3面试题3
下面代码输出结果是
public class Test02 {static int x, y, z;static {int x 5;x--;}static {x--;}public static void method() {y z z;}public static void main(String[] args) {System.out.println(x x);z--;method();System.out.println(result: (z y z));}
}分析
画个图执行顺序①-②-③-④-⑤-⑥ 输出结果 4面试题4
下面代码输出结果是
public class Test03 {public static void main(String[] args) {Sub s new Sub();}
}
class Base{Base(){method(100);}{System.out.println(base);}public void method(int i){System.out.println(base : i);}
}
class Sub extends Base{Sub(){super.method(70);}{System.out.println(sub);}public void method(int j){System.out.println(sub : j);}
}分析
画个图执行顺序①-②-③-④-⑤-⑥-⑦-⑧ 调试
大家也可以自行调试这里就做个示范。 输出结果