1 类加载顺序

  1. 以下代码执行后输出结果为(C
  2. public class Test
  3. {
  4. public static Test t1 = new Test();
  5. {
  6. System.out.println("blockA");
  7. }
  8. static
  9. {
  10. System.out.println("blockB");
  11. }
  12. public static void main(String[] args)
  13. {
  14. Test t2 = new Test();
  15. }
  16. }
  17. A blockAblockBblockA
  18. B blockAblockAblockB
  19. C blockBblockBblockA
  20. D blockBblockAblockB

类的加载顺序
(1) 父类静态对象和静态代码块
(2) 子类静态对象和静态代码块
(3) 父类非静态对象和非静态代码块
(4) 父类构造函数
(5) 子类 非静态对象和非静态代码块
(6) 子类构造函数

2 泛型性能

  1. 在开发中使用泛型取代非泛型的数据类型(比如用ArrayList<String>取代ArrayList),程序的运行时性能会变得更好。(错误)
  2. 正确
  3. 错误

使用泛型的好处
1,类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
2,消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
3,潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。
泛型仅仅是java的语法糖,它不会影响java虚拟机生成的汇编代码,在编译阶段,虚拟机就会把泛型的类型擦除,还原成没有泛型的代码,顶多编译速度稍微慢一些,执行速度是完全没有什么区别的。

3 子类方法重写方法执行顺序

  1. Test.main()函数执行后的输出是(B
  2. class Test {
  3. public static void main(String[] args) {
  4. System.out.println(new B().getValue());
  5. }
  6. static class A {
  7. protected int value;
  8. public A (int v) {
  9. setValue(v);
  10. }
  11. public void setValue(int value) {
  12. this.value= value;
  13. }
  14. public int getValue() {
  15. try {
  16. value ++;
  17. return value;
  18. } finally {
  19. this.setValue(value);
  20. System.out.println(value);
  21. }
  22. }
  23. }
  24. static class B extends A {
  25. public B () {
  26. super(5);
  27. setValue(getValue()- 3);
  28. }
  29. public void setValue(int value) {
  30. super.setValue(2 * value);
  31. }
  32. }
  33. }
  34. 6 7 7
  35. 22 34 17
  36. 22 74 74
  37. 11 17 34

解析
本题较为复杂,具体思路如下:
第一个数值
1. new B()构造B类实例对象,进入B类的构造方法,B类构造方法的第一行代码用super(5)调用了父类带有参数的构造函数,父类的构造函数又调用了setValue()方法,但值得注意的是,子类中的方法覆盖父类的方法以后,由于向上转型,父类调用的方法实际上是子类的。那么这里的setValue(v);调用了B类的setValue()方法,而B类中setValue()方法又使用super关键字调用了父类的setValue()方法,将B实例的value值设置为2 x 5 = 10。那么到这里,B类的构造函数中第一行代码super(5)执行完毕,程序继续向下执行进入setValue(getValue()- 3);代码块。
2. 这里先执行getValue()方法,但因为B类中并没有重写该方法,这里需要调用父类的getValue()方法。进入A类getValue()方法,首先是value++,那么此时B的成员变量value值由 10变为11,程序继续向下执行,将11作为返回值,但此处要注意的一点是,在Try catch finally体系当中,在return之前始终会执行finally里面的代码,如果finally里面有return,则数据跟随finally改变。如果没有return,则原数据不跟随finally里改变的数据改变。那么进入finally代码块,由于此时正在初始化的是B类的一个对象(运行时多态),因此调用B类的setValue()方法。B类的setValue()方法中使用super关键字调用了父类的setValue()方法,将原有的value2,即11 x 2 = 22,继续向下进行System.out.println(value);输出第一个数值22。随后,A类的getValue()方法将之前暂存的value=11返回。
第二个数值
1. 拿到getValue()方法返回值之后程序继续运行,此处代码变为setValue(11- 3);根据和之前相同的流程,B类成员变量value的值变为16。程序运行到此处,new B()执行完毕。
2. 回到main函数中,实例化的B类对象调用getValue()方法,B类中并没有重写该方法,需要调用父类的getValue()方法。getValue()方法第一行代码value++将B的成员变量value值变为17,此时执行到return代码,将value=17暂存,等待finally代码块运行完毕后返回。
3. 此处finally代码块执行流程和之前相同,这里不再赘述。那么执行完this.setValue(value);后,value值变为2 x 17 = 34。继续向下进行System.out.println(value);输出第二个数值34,return刚刚暂存的value=17。
*第三个数值

回到main函数,将刚刚返回的值输出,就得到了第三个数值17。
综上所述,本题正确答案为B。

4 super的执行

  1. public class Base
  2. {
  3. public void methodOne()
  4. {
  5. System.out.print("A");
  6. methodTwo();
  7. }
  8. public void methodTwo()
  9. {
  10. System.out.print("B");
  11. }
  12. }
  13. public class Derived extends Base
  14. {
  15. public void methodOne()
  16. {
  17. super.methodOne();
  18. System.out.print("C");
  19. }
  20. public void methodTwo()
  21. {
  22. super.methodTwo();
  23. System.out.print("D");
  24. }
  25. }

知识点:
1.多态中成员方法使用规则 编译看左边,运行看右边。
2..多态中,子类重写的方法,当super调用就是调用父类方法。

5 值传递

  1. 下面代码运行结果是?(A
  2. class Value{
  3. public int i=15;//25
  4. }
  5. public class Test{
  6. public static void main(String argv[]){
  7. Test t=new Test( );
  8. t.first( );
  9. }
  10. public void first( ){
  11. int i=5;
  12. Value v=new Value( );
  13. v.i=25;
  14. second(v,i);
  15. System.out.println(v.i);
  16. }
  17. public void second(Value v,int i){//v.i=25 i=5
  18. i = 0;
  19. v.i = 20;
  20. Value val = new Value( );
  21. v = val;
  22. System.out.println(v.i+" "+i);
  23. }
  24. }
  25. 15 0 20
  26. 15 0 15
  27. 20 0 20
  28. 0 15 20

可能有人会选择B,包括我刚开始也是。总以为v不是已经指向了val了吗??为什么还是20呢?不应该是15吗?
其实,原因很简单。现在我们把second()换一下
publicvoidsecond(Value tmp,inti){
i = 0;
tmp.i = 20;
Value val = newValue( );
tmp = val;
System.out.println(tmp.i+” “+i);
}
这个tmp其实相当于是一个指向原来first中的V这个对象的指针,也就是对v对象的引用而已。但是引用是会改变所指的地址的值的。
所以在second中当tmp.i= 20的时候,就把原来first中的v的i值改为20了。接下来,又把tmp指向了新建的一个对象,所以在second中的tmp
现在指的是新的对象val,i值为15.
当执行完毕second后,在first中在此输出v.i的时候,应为前面second中已经把该位置的i的值改为了20,所以输出的是20.
至于疑惑v指向了val,其实只是名字的问题,在second中的v实践也是另外的一个变量,名字相同了而已,这个估计也是纠结的重点。

6 代码执行顺序

  1. 运行代码,输出的结果是(A
  2. public class P {
  3. public static int abc = 123;
  4. static{
  5. System.out.println("P is init");
  6. }
  7. }
  8. public class S extends P {
  9. static{
  10. System.out.println("S is init");
  11. }
  12. }
  13. public class Test {
  14. public static void main(String[] args) {
  15. System.out.println(S.abc);
  16. }
  17. }
  18. P is init<br />123
  19. S is init<br />P is init<br />123
  20. P is init<br />S is init<br />123
  21. S is init<br />123

虚拟机规范严格规定了有且只有五种情况必须立即对类进行“初始化”:
1. 使用new关键字实例化对象的时候、读取或设置一个类的静态字段的时候,已经调用一个类的静态方法的时候。
2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化,则需要先触发其初始化。
3. 当初始化一个类的时候,如果发现其父类没有被初始化就会先初始化它的父类。
4. 当虚拟机启动的时候,用户需要指定一个要执行的主类(就是包含main()方法的那个类),虚拟机会先初始化这个类;
5. 使用Jdk1.7动态语言支持的时候的一些情况。

除了这五种之外,其他的所有引用类的方式都不会触发初始化,称为被动引用。下面是被动引用的三个例子:
1. 通过子类引用父类的的静态字段,不会导致子类初始化。
2. 通过数组定义来引用类,不会触发此类的初始化。

  1. public class NotInitialization {
  2. public static void main(String[] args) {
  3. SuperClass[] sca = new SuperClass[10];
  4. }
  5. }
  1. 常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。

    1. public class ConstClass {
    2. static {
    3. System.out.println("ConstClass init!");
    4. }
    5. public static final int value = 123;
    6. }
    7. public class NotInitialization{
    8. public static void main(String[] args) {
    9. int x = ConstClass.value;
    10. }
    11. }

    上述代码运行之后,也没有输出“ConstClass init!”,这是因为虽然在Java源码中引用了ConstClass类中的常量HELLOWORLD,但其实在编译阶段通过常量传播优化,已经将此常量的值“hello world”存储到了NotInitialization类的常量池中,以后NotInitialization对常量ConstClass.HELLOWORLD的引用实际都被转化为NotInitialization类对自身常量池的引用了。也就是说,实际上NotInitialization的Class文件之中并没有ConstClass类的符号引用入口,这两个类在编译成Class之后就不存在任何联系了。

    7 深拷贝方法

    Apache、Spring的BeanUtils.copyProperties是浅拷贝;

    7.1 通过Clone

    ```java @Setter @Getter public class AddressClone implements Cloneable{

    private String address1;

    private String address2;

    public AddressClone() { }

    public AddressClone(String address1, String address2) { this.address1 = address1; this.address2 = address2; }

    @Override protected AddressClone clone() throws CloneNotSupportedException { return (AddressClone) super.clone(); } }

package com.lyj.demo.pojo.cloneTest;

import lombok.Getter; import lombok.Setter;

/**

  • @author 凌兮
  • @date 2021/4/15 14:48
  • 通过实现Clone接口实现深拷贝 */ @Setter @Getter public class UserClone implements Cloneable{ private String userName;

    private AddressClone address;

    public UserClone() { }

    public UserClone(String userName, AddressClone address) {

    1. this.userName = userName;
    2. this.address = address;

    }

    /**

    • Object父类有个clone()的拷贝方法,不过它是protected类型的,
    • 我们需要重写它并修改为public类型。除此之外,
    • 子类还需要实现Cloneable接口来告诉JVM这个类是可以拷贝的。
    • @return
    • @throws CloneNotSupportedException */ @Override protected UserClone clone() throws CloneNotSupportedException { // 需要注意的是,super.clone()其实是浅拷贝, // 所以在重写UserClone类的clone()方法时,address对象需要调用address.clone()重新赋值 UserClone userClone = (UserClone) super.clone(); userClone.setAddress(this.address.clone()); return userClone; }

      public static void main(String[] args) throws CloneNotSupportedException { AddressClone address = new AddressClone(“小区1”, “小区2”); UserClone user = new UserClone(“小李”, address); UserClone copyUser = user.clone(); user.getAddress().setAddress1(“小区3”); // false System.out.println(user == copyUser); // false System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1())); } }

      1. <a name="zF5mC"></a>
      2. ## 7.2 通过序列化方式
      3. ```java
      4. public class Test {
      5. public static void main(String[] args) {
      6. List<?> sourceList=new ArrayList<>();
      7. List<?> targetList = new ArrayList<>();
      8. //通过序列化方法实现深拷贝
      9. ByteArrayOutputStream bos=new ByteArrayOutputStream();
      10. try {
      11. ObjectOutputStream oos = new ObjectOutputStream(bos);
      12. oos.writeObject(sourceList);
      13. oos.flush();
      14. ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
      15. targetList =(List<BillDetailDto>) ois.readObject();
      16. } catch (IOException | ClassNotFoundException e) {
      17. e.printStackTrace();
      18. }
      19. }
      20. }

      7.3 通过Gson序列化方式

      ```java public class Test { public static void main(String[] args) { Gson gson = new Gson(); OrderInfo order = new OrderInfo(); String str = gson.toJson(orderInfo, OrderInfo.class); order = gson.fromJson(str, OrderInfo.class); } }

```