Java面试突击(一)

164 阅读4分钟

001 自增运算

输出以下代码的结果:_____

public static void main(String[] args) {
  int i = 1;
  i = i++;      // 1
  int j = i++;  // 2
  int k = i + (++i) * (i++);    // 3
  System.out.println("i=" + i);
  System.out.println("j=" + j); 
  System.out.println("k=" + k); 
}

答案i=4;j=1;k=11

解释:先明确两个Java虚拟机栈中的概念【操作数栈、局部变量表】

提到Java虚拟机栈又不得不说栈帧,它是Java虚拟机栈中用来存储数据和部分过程结果的数据结构,每一个栈帧都有自己的局部变量表(Local Variables)、操作数栈(OperandStack)

i++:是先把i压入操作数栈中,然后i自增
++ii先自增,然后压入操作数栈

对于1处

  • ①先把i存入操作数栈中,此时操作数栈中i的值为1
  • ②然后把操作数栈中i的值赋值给左边的i

对于2处

  • ①先把i的值压入操作数栈
  • i变量自增1
  • ③把操作数栈中i的值1赋值给j

因为之前已经自增两次了,但第一次被赋值了1,所以此时i的值为2

对于3处

  • =运算符的右边将变量从左往右依次压栈,i先压栈,因为是++i,所以先自增i,然后把i(i==3)压入栈中,随后又把i(i==3)压入栈中,i自增;
  • 最后操作数弹出栈,先把栈顶操作数弹出栈,先把第一个i(i==3)弹出,随后又把i(i==3)弹出,之后两数相乘,得到结果9,因为=右边的运算还没有结束,故9被压入栈中;
  • 然后继续将临时结果9与栈中最后的i(i==2)弹出栈相加,把结果11赋值给k
3  (模拟栈)
3
2

总结

  • =右边的操作数从左到右加载值,依次压入操作数栈
  • 自增、自减操作都是直接修改变量的值,不经过操作数栈
    • 比如:i=i+1,就要先将i压栈,然后将1压入栈中,最后取出两个运算数相加后,赋值给i
  • 在赋值之前,临时结果是被存储在操作数栈中的

002 判断输出结果

一、考点

  • 类初始化过程
  • 实例初始化过程
  • 方法的重写

二、题目

public class Father{
  private int i = test();
  private static int j = method();
	
  static{ System.out.print("(1)"); }
  Father(){ System.out.print("(2)"); }
  
  {  System.out.print("(3)"); }

  public int test(){
    System.out.print("(4)");
    return 1;
  }
  public static int method(){
    System.out.print("(5)");
    return 1;
  }
}
public class Son extends Father{
  private int i = test();
  private static int j = method();
  
  static{  System.out.print("(6)");  }
  
  Son() {  // super(); System.out.print("(7)"); }
  
  {  System.out.print("(8)"); }
  
  public int test(){
    System.out.print("(9)");
    return 1;
  }
  public static int method(){
    System.out.print("(10)");
    return 1;
  }
  public static void main(String[] args) {
    Son s1 = new Son();
    System.out.println();
    Son s2 = new Son();
  }
}

类初始化过程

① 一个类要创建实例需要先加载并初始化该类

  • 先加载和初始化main方法所在的类

② 先初始化父类,然后初始化子类

③ 一个类的初始化就是执行<clinit>()方法

  • <clinit>()方法由静态类变量显示赋值代码和静态代码块组成
  • 类变量显式赋值代码和静态代码块代码从上到下顺序执行
  • <clinit>()方法只执行一次

当执行new Son()时,首先加载并初始化该类的父类father,也会执行父类的<clinit>()方法,而该方法由静态类变量赋值代码与静态代码块组成,所以也就是执行

  • private static int j = method();,输出(5)
  • static{ System.out.print("(1)"); },输出(1)

随后执行子类的<clinit>()方法

  • private static int j = method();,输出(10)
  • static { System.out.print("(6)"); },输出(6)

顺序:(5)(1)(10)(6)

实例初始化

(1)先执行父类的<init>()方法

实例初始化实际上是执行<init>()方法,该方法由非静态实例变量显示赋值代码和非静态代码块、对应构造器代码组成。构造方法最后被执行.

需要注意的是:每一个子类的<init>()方法执行之前先要执行父类的<init>()方法

① 当执行new Son()时,先执行父类的private int i = test();

② 然后顺序执行{ System.out.print("(3)"); }

③ 最后执行构造方法

Father() { System.out.print("(2)"); }

得到的顺序:(4)(3)(2)

问题一: 这里的输出顺序还不对,因为还有text()方法的重写问题

  • 子类如果重写了父类的方法(text()),通过子类对象调用的一定是子类重写过的代码,所以这里输出的是(9),而不是(4)

顺序:(9)(3)(2)

问题二:method()方法也是被重写了的啊,在类初始化之前为什么没有执行子类的method()两次?

  • 因为在面向对象机制中就设定了静态方法不会被重写

(2)后执行子类的<init>()方法

private int i = test();,输出(9)

{ System.out.print("(8)"); }

Son(){ System.out.print("(7)"); }

顺序:(9)(8)(7)

最后的答案

(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)