首先需要了解的是:
java中对象分为基本类型和引用类型。除了8个基本类型,其他的都可以称为引用类型。Java基本类型与引用类型
1. ==的用法
在《java核心技术卷1》中,“==”被归纳与关系运算符。其常常用于基本类型数据比较,也可以用来比较其他相同类型对象,即引用类型。
- 如果是基本类型的比较,则比较的是二者的值是否相等
- 如果是引用类型的比较,则是比较二者是不是同一引用,并不是比较二者内容是否相等
下面举例进行进一步的加深理解:
一个User类:
public class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
main方法中:
public static void main(String[] args) {
User userthree = new User("zs",1);
User userfour = new User("zs",1);
String a = "yao";
String b = "zhi";
String e = "yao";
String f = new String ("yao");
int c = 10;
int d = 10;
System.out.println(c==d);//true
System.out.println(a==e);//true
System.out.println(a==b);//false
System.out.println(a==f);//false
System.out.println(userthree==userfour);//false
}
运行结果已经注释在后面,下面进行分析:
- c和d属于基本数据类型,所以只要他们的值相等,返回的结果自然是true
- a和e看似好像和c和d是一样的情况,其实不然。a和e和a和f都是引用类型的比较,看的不是二者的内容而是是否是同一引用。其中a和e是同一引用,故为true,而a和f不是同一引用,故为false。这里String x = “???”和String y = new String (“???”)的区别,在本文后半段会提起
- 同理,userthree和userfour不是同一引用,即使看起来值一样,但是也是false。
2. equals的用法
在《java核心技术卷1》中对Object描述:Object类是所有java类的始祖。每个类都继承了Object,故每个类都有equals方法。
equals方法主要是检测两个对象是否相等。想要知道比较原理和依据是什么,只有查看一下源码才知道。
下面是Object类中equals方法源码:
public boolean equals(Object obj) {
return (this == obj);
}
可见,equals方法调用的就是“==”这个运算符,其本质和“==”运算符并无区别。
java语言规范要求equals方法具有以下特性:
- 自反性。对于任意不为null的引用值x,x.equals(x)一定是true。
- 对称性。对于任意不为null的引用值x和y,当且仅当x.equals(y)是true时,y.equals(x)也是true。
- 传递性。对于任意不为null的引用值x、y和z,如果x.equals(y)是true,同时y.equals(z)是true,那么x.equals(z)一定是true。
- 一致性。对于任意不为null的引用值x和y,如果用于equals比较的对象信息没有被修改的话,多次调用时x.equals(y)要么一致地返回true要么一致地返回false。
- 对于任意不为null的引用值x,x.equals(null)返回false。
如果分析到了这里,得出equals方法等同于“==”就结束了,那么学习的深度也太浅了。虽然equals方法等于“==”,但是很多类对equals方法进行了重写。下面看一个典型的类:String
下面我们看看String类中equals方法源码:
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类对equals方法重写,使得String类中equals方法比较的是两个字符串的内容是否相等,而不是引用是否一致了。
举个例子:
public static void main(String[] args) {
User userthree = new User("zs",1);
User userfour = new User("zs",1);
String a = "yao";
String b = "zhi";
String e = "yao";
System.out.println(a.equals(e));//true
System.out.println(userthree.equals(userfour));//false
System.out.println(a.equals(b));//false
}
分析一下:
- 显然userthree和userfour之间的使用的equals是未重写的,故比较的是二者之间的引用,所以为false
- 而a和b和e之间的equals是使用的String类的重写方法equals,故比较的是二者值是否相同,所以为true。
以上基本就解释清楚了“==”和equals的区别,但是我们这是思维导图式学习法,故仅仅这些还是不够的。
除了以上两种比较还有一个方法与之有关,不过略有差别。
hashCode也是Object类中方法,观其源码:
public native int hashCode();
该方法是一个本地方法;该方法返回对象的散列码(int类型);它的实现是根据本地机器相关的;
那什么是hash呢?
Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
Java对于eqauls方法和hashCode方法是这样规定的:
- 如果两个对象相同,那么它们的hashCode值一定要相同;
- 如果两个对象的hashCode相同,它们并不一定相同。
- equals()相等的两个对象,hashcode()一定相等;equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。
那么在什么地方使用hashCode?
Hashcode值主要用于基于散列的集合,如HashMap、HashSet、HashTable…等等;
这些集合都使用到了hashCode,想象一下,这些集合中存有大量的数据,假如有一万条,我们向其中插入或取出一条数据,插入时如何判断插入的数据已经存在?取出时如何取出相同的数据?难道一个一个去比较?这时候,hashCode就提现出它的价值了,大大的减少了处理时间;这个有点类似于MySQL的索引;
举个例子:
public static void main(String[] args) {
//未重写hashCode的类
User userthree = new User("zs",1);
User userfour = new User("zs",1);
System.out.println(userthree.hashCode());
System.out.println(userfour.hashCode());
//重写了hashCode的类
String testone = new String("String");
String testtwo = new String("String");
System.out.println(testone.hashCode());
System.out.println(testtwo.hashCode());
}
运行结果截图:
分析如下:
- userthree和userfourequals不等,故hashCode不等
- testone和testtwo因为String类重写了,equals相等,故hashCode相等。
看一下String类重写hashCode的源码:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
由hashCode又可以联想到什么呢?(后文将继续探讨,此处不再介绍)
前面提到了String x = “???”和String y = new String (“???”)的区别。现在这里来了解一下:
在研究String直接赋值与new String的区别之前我们需要先了解java中的字符串常量池的概念。
字符串常量池
String类是我们平常项目中使用频率非常高的一种对象类型,jvm为了提升性能和减少内存开销,避免字符的重复创建,其维护了一块特殊的内存空间,即字符串池,当需要使用字符串,先去字符串池中查看该字符串是否已经存在,如果存在,直接使用;不过不存在,则先初始化,并将该字符串放入字符串常量池中。
使用String直接赋值
String str = “abc”;可能创建一个或者不创建对象,如果”abc”在字符串池中不存在,会在java字符串池中创建一个String对象(”abc”),然后str指向这个内存地址,无论以后用这种方式创建多少个值为”abc”的字符串对象,始终只有一个内存地址被分配。==判断的是对象的内存地址,而equals判断的是对象内容。通过以下代码测试:
String str = "abc";
String str1 = "abc";
String str2 = "abc";
System.out.println(str==str1);//true
System.out.println(str==str2);//true
即三者指向同一内存地址
使用new String 创建字符串
String str = new String(“abc”);至少会创建一个对象,也有可能创建两个。因为用到new关键字,肯定会在堆中创建一个String对象,如果字符池中已经存在”abc”,则不会在字符串池中创建一个String对象,如果不存在,则会在字符串常量池中也创建一个对象。
String str = new String("abc");
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str==str1);//false
System.out.println(str==str2);//false
显然,三者地址不同
此时,又可以进行深入理解一下Java的string类常量池的知识(篇幅有限,将再后文另开一篇文章来学习)
注意到在String类重写equals方法时,有一个关键字instanceof,或许我以前知道干嘛的但是忘了,又或者一直不知道,但是至少我现在不知道,故研究一下:
- 返回类型是 boolean值
- 在运行时指出对象是否是特定类的一个实例
由if (anObject instanceof String) 可知其基本用法:
result = Object instanceof Class
- result 返回值 boolean型
- Object是你要判断是表达式
- Class是你要判断的结果对象类型
故:if (anObject instanceof String) 是判断anObject表达式是否是String类型。
但是instanceof在Java的编译状态和运行状态是有区别的:
- 在编译状态中,class可以是object对象的父类,自身类,子类。在这三种情况下Java编译时不会报错。
- 在运行转态中,class可以是object对象的父类,自身类,不能是子类。在前两种情况下result的结果为true,最后一种为false。但是class为子类时编译不会报错。运行结果为false。
才疏学浅之处,切莫在意。
——2019.7.1下午