重写equals方法(未完)
equals方法是我们日常编程中很常见的方法,Object中对这个方法的解释如下:
boolean |
查看该方法的底层代码如下:
public boolean equals(Object obj) {
return (this == obj);
}
通过上面的代码很容易就能看出来,Object的equals方法上是用来比较两个实例是否为同一个实例对象,底层还是依赖于==的判断。
2.4.1.1 什么情况下无需覆盖equals方法
Equals方法看起来是个很简单的方法,但是重写equals方法还是很容易出现问题的,并不是说每个类都需要来重写equals方法。满足下面任意条件的类都可以不用覆盖equals方法;
- l 类的每个实例本质上都是唯一的,如枚举,单例等;
- l 不关心类是否提供了“逻辑相等”的测试功能。如Random覆盖equals,检查两个Random实例是否产生相同的随机数序列,但是这其实是很不必要的,所以从Object中继承来的equals方法已经足够了。
- l 超类已经覆盖了equals方法,从超类集成过来的行为对于子类也是合适的。如List实现从ArrayList继承equals实现。
- l 类是私有的或者包访问权限的,可以确定它的equals方法永远不会被调用。
通过上面的叙述,知道了只要满足四种情况,那么无疑覆盖equals方法,既是多余也容易出现问题,我们应该加以避免。
2.4.1.2 覆盖equals方法
了解了无需覆盖equals的条件,那么什么条件中覆盖equals方法呢?覆盖equals需要注意什么呢?
当类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖equals方法以实现期望的行为时,就需要覆盖equals方法。
上面通常属于“值类(value class)”的情形。值类仅仅是一个表示值的类,如Integer、Date、String等。程序员在利用equals方法来比较值对象的引用时,希望知道逻辑上的值是否相等,而并非比较是否指向同一个对象。为了满足程序员的要求,覆盖equals是很必须的,这样做也使得这个类的实例可以用做映射表(map)的键(key),或者集合(set)的元素,使映射表或map中表现出预期的行为,如不存放具有相同值。
但是Java中有一种值类却不在这一要求之中,那就是枚举。在覆盖equals方法时,必须遵循以下的通用约定:
- l 自反性(reflexive):对于任何非null的引用值x,x.equals(x)必须返回true。
- l 对称性(symmetric):对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
- l 传递性(transitive):对于任何非null的引用值x,y和z,如果x.equals(y)返回true,且y.equals(z)返回true,那么x.equals(z)也必须返回true。
- l 一致性(consistent):对于任何非null的引用值x和y,只有equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)总会一致地返回相同的结果。
- l 非空性(Non-nullity):对于任何非null的引用值x,x.equals(null)必须返回false。
这些约定看起来很简单,但是在实际编程中很容易违反这些约定,一旦违反这些约定,程序运行就会不正常,接下来通过例子展示违反约定的情况以及如何避免这些错误。
自反性
自反性:如果我们书写的equals方法形式的功能不是用于判断自己的对象是否等于自身(或是自身包含的值),那么该会是多么可怕的事情呢?如List中存放实例,然后List判断contains()该对象时,那么就会永远都会返回false;通过代码来验证一下:
package cn.object.override.equals;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class EqualsDemo { @Test
public void test(){
EqualsDemo demo1 = new EqualsDemo();
EqualsDemo demo2 = new EqualsDemo();
List<EqualsDemo> list = new ArrayList<>();
list.add(demo1);
System.out.println("list是否包含demo1:"+list.contains(demo1));
System.out.println("list是否包含demo2:"+list.contains(demo2));
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
return false;
}
}
我们通过ArrayList的底层代码知道contains是通过调用当前类的indexOf方法来确定的,而indexOf是通过循环遍历判断参数和集合中的元素是否相等,而这个判断是通过实例的equals方法来确定的,所以上面的两个判断都是false也就可以理解了,这就是equals不正确覆盖带来的严重后果了;
对称性
对称性也是很重要的一个要求,并且逻辑思维也应该是这样的,那么如果我们不正常的覆盖,导致情况不是这样的会怎么样呢?通过一个代码来实现;
package cn.object.override.equals;
import org.junit.Test;
/**
* @author YorickYou
* Code Address:https://github.com/YorickYou/JavaSE.git
*/
public class CaseInsensitiveString {
private final String s;
//有参构造
public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s);
if (obj instanceof String)
return s.equalsIgnoreCase((String)obj);
return false;
}
public static void main(String[] args) {
CaseInsensitiveString string1 = new CaseInsensitiveString("abc");
String str = "abc";
//两次调用equals方法,前者是调用的CaseInsensitiveString的equals方法,后者是String的equals方法
System.out.println(string1.equals(str)+"自反性"+str.equals(string1));
}
}
上面对equals方法的覆盖就严重违反了对称性.如果我们往集合中添加该对象,那么此时会出现什么情况呢?
public static void test1(){
List<CaseInsensitiveString> list = new ArrayList<CaseInsensitiveString>();
CaseInsensitiveString string1 = new CaseInsensitiveString("abc");
String str = "abc";
list.add(string1);
System.out.println(string1.equals(str)+"--"+list.contains(str)+"--"+list.contains(string1));
}//true--false--true
所以在覆盖equals方法时,我们一定要注意对称性,对于上面问题的解决只需要将String的互操作这一段从equals中移除即可.
package cn.object.override.equals; import java.util.ArrayList;
import java.util.List; import org.junit.Test; /**
*
* @author YorickYou
* Code Address:https://github.com/YorickYou/JavaSE.git
*/
public class CaseInsensitiveString {
private final String s;
//有参构造
public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
}
public static void main(String[] args) {
CaseInsensitiveString string1 = new CaseInsensitiveString("abc");
String str = "abc";
//两次调用equals方法,前者是调用的CaseInsensitiveString的equals方法,后者是String的equals方法
System.out.println(string1.equals(str)+"自反性"+str.equals(string1));
test1();
} @Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
return obj instanceof CaseInsensitiveString && ((CaseInsensitiveString)obj).s.equalsIgnoreCase(s);
}
public static void test1(){
List<CaseInsensitiveString> list = new ArrayList<CaseInsensitiveString>();
CaseInsensitiveString string1 = new CaseInsensitiveString("abc");
String str = "abc";
list.add(string1);
System.out.println(string1.equals(str)+"--"+list.contains(str)+"--"+list.contains(string1));
}
}
重写equals方法(未完)的更多相关文章
- 为什么重写equals方法还要重写hashcode方法?
我们都知道Java语言是完全面向对象的,在java中,所有的对象都是继承于Object类.Ojbect类中有两个方法equals.hashCode,这两个方法都是用来比较两个对象是否相等的. 在未重写 ...
- 为什么要重写equals()方法与hashCode()方法
在java中,所有的对象都是继承于Object类.Ojbect类中有两个方法equals.hashCode,这两个方法都是用来比较两个对象是否相等的. 在未重写equals方法我们是继承了object ...
- Effective Java 第三版——10. 重写equals方法时遵守通用约定
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——11. 重写equals方法时同时也要重写hashcode方法
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- AbstractList 重写 equals() 方法
题目内容 题目内容很简单,就是创建 ArrayList 和 Vector 集合,向两者添加相同内容的字符串,最后用 equals() 方法比较是否相等. 这里就考察了 "==" 和 ...
- 重写equals方法需要知道的事
重写equals方法 相信在每个人都有过重写过java的equals的方法的经历.这篇博文就从以下几个方面说明重写equals方法的原由,与君共进步. 一 为什么要重写equals方法 首先我们了解e ...
- java重写equals方法
@Override public int hashCode() { return task.getId(); } @Override public boolean equals(Object obj) ...
- 重写equals()方法时,需要同时重写hashCode()方法
package com.wangzhu.map; import java.util.HashMap; /** * hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,<br/&g ...
- C#中在比较自定义对象的时候要重写Equals方法
using System;using System.Collections.Generic;using System.Text; namespace Equal{ using System; c ...
随机推荐
- 【Elasticsearch】集群管理
8.1 Elasticsearch时光机 Elasticsearch的快照,防止出错,灾备8.1.1 创建快照存储库创建快照之前必须建一个存储库,有如下几个方面,name,type,settings, ...
- 使用mini-define实现前端代码的模块化管理
这篇文章主要介绍了使用mini-define实现前端代码的模块化管理,十分不错的一篇文章,这里推荐给有需要的小伙伴. mini-define 依据require实现的简易的前端模块化框架.如果你不想花 ...
- 2017年10月9日 冒泡&去重复习
今天看了一下,就是数组跟js还是不太熟悉 冒泡排序 var arr = [4, 2, 1, 3, 6, 5]; for(var i = 1; i < arr.length; ...
- jQuery Ajax(异步改同步)
在实际使用中,我们经常会用的Ajax(异步加载,在不刷新整个网页的前提下对网页部分内容进行更新) 使用时,偶尔会遇上需要从一个接口中得到一个数组和数据对应的id,在另一个接口上再得到数据,最初写法如下 ...
- css随堂笔记(三)
Css随堂笔记(三) 1 关于背景图片 A 设置背景图片:background-image:url(“图片的路径”): B 背景图片位置: background-position:1 方位名词 ...
- Word中划线的方法(五种)
Word中划线的方法(五种): 1. 按CTRL+F9,在出现的黑底花括号内,如图输入内容, 最后按SHIFT+F9(或者右键菜单点切换域代码),以后可以反复按ALT+F9在代码与结果之间切换. 注: ...
- yanxin8文章归档
文章归档 - 2015年四月 (共21篇文章) 26日: 14443协议的CRC_A和CRC_B (0条评论) 25日: 百度钱包-1分钱5元话费 (0条评论) 22日: 驾照考试总结 (0条评论) ...
- linux常用指令集-持续更新...
0.查看所有java进程GC情况:for i in `jps|egrep -v "Jps|Launcher" |cut -d" " -f1`;do pwdx $ ...
- 一个.java源文件中可以有多个类吗?(内部类除外)有什么条件?
一个.java源文件中可以有多个类吗?(内部类除外)有什么条件?带着这个疑惑,动手建几个测试类, 揭开心中的疑惑.以下是解开疑惑过程: package test;/** * 一个.java源文件中可以 ...
- Android应用开发基础之三:数据存储和界面展现(三)
生成XML文件备份短信 创建几个虚拟的短信对象,存在list中 备份数据通常都是备份至sd卡 使用StringBuffer拼接字符串 把整个xml文件所有节点append到sb对象里 sb.appen ...