1 类加载顺序
以下代码执行后输出结果为(C)
public class Test
{
public static Test t1 = new Test();
{
System.out.println("blockA");
}
static
{
System.out.println("blockB");
}
public static void main(String[] args)
{
Test t2 = new Test();
}
}
A blockAblockBblockA
B blockAblockAblockB
C blockBblockBblockA
D blockBblockAblockB
类的加载顺序。
(1) 父类静态对象和静态代码块
(2) 子类静态对象和静态代码块
(3) 父类非静态对象和非静态代码块
(4) 父类构造函数
(5) 子类 非静态对象和非静态代码块
(6) 子类构造函数
2 泛型性能
在开发中使用泛型取代非泛型的数据类型(比如用ArrayList<String>取代ArrayList),程序的运行时性能会变得更好。(错误)
正确
错误
使用泛型的好处
1,类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
2,消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
3,潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。
泛型仅仅是java的语法糖,它不会影响java虚拟机生成的汇编代码,在编译阶段,虚拟机就会把泛型的类型擦除,还原成没有泛型的代码,顶多编译速度稍微慢一些,执行速度是完全没有什么区别的。
3 子类方法重写方法执行顺序
Test.main()函数执行后的输出是(B)
class Test {
public static void main(String[] args) {
System.out.println(new B().getValue());
}
static class A {
protected int value;
public A (int v) {
setValue(v);
}
public void setValue(int value) {
this.value= value;
}
public int getValue() {
try {
value ++;
return value;
} finally {
this.setValue(value);
System.out.println(value);
}
}
}
static class B extends A {
public B () {
super(5);
setValue(getValue()- 3);
}
public void setValue(int value) {
super.setValue(2 * value);
}
}
}
6 7 7
22 34 17
22 74 74
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的执行
public class Base
{
public void methodOne()
{
System.out.print("A");
methodTwo();
}
public void methodTwo()
{
System.out.print("B");
}
}
public class Derived extends Base
{
public void methodOne()
{
super.methodOne();
System.out.print("C");
}
public void methodTwo()
{
super.methodTwo();
System.out.print("D");
}
}
知识点:
1.多态中成员方法使用规则 编译看左边,运行看右边。
2..多态中,子类重写的方法,当super调用就是调用父类方法。
5 值传递
下面代码运行结果是?(A)
class Value{
public int i=15;//25
}
public class Test{
public static void main(String argv[]){
Test t=new Test( );
t.first( );
}
public void first( ){
int i=5;
Value v=new Value( );
v.i=25;
second(v,i);
System.out.println(v.i);
}
public void second(Value v,int i){//v.i=25 i=5
i = 0;
v.i = 20;
Value val = new Value( );
v = val;
System.out.println(v.i+" "+i);
}
}
15 0 20
15 0 15
20 0 20
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 代码执行顺序
运行代码,输出的结果是(A)
public class P {
public static int abc = 123;
static{
System.out.println("P is init");
}
}
public class S extends P {
static{
System.out.println("S is init");
}
}
public class Test {
public static void main(String[] args) {
System.out.println(S.abc);
}
}
P is init<br />123
S is init<br />P is init<br />123
P is init<br />S is init<br />123
S is init<br />123
虚拟机规范严格规定了有且只有五种情况必须立即对类进行“初始化”:
1. 使用new关键字实例化对象的时候、读取或设置一个类的静态字段的时候,已经调用一个类的静态方法的时候。
2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化,则需要先触发其初始化。
3. 当初始化一个类的时候,如果发现其父类没有被初始化就会先初始化它的父类。
4. 当虚拟机启动的时候,用户需要指定一个要执行的主类(就是包含main()方法的那个类),虚拟机会先初始化这个类;
5. 使用Jdk1.7动态语言支持的时候的一些情况。
除了这五种之外,其他的所有引用类的方式都不会触发初始化,称为被动引用。下面是被动引用的三个例子:
1. 通过子类引用父类的的静态字段,不会导致子类初始化。
2. 通过数组定义来引用类,不会触发此类的初始化。
public class NotInitialization {
public static void main(String[] args) {
SuperClass[] sca = new SuperClass[10];
}
}
常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
public class ConstClass {
static {
System.out.println("ConstClass init!");
}
public static final int value = 123;
}
public class NotInitialization{
public static void main(String[] args) {
int x = ConstClass.value;
}
}
上述代码运行之后,也没有输出“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) {
this.userName = userName;
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())); } }
<a name="zF5mC"></a>
## 7.2 通过序列化方式
```java
public class Test {
public static void main(String[] args) {
List<?> sourceList=new ArrayList<>();
List<?> targetList = new ArrayList<>();
//通过序列化方法实现深拷贝
ByteArrayOutputStream bos=new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(sourceList);
oos.flush();
ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
targetList =(List<BillDetailDto>) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
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); } }
```