使用所有对象通用的方法

本文是我们名为“ 高级Java ”的学院课程的一部分。

本课程旨在帮助您最有效地使用Java。 它讨论了高级主题,包括对象创建,并发,序列化,反射等。 它将指导您完成Java掌握的过程! 在这里查看

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()

void wait(long timeout)

void wait(long timeout, int nanos)

使当前线程等待,直到另一个线程为此对象调用notify()方法或notifyAll()方法。 我们将在本教程的第9部分 并发最佳实践中讨论这些方法。

表格1

在本教程的这一部分中,我们将研究equalshashCodetoStringclone方法,它们的用法以及需要牢记的重要约束。

2.方法等于和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()方法必须得到相同的值。
  • 等于Nullequals(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.
    @Override
    public boolean equals( Object obj ) {
        // Step 1: Check if the 'obj' is null
        if ( obj == null ) {
            return false;
        }
        
        // Step 2: Check if the 'obj' is pointing to the this instance
        if ( 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 equality
        final 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 );
    }
    
    @Override
    public 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.toStringHelperApache 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.
    @Override
    public 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 );
    }

    @Override
    public 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.下载源代码

8.接下来

在本节中,我们介绍了Object类,它是Java中面向对象编程的基础。 我们已经看到了每个类如何覆盖从Object类继承的方法并强加其自己的相等性规则。 在下一节中,我们将切换编码方式,并讨论如何正确设计类和接口。

翻译自: https://www.javacodegeeks.com/2015/09/using-methods-common-to-all-objects.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值