最近在准备面试,整理一些Java基础知识,不整理不知道,一整理吓一跳。深深的理解到了基础不牢,地动山摇。
言归正传,开干
面试官:
Java创建对象有哪几种方式?
我:
答:面试官,你好,整体来说可以分为四种,细分可以分为5种,分别为:
- 手动new 通过构造函数创建
- 实现超类Object的Cloneable方法
- 通过反射获取类模板对象,再通过Class对象的newInstance()方法创建
- 通过反射获取类模板对象,再Constructor对象创建
- 反序列化的方式创建
面试官:
深拷贝和浅拷贝的区是什么?
我:
深拷贝:
在内存中新开辟一片相同大小的内存空间用于创建对象,创建的对象拷贝的是原对象的所有值,新创建一个对象和原对象除了值相同其他没有任何关联,相互独立。
浅拷贝:
在内存中新开辟一片相同大小的内存空间用于创建对象,在内存中创建的对象对于基本类型的值每个对象各自保存在其对象内存空间中,对象的引用类型属性拷贝的是地址值,原对象的引用类型属性发生改变,拷贝之后的对象也会发生改变。
面试官:
如果原对象的String类型数据发生改变,原类型数据会变化吗?
我:
由于String是由final修饰的类,表明其是不可变类,所以其数据并不会发生改变,改变的只是引用变量指向的地址值,当引用变量地址值发生改变时其做的任何改变都不会影响原对象的改变,两者是独立的对象存在此时。
面试官:
你之前不是说浅拷贝改变原对象引用值,clone对象也会发生改变吗,为什么String类型没有变化?
我:
Tips:首先需要明确一个概念,Java是值传递类型的,对于引用类型也是通过值传递,只不过传递的是引用对象的地址值,只要地址值不改变,改变引用对象的属性值在原对象都会发生改变。
答:String类型本身是引用类型,同时也被定义为final类型的,表明其不可变性,故当原对象Person1改变其name属性值时,已经指向了常量池中的另一个地址值。而对于Person1中的name属性还指向原地址值。
String 不改变值是浅copy。但给name赋值时(set方法等),常量值的地址是固定不变的,字符串对象只能改变自己的引用了,原来对象的引用不会变,效果上这是相当于实现了深copy。
面试官:
能具体说下,new创建对象和clone创建对象的区别吗?
我:
- new会调用构造方法,通过构造函数,填充对象的各个域。
clone并不会调用构造方法,依赖于原对象,在内存分配一片和原对象相同大小的内存空间,然后再使 - 用原对象中对应的各个域,填充新对象的域;
面试官:
clone属于浅拷贝还是深拷贝?如何验证?
我:
clone本身属于浅拷贝,草稿纸拿来,验证如下
场景:Person类种有三个属性,分别为引用类型String name和Address address和基本类型int age。
验证:
- 观察clone对象Person1和原对象地址值是否相同,如果地址值相同,则进行的是引用拷贝而不是对象拷贝。
- 观察原对象Person的引用类型数据Address发生改变之后,clone对象中的引用类型地址值是否相同,如果相同则为浅拷贝。
package com.gan.cloneDemo;
/**
* @Author lyh
* @CreateTime 2020/7/26 - 7:30
* @Desperation Person 对象
* @Version 1.0
*/
public class Person implements Cloneable{
private String name;
private int age;
private Address address;
// 必须重写父类clone接口,否则无法创建clone对象
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address=" + address +
'}';
}
}
package com.gan.cloneDemo;
/**
* @Author lyh
* @CreateTime 2020/7/26 - 7:31
* @Desperation Address 对象
* @Version 1.0
*/
public class Address {
private String province;
public Address(String province) {
this.province = province;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
@Override
public String toString() {
return "Address{" +
"province='" + province + '\'' +
'}';
}
}
package com.gan.cloneDemo;
/**
* @Author lyh
* @CreateTime 2020/7/26 - 7:31
* @Desperation DemoTest 对象
* @Version 1.0
*/
public class DemoTest {
public static void main(String[] args) {
Address address = new Address("AnHui");
Person person = new Person("Gan3",23,address);
Person person1 = null;
try {
person1 = (Person) person.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(person == person1);//false
System.out.println("=======================================================");
System.out.println(person);
System.out.println(person1);
System.out.println("=======================================================");
System.out.println(person.getName() == person1.getName());//true
System.out.println(person.getAddress() == person1.getAddress());//true
System.out.println("=======================================================");
address.setProvince("HangZhou");
person.setName("Gan4");
person.setAge(24);
person.setAddress(address);
System.out.println(person);
System.out.println(person1);
System.out.println("=======================================================");
System.out.println(person.getName() == person1.getName());//false
System.out.println(person.getAddress() == person1.getAddress());//true
System.out.println("=======================================================");
Address test = new Address("Test");
person.setAddress(test);
System.out.println(person.getAddress() == person1.getAddress());//false
}
}
结果展示
false
=======================================================
Person{name='Gan3', age=23, address=Address{province='AnHui'}}
Person{name='Gan3', age=23, address=Address{province='AnHui'}}
=======================================================
true
true
=======================================================
Person{name='Gan4', age=24, address=Address{province='HangZhou'}}
Person{name='Gan3', age=23, address=Address{province='HangZhou'}}
=======================================================
false
true
=======================================================
false
Process finished with exit code 0
综上两点所述,clone是浅拷贝
面试官:
为什么实现Object的实现cloneable方法还需要重写clone方法?
我:
由Object的源码得知,如果不重写clone方法就会抛出异常
/**
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
面试官:
如何将clone的浅拷贝转换为深拷贝
我:
有两种方式(以下demo列出了第一种)
1.只需要在实现clone方法的时候通过指定引用属性进行引用赋值即可
2.通过反序列化(深拷贝的一种)
Tips:将对象转成二进制流,然后再把二进制流反序列成一个java对象,这时候反序列化生成的对象是一个全新的对象,里面的信息与原对象一样,但是所有内容都是一份新的。
这种方式需要注意的地方主要是所有类都需要实现Serializable接口,以便进行序列化操作。
@Override
protected Object clone() throws CloneNotSupportedException {
Person person = (Person)super.clone();
//手动对address属性进行clone,并赋值给新的person对象
person.address = (Address) address.clone();
return person;
}
面试官:
你们项目中实际开发改写clone方法实现深拷贝多不多,为什么?
我:
并不多,因为当想要实现深拷贝时如果Person类有很多引用属性值又或者引用属性值值又有引用属性值,此时如果重写将会过于麻烦。
面试官:
数组是深拷贝还是浅拷贝?
我:
深拷贝
package com.gan.cloneDemo;
/**
* @Author lyh
* @CreateTime 2020/7/26 - 9:31
* @Desperation ArrayDemo 对象
* @Version 1.0
*/
public class ArrayDemo {
public static void main(String[] args) {
int[] ints = {1,2,3};
int[] ints1 = ints.clone();
ints1[0] = 0;
System.out.println(ints[0]); //1
System.out.println(ints1[0]);//0
}
}
面试官:
我们来谈一下,通过反射获取类模板对象,再通过Class对象的newInstance()方法创建
和通过反射获取类模板对象,再通过Constructor对象创建对象的区别吧
我:
主要有三点区别:
- Class.forName(String name).newInstance()方法是Java.lang,Class包下,new Constructor().newInstance()方法是java.lang.reflect.Constructor包下
- 第一种要求该类必须有无参构造器否则就会报错,因为调用的是无参构造器
- 如果希望创建有参构造器,只能使用Constructor创建
面试官:
关于反射你是怎么理解的?String的不可变性你前面说到过,这有几道题目你做下
我:
内心OS:说好一点点,淦!!!
答:要不下次再聊吧,植发医师喊我去植发了