本文是我们名为“ 高级Java ”的学院课程的一部分。
本课程旨在帮助您最有效地使用Java。 它讨论了高级主题,包括对象创建,并发,序列化,反射等。 它将指导您完成Java掌握的旅程! 在这里查看 !
目录
- 1.简介 2.方法equals和hashCode 3.方法toString 4.方法克隆 5.方法等于和==运算符 6.有用的助手类 7.下载源代码 8.接下来
1.简介
从本教程的第1部分“ 如何创建和销毁对象”中 ,我们已经知道Java是一种面向对象的语言(但是,不是纯粹的面向对象的语言)。 在Java类层次结构的顶部是Object
类,Java中的每个类都隐式地继承自它。 因此,所有类都继承Object
类中声明的方法集,最重要的是以下方法:
方法 | 描述 |
protected Object clone() | 创建并返回此对象的副本。 |
protected void finalize() | 当垃圾回收确定不再有对该对象的引用时,由垃圾回收器在对象上调用。 我们已经在本教程的第1部分“ 如何创建和销毁对象”中讨论了终结器。 |
boolean equals(Object obj) | 指示其他某个对象是否与此对象“相等”。 |
int hashCode() | 返回对象的哈希码值。 |
String toString() | 返回对象的字符串表示形式。 |
void notify() | 唤醒正在此对象的监视器上等待的单个线程。 我们将在本教程的第9部分 并发最佳实践中讨论此方法。 |
void notifyAll() | 唤醒正在此对象的监视器上等待的所有线程。 我们将在本教程的第9部分 并发最佳实践中讨论此方法。 |
void wait() | 使当前线程等待,直到另一个线程为此对象调用notify() 方法或notifyAll() 方法。 我们将在本教程的第9部分 并发最佳实践中讨论这些方法。 |
表格1
在本教程的这一部分中,我们将介绍equals
, hashCode
, toString
和clone
方法,它们的用法以及需要牢记的重要约束。
2.方法equals和hashCode
默认情况下,Java中的任何两个对象引用(或类实例引用)仅在它们引用相同的内存位置时才相等(引用相等)。 但是Java允许类通过重写Object
类的equals()
方法来定义自己的相等性规则。 听起来像一个强大的概念,但是正确的equals()
方法实现应符合一组规则并满足以下约束:
- 反身的 。 对象x必须等于自身,并且equals(x)必须返回true 。
- 对称的 。 如果equals(y)返回true,则y.equals(x)也必须返回true 。
- 传递的 。 如果equals(y)返回true,而y.equals(z)返回true ,则x.equals(z)也必须返回true 。
- 一致 。 除非修改用于相等比较的任何属性,否则多次调用equals()方法必须得到相同的值。
- 等于Null 。 equals(null)的结果必须始终为false 。
不幸的是,Java编译器无法在编译过程中强制执行这些约束。 但是,不遵循这些规则可能会导致非常怪异且难以解决问题。 一般建议是这样的:如果您打算编写自己的equals()
方法实现,请在实际需要时三思而后行。 现在,有了所有这些规则,让我们为Person
类编写一个equals()
方法的简单实现。
package com.javacodegeeks.advanced.objects;public class Person {private final String firstName;private final String lastName;private final String email;public Person( final String firstName, final String lastName, final String email ) {this.firstName = firstName;this.lastName = lastName;this.email = email;}public String getEmail() {return email;}public String getFirstName() {return firstName;}public String getLastName() {return lastName;}// Step 0: Please add the @Override annotation, it will ensure that your// intention is to change the default implementation.@Overridepublic boolean equals( Object obj ) {// Step 1: Check if the 'obj' is nullif ( obj == null ) {return false;}// Step 2: Check if the 'obj' is pointing to the this instanceif ( this == obj ) {return true;}// Step 3: Check classes equality. Note of caution here: please do not use the // 'instanceof' operator unless class is declared as final. It may cause // an issues within class hierarchies.if ( getClass() != obj.getClass() ) {return false;}// Step 4: Check individual fields equalityfinal Person other = (Person) obj;if ( email == null ) {if ( other.email != null ) {return false;} } else if( !email.equals( other.email ) ) {return false;}if ( firstName == null ) {if ( other.firstName != null ) {return false;} } else if ( !firstName.equals( other.firstName ) ) {return false;}if ( lastName == null ) {if ( other.lastName != null ) {return false;}} else if ( !lastName.equals( other.lastName ) ) {return false;}return true;}
}
并非偶然的是,本节的标题中还包含hashCode()
方法。 最后要记住的规则是:每当您重写equals()
方法时,也始终要重写hashCode()
方法。 如果对于任何两个对象, equals()
方法返回true ,则这两个对象中的每个对象的hashCode()
方法必须返回相同的整数值(但是相反的语句不那么严格:如果对于任何两个对象, equals()
方法返回false ,则这两个对象中的每个对象的hashCode()
方法都可能会或可能不会返回相同的整数值)。 让我们看一下Person
类的hashCode()
方法。
// Please add the @Override annotation, it will ensure that your
// intention is to change the default implementation.
@Override
public int hashCode() {final int prime = 31;int result = 1;result = prime * result + ( ( email == null ) ? 0 : email.hashCode() );result = prime * result + ( ( firstName == null ) ? 0 : firstName.hashCode() );result = prime * result + ( ( lastName == null ) ? 0 : lastName.hashCode() );return result;
}
为了避免意外,请尽可能在实现equals()
和hashCode()
同时尝试使用final
字段。 这将确保这些方法的行为不会受到字段更改的影响(但是,在实际项目中,并非总是可能的)。
最后,始终确保在equals()
和hashCode()
方法的实现中使用相同的字段。 如果有任何变化影响所讨论的字段,它将保证两种方法的行为一致。
3.方法toString
toString()
可以说是其他方法中最有趣的方法,并且被更频繁地覆盖。 它的目的是提供对象(类实例)的字符串表示形式。 正确编写的toString()
方法可以大大简化实际系统中问题的调试和故障排除。
默认的toString()
实现在大多数情况下不是很有用,只是返回完整的类名和对象哈希码,以@
,fe分隔:
com.javacodegeeks.advanced.objects.Person@6104e2ee
让我们尝试改善实现,并为我们的Person类示例重写toString()
方法。 这是使toString()
更有用的一种方法。
// Please add the @Override annotation, it will ensure that your
// intention is to change the default implementation.
@Override
public String toString() {return String.format( "%s[email=%s, first name=%s, last name=%s]", getClass().getSimpleName(), email, firstName, lastName );
}
现在, toString()
方法提供了Person
类实例的字符串版本,包括其所有字段。 例如,在执行以下代码片段时:
final Person person = new Person( "John", "Smith", "john.smith@domain.com" );
System.out.println( person.toString() );
以下输出将在控制台中打印出:
Person[email=john.smith@domain.com, first name=John, last name=Smith]
不幸的是,标准Java库对简化toString()
方法实现的支持有限,特别是,最有用的方法是Objects.toString()
, Arrays.toString() / Arrays.deepToString()
。 让我们看一下Office
类及其可能的toString()
实现。
package com.javacodegeeks.advanced.objects;import java.util.Arrays;public class Office {private Person[] persons;public Office( Person ... persons ) {this.persons = Arrays.copyOf( persons, persons.length );}@Overridepublic String toString() {return String.format( "%s{persons=%s}", getClass().getSimpleName(), Arrays.toString( persons ) );}public Person[] getPersons() {return persons;}
}
以下输出将在控制台中打印出来(如我们所见, Person
类实例也已正确转换为字符串):
Office{persons=[Person[email=john.smith@domain.com, first name=John, last name=Smith]]}
Java社区已经开发了几个非常全面的库,这些库在很大程度上toString()
实现的工作。 其中包括Google Guava's
Objects.toStringHelper和Apache Commons Lang
ToStringBuilder 。
4.方法克隆
如果Java中有一种声誉不佳的方法,则肯定是clone()
。 它的目的非常明确–返回正在调用的类实例的确切副本,但是有很多原因使它不像听起来那样简单。
首先,如果您决定实现自己的clone()
方法,则可以遵循Java文档中规定的许多约定。 其次,该方法在Object
类中声明为protected
,因此为了使其可见,应使用重写类本身的返回类型将其重写为public
。 第三,覆盖的类应实现Cloneable
接口(这只是一个未定义方法的标记或mixin接口),否则将引发CloneNotSupportedException
异常。 最后,实现应首先调用super.clone()
,然后在需要时执行其他操作。 让我们看看如何为示例Person
类实现它。
public class Person implements Cloneable {// Please add the @Override annotation, it will ensure that your// intention is to change the default implementation.@Overridepublic Person clone() throws CloneNotSupportedException {return ( Person )super.clone();}
}
该实现看起来非常简单明了,那么这里可能出什么毛病? 实际上,有两件事。 在执行类实例的克隆时,不会调用任何类构造函数。 这种行为的结果是可能会出现意外的数据共享。 让我们考虑上一节介绍的Office
类的以下示例:
package com.javacodegeeks.advanced.objects;import java.util.Arrays;public class Office implements Cloneable {private Person[] persons;public Office( Person ... persons ) {this.persons = Arrays.copyOf( persons, persons.length );}@Overridepublic Office clone() throws CloneNotSupportedException {return ( Office )super.clone();}public Person[] getPersons() {return persons;}
}
在此实现中, Office
类实例的所有克隆都将共享同一个人数组,这不太可能实现所需的行为。 为了使clone()
实现能够做正确的事情,应该做一些工作。
@Override
public Office clone() throws CloneNotSupportedException {final Office clone = ( Office )super.clone();clone.persons = persons.clone();return clone;
}
现在看起来更好,但是即使此实现也非常脆弱,因为将人员字段定为final
将导致相同的数据共享问题(因为不能重新分配final
)。
总的来说,如果您想精确复制类,最好避免使用clone()
/ Cloneable
并使用更简单的替代方法(例如,复制构造函数,具有C ++背景的开发人员熟悉的概念或工厂)方法,这是我们在本教程的第1部分“ 如何创建和销毁对象”中讨论的一种有用的构造模式。
5.方法等于和==运算符
Java ==
运算符和equals()方法之间存在有趣的关系,这会引起很多问题和混乱。 在大多数情况下(比较原始类型除外), ==运算符执行引用相等:如果两个引用都指向同一个对象,则返回true,否则返回false 。 让我们看一个说明差异的简单示例:
final String str1 = new String( "bbb" );
System.out.println( "Using == operator: " + ( str1 == "bbb" ) );
System.out.println( "Using equals() method: " + str1.equals( "bbb" ) );
从人类的角度来看,str1 ==“ bbb”和str1.equals(“ bbb”)之间没有区别:在两种情况下,结果都应该相同,因为str1只是对“ bbb”字符串的引用。 但是在Java中并非如此:
Using == operator: false
Using equals() method: true
即使两个字符串看起来完全相同,在此特定示例中,它们也作为两个不同的字符串实例存在。 根据经验,如果您处理对象引用,请始终使用equals()
或Objects.equals()
(请参阅下一部分有用的帮助程序类以获取更多详细信息)进行相等性比较,除非您确实有比较的意图如果对象引用指向同一实例。
6.有用的助手类
自Java 7发行以来,标准Java库附带了几个非常有用的帮助程序类。 其中之一是Objects
类。 特别地,以下三种方法可以大大简化您自己的equals()
和hashCode()
方法的实现。
方法 | 描述 |
static boolean equals(Object a, Object b) | 如果参数彼此相等,则返回true,否则返回false 。 |
static int hash(Object... values) | 为一系列输入值生成哈希码。 |
static int hashCode(Object o) | 返回非空参数的哈希码,对于空参数返回0。 |
表2
如果我们使用这些辅助方法为Person
的类示例重写equals()
和hashCode()
方法,则代码量将大大减少,并且代码的可读性也将大大提高。
@Override
public boolean equals( Object obj ) {if ( obj == null ) {return false;}if ( this == obj ) {return true;}if ( getClass() != obj.getClass() ) {return false;}final PersonObjects other = (PersonObjects) obj;if( !Objects.equals( email, other.email ) ) {return false;} else if( !Objects.equals( firstName, other.firstName ) ) {return false; } else if( !Objects.equals( lastName, other.lastName ) ) {return false; }return true;
}@Override
public int hashCode() {return Objects.hash( email, firstName, lastName );
}
7.下载源代码
- 您可以在此处下载源代码: advanced-java-part-2
8.接下来
在本节中,我们介绍了Object
类,它是Java中面向对象编程的基础。 我们已经看到了每个类如何重写从Object
类继承的方法并强加其自己的相等性规则。 在下一节中,我们将转换编码方式,并讨论如何正确设计类和接口。
翻译自: https://www.javacodegeeks.com/2015/09/using-methods-common-to-all-objects.html