1.java引用类型
java除了8大基本类型(byte、short、int、long、float、char、double、boolean)之外,其余(类、数组、接口)都为引用类型。基本类型的值是存储在栈空间中的,而引用类型我们在栈空间中存的是引用(指向堆空间的地址),其数据是存在堆空间空间中。
引用类型 引用=new 引用类型();
“==”比较的是对象内存地址(栈空间中的数据),equals比较的是对象内容(堆空间中的数据);
8大基本类型的包装类型也是引用类型,由于他们自带自动装箱和自动拆箱,所以能和基本类型相互赋值。包装类的值和String一样是由final修饰的,我们无法改变实例的值。当我们进行赋值时,时实际上是创建了新的对象,但需要注意的是除了浮点类型,其他包装类型都有缓存,在装箱时,如果该值在缓存的范围内(Character:0-127,Boolean:true,false,浮点类型:无,其它为:-128一127),就取缓存中的对象,否则就创建新的对象。
2.java创建对象
(1)通过new关键字
通过new关键字,调用类的有参或无参构造函数,来创建对象。Object ob=new Object();
(2)通过反射
- 通过Class类的newInstance(),默认调用类的无参构造方法。(内部还是调用Constructor类的newInstance())
- 通过Constructor类的newInstance(),调用指定的构造方法。
(3)通过clone的方法
通过实现Cloneable接口,重写clone()方法后,调用该对象的clone方法,就会得到一个该对象的拷贝对象
(4)通过反序列化
序列化是把堆内存中的 Java 对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点(在网络上传输)。而反序列化则是把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。
public static void creatObject() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, CloneNotSupportedException, IOException {
Dog dog1 = (Dog) Class.forName("com.demo.copyobject.Dog").newInstance();
dog1.setName("Class.newInstance()");
Dog dog2 = Dog.class.getConstructor().newInstance();
dog2.setName("Constructor.newInstance()");
Dog dog3= (Dog) dog2.clone();
dog3.setName("clone()");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(dog3);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Dog dog4 = (Dog) ois.readObject();
dog4.setName("反序列化");
Dog dog5 = new Dog("new", 0);
System.out.println("dog1:"+dog1);
System.out.println("dog2:"+dog2);
System.out.println("dog3:"+dog3);
System.out.println("dog4:"+dog4);
System.out.println("dog5:"+dog5);
}
输出
dog1:Dog{name='Class.newInstance()', age=null, foods=null, friend=null}
dog2:Dog{name='Constructor.newInstance()', age=null, foods=null, friend=null}
dog3:Dog{name='clone()', age=null, foods=null, friend=null}
dog4:Dog{name='反序列化', age=null, foods=null, friend=null}
dog5:Dog{name='new', age=0, foods=null, friend=null}
3.引用拷贝
当我们创建了一个新的对象1后,用该对象给一个新定义的对象2赋值,对象2并没开辟新的内存,而是引用了对象1,此时对象1和对象2指向的是同一个堆空间的地址,当我改变其中一个值后,另一个值也会发生变化。
总结:两个对象不是独立的,共用一个地址
Cat cat1 = new Cat("cat1", 2, new Food("fish", 100));
Cat cat2=cat1; //引用拷贝
System.out.println(cat1==cat2);
System.out.println("cat1:"+cat1);
System.out.println("cat2:"+cat2);
cat2.setAge(3);
cat2.setName("cat2");
cat2.setFood("猫粮",10);
System.out.println("cat1:"+cat1);
System.out.println("cat2:"+cat2);
输出
true
cat1:Cat{name='cat1', age=2, food=Food{name='fish', weight=100.0}}
cat2:Cat{name='cat1', age=2, food=Food{name='fish', weight=100.0}}
cat1:Cat{name='cat2', age=3, food=Food{name='猫粮', weight=10.0}}
cat2:Cat{name='cat2', age=3, food=Food{name='猫粮', weight=10.0}}
4.浅拷贝
所要拷贝的类实现了Cloneable接口,但该类的成员对象所属的类并未实现Cloneable接口,所以在进行拷贝时,对于基本类型,会开辟新的内存,拷贝并存储这些值,而对于未实现Cloneable接口的引用类型,并不会开辟新的内存,只是拷贝了引用并未拷贝其值。如果我们改变了其中一个对象里的引用类型的值,那么两个对象都会发生改变
总结:两个对象是独立的,对象里的基本类型有着各自的地址,未实现Cloneable接口的引用类型共用一个地址
cat1 = new Cat("cat1", 2,new Food("fish", 100));
cat2= (Cat) cat1.clone();//浅拷贝,因为Cat的成员引用类型Food没有实现Cloneable接口,所以为地址引用
System.out.println(cat1==cat2);
System.out.println("cat1:"+cat1);
System.out.println("cat2:"+cat2);
cat2.setAge(3);//Integer类型虽然是引用类型,但它的值是final修饰的,和String一样,不可以改变这个包装类的实例的值,我们对包装类的赋值操作实际上是创建了一个新的对象
cat2.setName("cat2");
cat2.setFood("猫粮",10);
System.out.println("cat1:"+cat1);
System.out.println("cat2:"+cat2);
输出
false
cat1:Cat{name='cat1', age=2, food=Food{name='fish', weight=100.0}}
cat2:Cat{name='cat1', age=2, food=Food{name='fish', weight=100.0}}
cat1:Cat{name='cat1', age=2, food=Food{name='猫粮', weight=10.0}}
cat2:Cat{name='cat2', age=3, food=Food{name='猫粮', weight=10.0}}
5.深拷贝
深拷贝就是将该对象完完全全拷贝新的一份,无论是其基本类型和引用类型都会开辟新的内存拷贝其值,所以拷贝出的对象和原对象有着各自的内存空间,彼此互不干扰。
总结:两个对象是独立的,对象里的所有类型都有着各自的地址
实现深拷贝有两种方法,一种是该对象和该对象里的应用类型(也包括组成该引用类型的引用类型)全部实现Cloneable接口,另一种是反序列化。序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取出来。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以我们可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象。对于不想进行序列化的变量,我们可以使用 transient 关键字修饰,但transient关键字只能修饰变量,不能修饰类和方法。
显然更推荐第二种做法,因为第一种如果该对象的组成比较复杂,包含了很多引用类型,那么我们要讲这些引用类型全部都实现Cloneable接口,显然代码量巨大,效率低下。
Dog dog=new Dog("Dog1",2);
Person person = new Person("张飒",18,180,dog);
System.out.println("Cloneable 实现深拷贝");
Person person2= (Person) person.clone();//需要拷贝的对象的成员包含引用类型的话,该类型也要实现Cloneable接口
System.out.println(person==person2);
System.out.println("person1:"+person);
System.out.println("person2:"+person2);
person2.setAge(19);
person2.setHight(190);
person2.setName("张三");
person2.setDog("Dog2",3);
System.out.println("person1:"+person);
System.out.println("person2:"+person2);
System.out.println("反序列化实现");
Food fish = new Food("fish", 100);
Cat cat1 = new Cat("cat1", 2, fish);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(cat1);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Cat cat2 = (Cat) ois.readObject();
System.out.println(cat1==cat2);
System.out.println("cat1:"+cat1);
System.out.println("cat2:"+cat2);
cat2.setAge(3);
cat2.setName("cat2");
cat2.setFood("猫粮",10);
System.out.println("cat1:"+cat1);
System.out.println("cat2:"+cat2);
输出
Cloneable 实现深拷贝
false
person1:Person{name='张飒', age=18, hight=180, dog=Dog{name='Dog1', age=2, foods=null, friend=null}}
person2:Person{name='张飒', age=18, hight=180, dog=Dog{name='Dog1', age=2, foods=null, friend=null}}
person1:Person{name='张飒', age=18, hight=180, dog=Dog{name='Dog2', age=3, foods=null, friend=null}}
person2:Person{name='张三', age=19, hight=190, dog=Dog{name='Dog2', age=3, foods=null, friend=null}}
反序列化实现
false
cat1:Cat{name='cat1', age=2, food=Food{name='fish', weight=100.0}}
cat2:Cat{name='cat1', age=2, food=Food{name='fish', weight=100.0}}
cat1:Cat{name='cat1', age=2, food=Food{name='fish', weight=100.0}}
cat2:Cat{name='cat2', age=3, food=Food{name='猫粮', weight=10.0}}