java自反性(java反射怎么实现)

华为云服务器特价优惠火热进行中!

2核2G2兆仅需 38 元;4核4G3兆仅需 79 元。购买时间越长越优惠!更多配置及优惠价格请咨询客服。

合作流程:
1、点击链接注册/关联华为云账号:点击跳转
2、添加客服微信号:cloud7591,确定产品方案、价格方案、服务支持方案等;
3、客服协助购买,并拉微信技术服务群,享受一对一免费技术支持服务;
技术专家在金蝶、华为、腾讯原厂有多年工作经验,并已从事云计算服务8年,可对域名、备案、网站搭建、系统部署、AI人工智能、云资源规划等上云常见问题提供更专业靠谱的服务,对相应产品提供更优惠的报价和方案,欢迎咨询。

本篇文章给大家谈谈java自反性,以及java反射怎么实现对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

微信号:cloud7591
如需了解更多,欢迎添加客服微信咨询。
复制微信号

本文目录一览:

Java实现Comparable接口

Java lang Comparable接口中唯答纤一的方法是pareTo() 在该方法中可以进行简单的相等比较以及执行顺序比较 接口实现框架如下 [java] view plaincopyprint?

public class ComparableImpl implements ComparableComparableImpl {

@Override public int pareTo(ComparableImpl o) { // TODO Auto generated method stub return }

}一个类实现了Comparable接口 则说明它的实例具有内在的排序关系 就可以跟多种泛型算法以及依赖于该接口的集合实现进行协作 依赖于比较关系的类包括有序集合类TreeSet和TreeMap 以及工具类Collections和Arrays 若一个数组中的元素实现了Comparable接口 则可以直接使用Arrays类的sort方法对这个数组进行排序 Java平台库中的所有值类(value classes)都实现了Comparable接口

歼卜Comparable的规范说明如下 将当前这个对象与指定对象进行顺序比较 当该对象小于 等于或大于指定对象时 分别返回一个负整数 零或者正整数 如果由于指定对象的类型而使得无法进行比较 则抛出ClassCastException异常

pareTo方法的实现必须满足如下几个限制条件 自反性 对称性 传递性和非空性

一般氏举穗来说 paraTo方法的相等测试应该返回与equals方法相同的结果 如果相同 则由pareTo方法施加的顺序关系被称为 与equals一致 如果不同 则顺序关系被称为 与equals不一致 如果一个类的pareTo方法与equals方法的顺序关系不一致 那么它仍然能正常工作 只是 如果一个有序集合包含了该类的实例 则这个集合可能无法遵循某些集合接口的通用约定 因为集合接口的通用约定是按照equals方法定义的 而有序集合使用了由pareTo施加的相等测试 下面是实现了Comparable接口的类 同时 该类还重写了equals和hashCode等方法 [java] view plaincopyprint?

public abstract class ZLTextPosition implements ComparableZLTextPosition {

public abstract int getParagraphIndex() public abstract int getElementIndex() public abstract int getCharIndex()

public boolean samePositionAs(ZLTextPosition position) { return getParagraphIndex() == position getParagraphIndex() getElementIndex() == position getElementIndex() getCharIndex() == position getCharIndex() }

@Override public int pareTo(ZLTextPosition position) { final int p = getParagraphIndex() final int p = position getParagraphIndex() if (p != p ) { return p p ? }

final int e = getElementIndex() final int e = position getElementIndex() if (e != e ) { return e e ? }

final int c = getCharIndex() final int c = position getCharIndex() if (c != c ) { return c c ? } return }

@Override public boolean equals(Object obj) { if (this == obj) { return true } if (!(obj instanceof ZLTextPosition)) { return false }

final ZLTextPosition position = (ZLTextPosition)obj return samePositionAs(position) }

@Override public int hashCode() { return (getParagraphIndex() ) + (getElementIndex() ) + getCharIndex() }

@Override public String toString() { return getClass() getName() + + getParagraphIndex() + + getElementIndex() + + getCharIndex() }

lishixinzhi/Article/program/Java/hx/201311/26372

Equals And HashCode 梳理

常言道:"基础不牢,地动山摇"。万丈高楼平地起,不管学习什么语言,在高谈阔论框架和语言应用的同时,也不能忘了语言基础。

本文是关于Equals和HashCode关系梳理的一篇文章,会主要从定义、联系、使用这三个方面围绕展开。

equals()是object里的方法,话不多说,先直接上源码:

正如注释第一句所谈到的, equals就是一个辨别两个对象之间的"相等"关系的函数 。

当然这种相等关系应由子类自行定义,但注释中指出需遵守以下性质:

性质1: reflexive,自反性 。对于任何非空引用x,x.equals(x)==true应恒成立。

性质2: symmetric,对称性 。对于两个非空引用x和y,x.euqals(y)==true当且仅当y.euqals(x)==true。

性质3: transitive,传递性。 简单地讲,x equals y , y equals z, 则x equals z成立。

性质4: consistent,一致性。 如果两个非空对象x,y没有发生变化,则反复调用x.euqals(y)所返回的结果应一致。

性质5:对于任何非空引用x,x.equals(null)==false成立。

了解完性质,关键的地方来了:

注释中指出,重写equals()则应重写hashcode(),使得equals相等的对象具有相等的hashCode。

hashcode是native方法,具体生成的细节和jdk版本有关,如何生成hashcode并不是今天的主题。接下来,我们看下hashcode的部分注释:

总的来讲:

hashcode的产生是为了庆雀早hashmap等使用到hashcode的散列存储结构服务的。

hashcode有如下三个性质:

性质1 :一致性:对于同一对象,多次调用hashcode()应返回相同的Integer。

性质2 :equals相等,则hashCode必须相等。

性质3 :equals不等,hashCode不是必须不等。

了解了hashcode()以及equals()之后,肯定会发生这样一个疑问,为什么equals相等,hashcode必须相等?

实际上,这个问题只要搞懂hashcode()的用途,就可以很清楚的明白了。

hashcode()产生的目的就是为了hashMap等散列存储结构提供支持。

提供什么样的支持?

首先要明白,散列结构存储元素的重要过程之一是判别元素的“相同”/“不同”,这个相同与否是根据元素的hash值(为了降低hash冲突的概率,不同结构计算hash值的方式不同,但都是基于hashCode)决定的。每次put元素,则计算对应元素的hash值以及对应的坐标 ,有hash冲突则解决hash冲突,没有就直接存放。

这样做的好处是 ,当我要誉雀向一个散列结构存储一个元素时,我判别是否有重复元素,只需要计算hash值一次并比较,而不是对已经存储的每个元素依次equals(想象一个巨大的hashset,每次存放一个元素要和所有元素依次equals,这效率该多么低下)。

所以问题来了 ,如果散列结构存储的元素重写了equals方法却没有重写hashcode()方法,就会导致一个问题:逻辑上我们认为相同的对象(equals==true),在散列存储结构的存储中因为hashcode的不同,最终被判定为两个不同的对象,从而存放了两份。

显然这并不是我们想要的。

下面附上一个例子,来展示下

这里创建了一个Person对象,重写了equals方法岁改,equals方法的逻辑是如果是同一个对象或者两个Person对象的名字和年龄相等,则认为是“相等”的。

这里我们创建了两个我们认为逻辑上相等的对象,使用hashset测试结果为:

显然hashset将其认为是两个对象,分别存储了。

所以我们现在要做的就是重写hashCode()方法,确保equals相等的实例hashcode()也相等。

不要小瞧equals()和hashcode()的重写,如果不恰当的重写,会导致一系列难以预料的结果产生。

在上面的person例子中,对equals的重写,我参照了String的equals重写。

大致逻辑是 判断是否为同一个对象,如果否,判断是否相同类型且逻辑相等。

实际上《Effective Java》这本书中提出了一个关于重写equals的规范,可以进行参考。

对于equals的重写, 最应该记住的是传入的应该是object! (最好使用@Override来帮助检查),否则就不是重写而是重载了!

当然,在重写完equals后最好对其进行单元测试,看其是否符合之前所讲的equals方法所强调的性质 。如果你是android开发人员,简单的junit单元测试可以参照我之前的一篇博客。

同样的《Effective Java》这本书中提出了一个关于重写HashCode方法的建议,可以进行参考。

我们参照上面的建议,重写一下Person类的hashcode():

在未测试的情况下这个hashcode()的冲突处理是否合适还需考量,但逻辑上它确实保证了与equals方法的同步。

现在再进行测试,发现已经搞定。

看到《Effective Java》建议的你,一定会有些疑问,why must 31?

事实上,如果你看过String(Android jdk)的hashCode源码,你会发现它是这样的

它竟然也使用了31这个数字!!

所以这背后的原理是什么呢?31又有怎样的好处呢?

《Effective Java》里也进行了解释:

这段话里已经讲的比较明确了:

1.选择奇数,防止溢出信息丢失。(实际这一点我还不太明白,有懂的老哥可以讲讲)

2.选择素数,because its traditional。(总的来讲就是和是否是素数关系不大。可以看下面的StackOverFlow链接,许多人认为和是否为素数没关系,毕竟所有的素数都是奇数(除了2))

3.乘以31,可以被优化为位运算,就会比传统的乘法快很多。

所以为什么必须是31呢?41,51不行吗?

stackOverFlow的这篇问答里有一个老哥做了测试:

也就是说,如果对超过50000个英语单词做hash运算,并采用31,33,37,39,41等做为常数乘,结果冲突次数都在7次以内。

那么31是最接近2幂次的数字,优化为位运算更简洁,自然采用31。

这篇博客在草稿箱里也待了好几天,一直耽搁着没有发出来。今天完成了发出来真是畅快啊。

java里 equals和== 区别

==是一个比较运算符,基本数据链尺歼类型比较的是值,引用数据类型比较的是地址值。

(比较地址值即是指是否为同一个对象的引用)

equals()是一个方法,只能比较引用数据类型。重写前比较的是地址值,重写后比一般是比较对象的属性。

扩展资料:

java中的数困卜据类型,可分为两类:

1.基本数据类型,也称原始数据类型。

byte,short,char,int,long,float,double,boolean,他们之间的比较,应用双等号(==),比较的是他们的值。

2.复合数据类型。

当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个棚冲new出来的对象,他们的比较后的结果为true,否则比较后结果为false。

JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地址。

但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。

对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。

java中的equals,hashcode的区别和联系

equals

public boolean equals(Object obj)

指示其他某个对象是否与此对象“相等”。

equals 方法在非空对象引用上实现相等关系:

自反性:对于任何非空引用值 x,x.equals(x) 都应返回 

true。

对称性:对于任何非空引用值 x 和 y,当且仅当 

y.equals(x) 返回 true 时,x.equals(y) 才应返回 

true。

传递性:对于任何非空引用值 x、y 和 z,如果 

x.equals(y) 返回 true,并且 y.equals(z) 返回 

true,那么 x.equals(z) 应返回 true。

一致性:对于任何非空引用值 x 和 y,多次调用 

x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 

equals 比较中所用的信息没有被修改。

对于任何非空引用值 x,x.equals(null) 都应返回 

false。

Object 类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值 

x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true(x == y 具有值 true)。

注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

参数:

obj - 要与之比较的引用对象。

返回:

如果此对象与 obj 参数相同,则返回 true;否则返回 false。

另请参见:

hashCode(), Hashtable

hashCode

public int hashCode()

返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提判虚茄供的哈希表)的性能。

hashCode 的常规协定是:

在 Java 应用程序执行期间,在掘察对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 

equals 比较时所用的信息没有被修改。从誉埋某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。

如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 

hashCode 方法都必须生成相同的整数结果。

如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

实际上,由 Object 类定义的 hashCode 

方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)

返回:

此对象的一个哈希码值。

另请参见:

equals(java.lang.Object), 

Hashtable

也就是说equals和hashCode的关系只是在于一个协定,equals默认判断依据是对象是否相等,hashCode()得到对象的内存地址的一个特殊计算得到的值,协定内容是:equals相等的对象的hashCode值相等,所以要求重写了equals之后重写hashCode。

equals是判断对象是否相等的方法。hashCode是得到对象hash值的方法,对象hash值默认是根据内存地址计算得到。equals默认表的是对象内存地址。

java中users[i].name.equals(name))怎么理解

前言

这一篇文章是对java的Object的方法的equals的描述,也包括对重写方法写的一些理解。

一、Object类的equals方法?

1. Object是什么?

首先要明确Object类中有什么,它是什么?

Object类在《java核心技术1》中是这样描述的是Java中所有类的始祖,每个类都是尤其扩展出来的。所以我们创建的类应该都会有应该有这样的表达:

public class Classname extends Object{ }

登录后复制

但其实上公司早已经考虑过这个问题了,所以每个类都是默认继承Object的。所以直接就可以就可以引用Object类中的方法,编译器是不会报错的。

2. equals方法的解析

1、 为什么需要这个方法?

因为传统的逻辑判断“ == ”,是去判断数据在内存堆中地址,基础数据在堆中内存是相同的,所以 “ ==”最后是对数据的值来进行比较的。

2、 equals方法在书上的解析

在《Java核心技术1》中对equals方法的描述是用于检查一个对象是否等于另一个对象,这两个对象是否具有相同的引用。然后接下来我会使用代码,以及代码运行的结果对这个方法进行讲解。

/*

我们创造一个Employee类来验证——一个对象是否等于另一个对象,这两个对象是否具有相同的引用

*/

public class Employee extends Person{

private int salary;

private int worktime;

public Employee(String name, String sex,int salary, int worktime) {

this.name = name;

this.sex = sex;

this.salary = salary;

this.worktime = worktime;

}

/*

现在我们目标是使用equals方法

具体实现是:

1.首先先使用Employee类来进行信息的比对

2.再使用Student类和Employee类进行比对

*/迹返洞

//step1

Employee employee1 = new Employee("啊狗","男",2000,12);

Employee employee2 = new Employee("啊狗","男",2000,12);

//现在我们创造两个数据皆相同的对象我们现在进行equals方法的判断

System.out.println(employee1.equals(employee2));

登录后复姿枯制

终端会给我们输出false,我们可以开调试来看看为什么会输出flase。我们可以看到employee1和employee2地址会不同,这就能解释到《Java核心技术1》对Object类中的equals方法是对比一个对象是否等于另外一个对象。这不仅仅局限于数据的相同,也需要去判断数据存放的地址是否相同。

3. 对上公司的Object类方法的理解

深入的我们可以去看看上公司如何世晌写Object类的equals方法。

public boolean equals(Object obj)

return (this == obj);

}

登录后复制

这里的equals方法接入的是Object类, (this == obj)这句话的意思是当前对象和传入obj地址是否相等,因为 “==”的作用就是比较两者地址是否相同。

4. 对上公司对重写的equals方法的一些理解

那么接下来我们研究方向就是如何让两个数据相同的对象相同,这一点由为之重要。上公司已经非常贴心地为我们重写好了这个方法,我们可以一起看看这个方法地实现。

@Override

public boolean equals(Object o) {

if (this == o) return true;

if (o == null || getClass() != o.getClass()) return false;

Employee employee = (Employee) o;

return salary == employee.salary worktime == employee.worktime;

}

登录后复制

if (this == o) return true;

登录后复制

这句话是自反性的体现,先使用this调用当前对象,然后再将对象o放入判断语句中,也就是书中所说的对于非空引用x,x.equals(x)会返回true。

if (o == null || getClass() != o.getClass()) return false;

登录后复制

这句话是将调用getClass()方法,因为两个不同的类不可以做比较,会直接返回false的。具体我们可以利用我们的Student类和Employee类来做比较。

Employee employee1 = new Employee("啊狗","男",2000,12);

Student student = new Student("啊猫","男",18,"智能科学");

//2.再使用Student类和Employee类进行比对

System.out.println(employee1.equals(student));

登录后复制

无论从直观上还是客观上来看,我们都能会觉得终端会输出false,事实如此,确实最后终端是输出了false。

Employee employee = (Employee) o;

登录后复制

这句话又是什么意思呢?这个涉及一个强制类型转化的知识,因为所有类都默认继承或者间接继承Object类,因为前面已经使用getClass方法来判断类是否相同,所以这里不需要进行instanceof来判断两个类是否相同,如果两个类不同的话,直接运行java会抛出ClassExceptionError错误。这一步就是让传入的值获得自己私有得信息,这一个点是涉及多态的缺点的缘由。

return salary == employee.salary worktime == employee.worktime;

登录后复制

这一句话我们可以通过调试来看看到底相比较的是什么?我们可以看到是将当前对象的salary和worktime的值来和传入对象的salary和worktime的值比较,这样就可以免除“==”比较地址而造成最后的结果和主观感觉不一样的预期。

总结

这篇文章是我第一次写博客,文章结合《java核心技术1》对Object类中的equals方法的一点理解的,本来想写Object类常用API的一个汇总,但是鉴于能力的不足,所以最后仅仅只是写了equals方法的理解。这一次编写博客的过程中,我明显发现自己对很多不理解的地方,本文如果有讲解错误,或者讲解不当的地方,希望大家能指正我的错误,感谢!

请问各位大佬这道java的重写equals和重写hashcode方法内部是什么意思呀?

1.equals()的所属以及内部原理(即Object中equals方法的实现原理)

说起equals方法,我们都知道是超类Object中的一个基本方法,用于检测一个对象是否与另外一个对象相等。而在Object类中这个方法实际上是判断两个对象是否具有相同的引用,如果有,它们就一定相等。其源码如下:

public boolean equals(Object obj) { return (this == obj); }

实际上我们知道所有的对象都拥有标识(内存地址)和状态(数据),同时“==”比较两个对象的的内存地址,所以说 Object 的 equals() 方法是比较两个对象的内存地址是否相等,即若 object1.equals(object2) 为 true,则表示 equals1 和 equals2 实际上是引用同一个对象。

2.equals()与‘==’的区别

或许这是我们面试时更容易碰到的问题”equals方法与‘==’运算符有什么区别?“,并且常常我们都会胸有成竹地回答:“equals比较的是对象的内容,而‘==’比较的是对象的地址。”。但是从前面我们可以知道equals方法在Object中的实现也是间接使用了‘==’运算符进行比较的,所以从喊码严格意义上来说,我们前面的回答并不完全正确。我们先来看一段代码并运行再来讨论这个问题。

package com.zejian.test;

public class Car {

private int batch;

public Car(int batch) {

this.batch = batch;

}

public static void main(String[] args) {

Car c1 = new Car(1);

Car c2 = new Car(1);

System.out.println(c1.equals(c2));

System.out.println(c1 == c2);

}

}

运行结果:

false

false

分析:对于‘==’运算符比较两个Car对象,返回了false,这点我们很容易明白,毕竟它们比较的是内存地址,而c1与c2是两个不同的对象,所以c1与c2的内存地址自然也不一样握渗差。现在的问题是,我们希望生产的两辆的批次(batch)相同的情况下就认为这两辆车相等,但是运行的结果是尽管c1与c2的批次相同,但equals的结果却反回了false。当然对于equals返回了false,我们也是心知肚明的,因为equal来自Object超类,访问修饰符为public,而我们并没有重写段皮equal方法,故调用的必然是Object超类的原始方equals方法,根据前面分析我们也知道该原始equal方法内部实现使用的是'=='运算符,所以返回了false。因此为了达到我们的期望值,我们必须重写Car的equal方法,让其比较的是对象的批次(即对象的内容),而不是比较内存地址,于是修改如下:

@Override

public boolean equals(Object obj) {

if (obj instanceof Car) {

Car c = (Car) obj;

return batch == c.batch;

}

return false;

}

使用instanceof来判断引用obj所指向的对象的类型,如果obj是Car类对象,就可以将其强制转为Car对象,然后比较两辆Car的批次,相等返回true,否则返回false。当然如果obj不是 Car对象,自然也得返回false。我们再次运行:

true

false

嗯,达到我们预期的结果了。因为前面的面试题我们应该这样回答更佳

总结:默认情况下也就是从超类Object继承而来的equals方法与‘==’是完全等价的,比较的都是对象的内存地址,但我们可以重写equals方法,使其按照我们的需求的方式进行比较,如String类重写了equals方法,使其比较的是字符的序列,而不再是内存地址。

3.equals()的重写规则

前面我们已经知道如何去重写equals方法来实现我们自己的需求了,但是我们在重写equals方法时,还是需要注意如下几点规则的。

自反性。对于任何非null的引用值x,x.equals(x)应返回true。

对称性。对于任何非null的引用值x与y,当且仅当:y.equals(x)返回true时,x.equals(y)才返回true。

传递性。对于任何非null的引用值x、y与z,如果y.equals(x)返回true,y.equals(z)返回true,那么x.equals(z)也应返回true。

一致性。对于任何非null的引用值x与y,假设对象上equals比较中的信息没有被修改,则多次调用x.equals(y)始终返回true或者始终返回false。

对于任何非空引用值x,x.equal(null)应返回false。

当然在通常情况下,如果只是进行同一个类两个对象的相等比较,一般都可以满足以上5点要求,下面我们来看前面写的一个例子。

package com.zejian.test;

public class Car {

private int batch;

public Car(int batch) {

this.batch = batch;

}

public static void main(String[] args) {

Car c1 = new Car(1);

Car c2 = new Car(1);

Car c3 = new Car(1);

System.out.println("自反性-c1.equals(c1):" + c1.equals(c1));

System.out.println("对称性:");

System.out.println(c1.equals(c2));

System.out.println(c2.equals(c1));

System.out.println("传递性:");

System.out.println(c1.equals(c2));

System.out.println(c2.equals(c3));

System.out.println(c1.equals(c3));

System.out.println("一致性:");

for (int i = 0; i 50; i++) {

if (c1.equals(c2) != c1.equals(c2)) {

System.out.println("equals方法没有遵守一致性!");

break;

}

}

System.out.println("equals方法遵守一致性!");

System.out.println("与null比较:");

System.out.println(c1.equals(null));

}

@Override

public boolean equals(Object obj) {

if (obj instanceof Car) {

Car c = (Car) obj;

return batch == c.batch;

}

return false;

}

}

运行结果:

自反性-c1.equals(c1):true

对称性:

true

true

传递性:

true

true

true

一致性:

equals方法遵守一致性!

与null比较:

false

由运行结果我们可以看出equals方法在同一个类的两个对象间的比较还是相当容易理解的。但是如果是子类与父类混合比较,那么情况就不太简单了。下面我们来看看另一个例子,首先,我们先创建一个新类BigCar,继承于Car,然后进行子类与父类间的比较。

package com.zejian.test;

public class BigCar extends Car {

int count;

public BigCar(int batch, int count) {

super(batch);

this.count = count;

}

@Override

public boolean equals(Object obj) {

if (obj instanceof BigCar) {

BigCar bc = (BigCar) obj;

return super.equals(bc) count == bc.count;

}

return false;

}

public static void main(String[] args) {

Car c = new Car(1);

BigCar bc = new BigCar(1, 20);

System.out.println(c.equals(bc));

System.out.println(bc.equals(c));

}

}

运行结果:

true

false

对于这样的结果,自然是我们意料之中的啦。因为BigCar类型肯定是属于Car类型,所以c.equals(bc)肯定为true,对于bc.equals(c)返回false,是因为Car类型并不一定是BigCar类型(Car类还可以有其他子类)。嗯,确实是这样。但如果有这样一个需求,只要BigCar和Car的生产批次一样,我们就认为它们两个是相当的,在这样一种需求的情况下,父类(Car)与子类(BigCar)的混合比较就不符合equals方法对称性特性了。很明显一个返回true,一个返回了false,根据对称性的特性,此时两次比较都应该返回true才对。那么该如何修改才能符合对称性呢?其实造成不符合对称性特性的原因很明显,那就是因为Car类型并不一定是BigCar类型(Car类还可以有其他子类),在这样的情况下(Car instanceof BigCar)永远返回false,因此,我们不应该直接返回false,而应该继续使用父类的equals方法进行比较才行(因为我们的需求是批次相同,两个对象就相等,父类equals方法比较的就是batch是否相同)。因此BigCar的equals方法应该做如下修改:

@Override

public boolean equals(Object obj) {

if (obj instanceof BigCar) {

BigCar bc = (BigCar) obj;

return super.equals(bc) count == bc.count;

}

return super.equals(obj);

}

这样运行的结果就都为true了。但是到这里问题并没有结束,虽然符合了对称性,却还没符合传递性,实例如下:

package com.zejian.test;

public class BigCar extends Car {

int count;

public BigCar(int batch, int count) {

super(batch);

this.count = count;

}

@Override

public boolean equals(Object obj) {

if (obj instanceof BigCar) {

BigCar bc = (BigCar) obj;

return super.equals(bc) count == bc.count;

}

return super.equals(obj);

}

public static void main(String[] args) {

Car c = new Car(1);

BigCar bc = new BigCar(1, 20);

BigCar bc2 = new BigCar(1, 22);

System.out.println(bc.equals(c));

System.out.println(c.equals(bc2));

System.out.println(bc.equals(bc2));

}

}

运行结果:

true

true

false

bc,bc2,c的批次都是相同的,按我们之前的需求应该是相等,而且也应该符合equals的传递性才对。但是事实上运行结果却不是这样,违背了传递性。出现这种情况根本原因在于:

父类与子类进行混合比较。

子类中声明了新变量,并且在子类equals方法使用了新增的成员变量作为判断对象是否相等的条件。

只要满足上面两个条件,equals方法的传递性便失效了。而且目前并没有直接的方法可以解决这个问题。因此我们在重写equals方法时这一点需要特别注意。虽然没有直接的解决方法,但是间接的解决方案还说有滴,那就是通过组合的方式来代替继承,还有一点要注意的是组合的方式并非真正意义上的解决问题(只是让它们间的比较都返回了false,从而不违背传递性,然而并没有实现我们上面batch相同对象就相等的需求),而是让equals方法满足各种特性的前提下,让代码看起来更加合情合理,代码如下:

package com.zejian.test;

public class Combination4BigCar {

private Car c;

private int count;

public Combination4BigCar(int batch, int count) {

c = new Car(batch);

this.count = count;

}

@Override

public boolean equals(Object obj) {

if (obj instanceof Combination4BigCar) {

Combination4BigCar bc = (Combination4BigCar) obj;

return c.equals(bc.c) count == bc.count;

}

return false;

}

}

从代码来看即使batch相同,Combination4BigCar类的对象与Car类的对象间的比较也永远都是false,但是这样看起来也就合情合理了,毕竟Combination4BigCar也不是Car的子类,因此equals方法也就没必要提供任何对Car的比较支持,同时也不会违背了equals方法的传递性。

4.equals()的重写规则之必要性深入解读

前面我们一再强调了equals方法重写必须遵守的规则,接下来我们就是分析一个反面的例子,看看不遵守这些规则到底会造成什么样的后果。

package com.zejian.test;

import java.util.ArrayList;

import java.util.List;

/** * 反面例子 * @author zejian */

public class AbnormalResult {

public static void main(String[] args) {

ListA list = new ArrayListA();

A a = new A();

B b = new B();

list.add(a);

System.out.println("list.contains(a)-" + list.contains(a));

System.out.println("list.contains(b)-" + list.contains(b));

list.clear();

list.add(b);

System.out.println("list.contains(a)-" + list.contains(a));

System.out.println("list.contains(b)-" + list.contains(b));

}

static class A {

@Override

public boolean equals(Object obj) {

return obj instanceof A;

}

}

static class B extends A {

@Override

public boolean equals(Object obj) {

return obj instanceof B;

}

}

}

上面的代码,我们声明了 A,B两个类,注意必须是static,否则无法被main调用。B类继承A,两个类都重写了equals方法,但是根据我们前面的分析,这样重写是没有遵守对称性原则的,我们先来看看运行结果:

list.contains(a)-true

list.contains(b)-false

list.contains(a)-true

list.contains(b)-true

19行和24行的输出没什么好说的,将a,b分别加入list中,list中自然会含有a,b。但是为什么20行和23行结果会不一样呢?我们先来看看contains方法内部实现

@Override

public boolean contains(Object o) {

return indexOf(o) != -1;

}

进入indexof方法

@Override

public int indexOf(Object o) {

E[] a = this.a;

if (o == null) {

for (int i = 0; i a.length; i++)

if (a[i] == null)

return i;

} else {

for (int i = 0; i a.length; i++)

if (o.equals(a[i]))

return i;

}

return -1;

}

可以看出最终调用的是对象的equals方法,所以当调用20行代码list.contains(b)时,实际上调用了

b.equals(a[i]),a[i]是集合中的元素集合中的类型而且为A类型(只添加了a对象),虽然B继承了A,但此时

a[i] instanceof B

结果为false,equals方法也就会返回false;而当调用23行代码list.contains(a)时,实际上调用了a.equal(a[i]),其中a[i]是集合中的元素而且为B类型(只添加了b对象),由于B类型肯定是A类型(B继承了A),所以

a[i] instanceof A

结果为true,equals方法也就会返回true,这就是整个过程。但很明显结果是有问题的,因为我们的 list的泛型是A,而B又继承了A,此时无论加入了a还是b,都属于同种类型,所以无论是contains(a),还是contains(b)都应该返回true才算正常。而最终却出现上面的结果,这就是因为重写equals方法时没遵守对称性原则导致的结果,如果没遵守传递性也同样会造成上述的结果。当然这里的解决方法也比较简单,我们只要将B类的equals方法修改一下就可以了。

static class B extends A{

@Override

public boolean equals(Object obj) {

if(obj instanceof B){

return true;

}

return super.equals(obj);

}

}

到此,我们也应该明白了重写equals必须遵守几点原则的重要性了。当然这里不止是list,只要是java集合类或者java类库中的其他方法,重写equals不遵守5点原则的话,都可能出现意想不到的结果。

5.为什么重写equals()的同时还得重写hashCode()

这个问题之前我也很好奇,不过最后还是在书上得到了比较明朗的解释,当然这个问题主要是针对映射相关的操作(Map接口)。学过数据结构的同学都知道Map接口的类会使用到键对象的哈希码,当我们调用put方法或者get方法对Map容器进行操作时,都是根据键对象的哈希码来计算存储位置的,因此如果我们对哈希码的获取没有相关保证,就可能会得不到预期的结果。在java中,我们可以使用hashCode()来获取对象的哈希码,其值就是对象的存储地址,这个方法在Object类中声明,因此所有的子类都含有该方法。那我们先来认识一下hashCode()这个方法吧。hashCode的意思就是散列码,也就是哈希码,是由对象导出的一个整型值,散列码是没有规律的,如果x与y是两个不同的对象,那么x.hashCode()与y.hashCode()基本是不会相同的,下面通过String类的hashCode()计算一组散列码:

package com.zejian.test;

public class HashCodeTest {

public static void main(String[] args) {

int hash=0;

String s="ok";

StringBuilder sb =new StringBuilder(s);

System.out.println(s.hashCode()+" "+sb.hashCode());

String t = new String("ok");

StringBuilder tb =new StringBuilder(s);

System.out.println(t.hashCode()+" "+tb.hashCode());

}

}

运行结果:

3548 1829164700

3548 2018699554

我们可以看出,字符串s与t拥有相同的散列码,这是因为字符串的散列码是由内容导出的。而字符串缓冲sb与tb却有着不同的散列码,这是因为StringBuilder没有重写hashCode方法,它的散列码是由Object类默认的hashCode方法计算出来的对象存储地址,所以散列码自然也就不同了。那么我们该如何重写出一个较好的hashCode方法呢,其实并不难,我们只要合理地组织对象的散列码,就能够让不同的对象产生比较均匀的散列码。

原文链接:

关于java自反性和java反射怎么实现的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。

发布于 2023-04-14 18:04:11
收藏
分享
海报
33
目录

    忘记密码?

    图形验证码

    复制成功
    微信号: cloud7591
    如需了解更多,欢迎添加客服微信咨询。
    我知道了