前言
以下是自己学习的记录和一些尝试,如果存在不对的地方,请指出。多谢!
强引用
正常引用即为强引用。eg:
Object o = new Object();
软引用
使用SoftReference获取的对象即为软引用对象。eg:
SoftReference<Object> sr = new SoftReference<>(new Object());
通过sr实现对对象new Object()的软引用。
弱引用
使用WeakReference获取的对象即为弱引用对象。eg:
WeakReference<Object> wr = new WeakReference<>(new Object());
通过wr实现对对象new Object()的弱引用。
虚引用
使用PhantomReference获取的对象即为虚引用对象。eg:
PhantomReference<Object> pr = new PhantomReference<>(new Object(), new ReferenceQueue<>());
通过pr实现对对象new Object()的虚引用。
PS:1. 该引用对象不可访问;2. 需要指明new ReferenceQueue<>()来作为回收使用的队列容器。
主要区别
除了申明以外,主要区别在于对象的引用方式、内存定位和回收机制。
引用
首先,说一下引用,先创建一个简单的类Person,有两个属性name, age。
package com.test.customize;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
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;
}
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
然后开始调用创建对象(代码是另一台电脑写的,没有配置中文,见谅!)
package com.test.application;
import com.test.customize.Person;
public class References {
public static void main(String[] args) {
//Create a new object and add three references
Person p1 = new Person("a", 1);
Person p2 = p1;
Person p3 = p2;
System.out.println("p1 = " + p1 + "; " + "p2 = " + p2 + "; " + "p3 = " + p3 + "; " + "(p1 == p3) = " + (p1 == p3) + "; " + "(p1.equals(p3) = " + (p1.equals(p3)));
//Set the middle reference to be null
p2 = null;
System.out.println("p1 = " + p1 + "; " + "p2 = " + p2 + "; " + "p3 = " + p3 + "; " + "(p1 == p3) = " + (p1 == p3) + "; " + "(p1.equals(p3) = " + (p1.equals(p3)));
//Use the last reference to set a new value for the age in the object
p3.setAge(5);
System.out.println("p1 = " + p1 + "; " + "p2 = " + p2 + "; " + "p3 = " + p3 + "; " + "(p1 == p3) = " + (p1 == p3) + "; " + "(p1.equals(p3) = " + (p1.equals(p3)));
//Set the first reference to be null
p1 = null;
System.out.println("p1 = " + p1 + "; " + "p2 = " + p2 + "; " + "p3 = " + p3);
//Create another object with the same name and age as the created object
Person p4 = new Person("a", 5);
System.out.println("p4 = " + p4 + "; " + "(p3 == p4) = " + (p3 == p4) + "; " + "(p3.equals(p4)) = " + (p3.equals(p4)));
//Get the hashcode of two object
System.out.println("p3 hashcode = " + p3.hashCode());
System.out.println("p4 hashcode = " + p4.hashCode());
}
}
运行结果:
p1 = Person{name='a', age=1}; p2 = Person{name='a', age=1}; p3 = Person{name='a', age=1}; (p1 == p3) = true; (p1.equals(p3) = true
p1 = Person{name='a', age=1}; p2 = null; p3 = Person{name='a', age=1}; (p1 == p3) = true; (p1.equals(p3) = true
p1 = Person{name='a', age=5}; p2 = null; p3 = Person{name='a', age=5}; (p1 == p3) = true; (p1.equals(p3) = true
p1 = null; p2 = null; p3 = Person{name='a', age=5}
p4 = Person{name='a', age=5}; (p3 == p4) = false; (p3.equals(p4)) = false
p3 hashcode = 25418303
p4 hashcode = 26202540
分析一下对象的创建和引用
- 第一次创建对象
第一次创建对象的时候,创建了一个对象new Person(“a”, 1),并且建立了3个变量实现对它的引用。此时,内存中只有这一个对象。
这三个变量是完全相等的,并且也是完全一致的,比较的结果都是true。 - 变量和对象修改
1.修改3个变量中的p2,将其置为null,此时,对象 new Person(“a”, 1) 不再获得p2的引用,但是对已经存在的引用p1和p3不产生任何影响。此时,内存中还是只有这一个对象。
2.使用p3对对象 new Person(“a”, 1) 重新设置属性,则对该对象存在引用的p1打印结果同步变化。
3.p1置为null,断开对对象 new Person(“a”, 1) 的引用,对已经建立引用关系的p3不产生影响。 - 第二次创建对象
创建一个新的对象 new Person(“a”, 5),并使用变量p4来引用该对象。虽然该对象的属性值和第一个对象完全保持一直,但是该对象是一个全新的对象,与之前的对象共存于内存中。
使用这个语句 Person p4 = new Person(p3.getName(), p3.getAge()); 创建的也一样。 - 关于比较对象
正常的比较对象,实际上比较的是内存地址。每一个对象的内存地址都是唯一的,只要内存地址一致,则是同一个对象。在没有重写Object的equals()方法的前提下,“==”和“equals”是完全一直的,比较的都是内存地址。因此,最开始的p1\p2\p3是完全相等的,但是p4是单独的,与前面三个不相等。
Object的equals方法如下:
public boolean equals(Object obj) {
return (this == obj);
}
- 关于重写equals()方法
先说结论:不建议重写,尤其是对已经存在的对象进行二次编辑的时候。
对于String类,是已经重写了Object的equals()方法的,源码如下:
private final char value[];
···
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
可以看到,String也是优先采取内存地址比较的方法来判断的。当内存地址不一致时,才将对象拆分成char数组来比较每一位上的char值是否一致(此时,已经忽略掉了对象的内存地址和hashcode)。
接下来说下为什么不建议重写equals方法(String是final类,不能再次重写了,而且比较的时候使用的是char,不受内存地址和hashcode影响。)
对于已经存在的系统,如果系统中已经存在了使用类似于(p3.equals(p4))的语句,在没有重写的前提下,它的效用和(p3 == p4)是完全一样的。如果此时,重写了equals方法,比如:
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Person person = (Person) o;
if (age != person.age)
return false;
return name != null ? name.equals(person.name) : person.name == null;
}
此时,(p3.equals(p4))的效应已经跟(p3 == p4)不一样了,很有可能会导致之前的判断已经失效了。而对于一个新的系统而言,除非明确规定了或者说所有人的编程习惯都一样,否则的话,不能绝对排除有人会使用(p3.equals(p4))来代替(p3 == p4)作为判断的条件。
如果需要进行非内存地址的判断,建议自己重新定义一个比较方法,例如:
public boolean isEqual(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Person person = (Person) o;
if (age != person.age)
return false;
return name != null ? name.equals(person.name) : person.name == null;
}