Java中的Object类和Objects类在Java类库中扮演着不同的角色,它们之间存在明显的区别。
Object类
基础与根源:
Object类是Java类层次结构的根类。这意味着Java中的每一个类(除了Object类本身)都直接或间接地继承自Object类。Object类位于java.lang包中,这个包是Java的核心包之一,自动被所有的Java程序所导入。方法与属性:
Object类提供了一系列的方法,如equals(Object obj), toString(), hashCode(), clone(), finalize(), getClass(), notify(), notifyAll(), wait(), wait(long timeout), wait(long timeout, int nanos)等。Object类没有显式声明的属性(字段),因为它主要是作为一个基类来提供通用的方法。功能:
Object类的方法为所有Java对象提供了基本的行为。例如,equals()用于比较两个对象是否相等,toString()用于返回对象的字符串表示,hashCode()用于生成对象的哈希码等。Objects类
工具类:
Objects类是一个工具类,位于java.util包中。它提供了一系列静态方法,用于处理对象,特别是为了避免空指针异常(NullPointerException)和提高代码的可读性。Objects类自Java 7引入,是JDK的一部分。方法与功能:
Objects类提供了如isNull(Object obj), nonNull(Object obj), requireNonNull(Object obj), requireNonNull(Object obj, String message), equals(Object a, Object b), deepEquals(Object a, Object b), hashCode(Object o), toString(Object o), toString(Object o, String nullDefault)等方法。这些方法大多是null安全的,即它们能够优雅地处理null值,避免在比较或调用方法时抛出空指针异常。与Object类的区别:
位置与目的:Object类是类层次结构的根,提供基本的行为;而Objects类是一个工具类,提供处理对象的实用方法。方法与实现:Object类的方法通常是实例方法,需要在对象上调用;而Objects类的方法全部是静态的,可以直接通过类名调用。先介绍一下 object类常见方法:
1.toString(Object o)
Objects.toString(Object o)方法用于生成对象的字符串表示。如果对象不为null,则调用对象的toString方法;如果对象为null,则返回字符串"null"。这个方法的好处在于它可以安全地处理null值,避免了在调用toString方法前进行null检查的需要。
示例代码:
import java.util.Objects;
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
// 使用Objects.toString来安全地生成字符串表示
return "Person{" +
"name='" + Objects.toString(name) + '\'' +
'}';
}
public static void main(String[] args) {
Person person = new Person("Alice");
System.out.println(person); // 输出: Person{name='Alice'}
Person nullPerson = null;
System.out.println(Objects.toString(nullPerson)); // 输出: null
}
}
2. equals(Object obj) 方法
equals(Object obj) 方法用于比较两个对象是否相等。在Object类中,equals()方法默认的实现是比较两个对象的引用是否指向内存中的同一个位置(即是否是同一个对象)。但是,在自定义类中,通常会根据对象的实际内容来重写equals()方法。
在Java中,重写equals方法以比较两个对象的内容是否相同是一个常见的需求。当你定义了一个类,并且希望基于类的某些字段(或全部字段)来判断两个对象是否相等时,就需要重写equals方法。同时,为了保持hashCode方法的一致性约定,通常也需要重写hashCode方法。
下面是一个基于自定义类Person的例子,该类包含name和age两个字段,并且重写了equals和hashCode方法以基于这两个字段的内容来判断对象是否相等:
import java.util.Objects;
public class Person {
private String name;
private int age;
// 构造函数
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getter 和 Setter 省略
// 重写 equals 方法
@Override
public boolean equals(Object obj) {
// 检查是否为同一个对象的引用
if (this == obj) return true;
// 检查是否为 null 或者类型是否相同
if (obj == null || getClass() != obj.getClass()) return false;
// 类型转换
Person person = (Person) obj;
// 比较字段
return age == person.age &&
Objects.equals(name, person.name);
}
// 重写 hashCode 方法
@Override
public int hashCode() {
return Objects.hash(name, age);
}
// toString 方法,方便输出查看
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 主方法,用于测试
public static void main(String[] args) {
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);
Person p3 = p1; // 同一个引用
System.out.println(p1.equals(p2)); // 输出 true
System.out.println(p1.equals(p3)); // 输出 true
System.out.println(p1.equals(null)); // 输出 false,因为null不是Person类型
System.out.println(p1.hashCode() == p2.hashCode()); // 输出 true,因为内容相同
// 使用 HashSet 测试
HashSet
set.add(p1);
set.add(p2); // 这行不会添加新的元素,因为 p1 和 p2 内容相同
System.out.println(set.size()); // 输出 1
}
}
// 注意:需要导入 HashSet
// import java.util.HashSet;
在这个例子中,equals方法首先检查调用它的对象(this)和参数对象(obj)是否是同一个对象引用(通过==)。然后,它检查参数对象是否为null或者是否属于与调用对象不同的类(通过getClass()方法)。接下来,它将参数对象强制转换为正确的类型(在这个例子中是Person),并比较两个对象的字段值。
hashCode方法使用Objects.hash方法来生成一个基于name和age的哈希码,这有助于在基于哈希的集合中保持equals和hashCode的一致性。
最后,main方法中的测试代码演示了如何使用重写后的equals和hashCode方法,并展示了它们如何影响基于哈希的集合(如HashSet)的行为。
有人会问为什么要重写 hashCode 方法?
在Java中,虽然从技术上讲你可以只重写equals方法而不重写hashCode方法,但在实践中这通常是不推荐的,原因与Java集合框架(Collections Framework)的某些类(特别是基于哈希的类,如HashSet、HashMap、Hashtable和LinkedHashSet等)的行为密切相关。
这些基于哈希的集合类使用哈希码(hash code)来优化查找、插入和删除操作的速度。它们通过调用对象的hashCode方法来获取哈希码,并使用这个哈希码来快速定位元素在集合中的存储位置(即“桶”或“槽”)。
当你重写equals方法时,你实际上是在改变对象相等性的判断逻辑。如果两个对象通过equals方法被认为是相等的,那么它们必须产生相同的哈希码(即hashCode方法的返回值必须相同),这是hashCode方法的一般约定(contract)所要求的。
如果你只重写了equals方法而没有重写hashCode方法,那么可能会出现以下情况:
违反hashCode的一般约定:如果两个相等的对象(即equals方法返回true的对象)没有相同的哈希码,那么这违反了hashCode方法的一般约定。
破坏基于哈希的集合的行为:在基于哈希的集合中,如果两个相等的对象没有相同的哈希码,那么这些集合可能无法正确地存储、检索或删除这些对象。例如,在HashSet中,即使两个对象通过equals方法比较是相等的,如果它们的哈希码不同,它们也可能被存储在不同的位置,导致HashSet错误地认为它们是不同的对象。
导致性能问题:即使基于哈希的集合在内部能够处理哈希冲突(即不同对象具有相同哈希码的情况),但如果相等的对象没有相同的哈希码,那么这种冲突可能会更加频繁,从而降低集合的性能。
因此,当你重写equals方法时,通常也应该重写hashCode方法,以确保相等的对象具有相同的哈希码,从而维护hashCode方法的一般约定,并避免在基于哈希的集合中出现问题。这是Java编程中的一个重要实践。
在IntelliJ IDEA(简称IDEA)中,快捷键Alt+Insert。可以选择相应的内容直接简写。
或右键选择generate。
3. clone() 方法
clone() 方法用于创建并返回当前对象的一个副本。但是,Object类中的clone()方法是受保护的,并且是一个本地方法,这意味着它依赖于具体实现(通常是JVM)。要在自定义类中使用clone()方法,你需要:
实现Cloneable接口(这是一个标记接口,不包含任何方法,但它指示Object的clone()方法可以被该类的对象调用)。在你的类中重写clone()方法,并调用super.clone()。public class Person implements Cloneable {
// 成员变量、构造函数、getter和setter略
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone(); // 调用Object类的clone()方法
}
public static void main(String[] args) throws CloneNotSupportedException {
Person original = new Person();
Person copy = original.clone();
// 注意:这里只是浅拷贝,如果Person类包含对其他可变对象的引用,则这些引用会被共享
}
}
请注意,上述clone()方法的实现是浅拷贝(shallow copy)。如果你需要深拷贝(deep copy),你需要手动实现深拷贝的逻辑,包括递归地拷贝所有引用到的可变对象。
再介绍一下 objects类常见方法:
1.Objects.equals(Object a, Object b)
在Java的Objects类中,equals方法是一个静态方法,用于比较两个对象是否相等。这个方法特别有用,因为它提供了一种类型安全的方式来比较两个对象,同时避免了直接调用null.equals(Object)时可能发生的NullPointerException。
Objects.equals(Object a, Object b)方法的工作原理如下:
如果两个参数都是null,则方法返回true。如果其中一个参数是null而另一个不是,则方法返回false。否则,它返回a.equals(b)的结果。这里是一个使用Objects.equals方法的代码示例:
import java.util.Objects;
public class ObjectsEqualsExample {
public static void main(String[] args) {
// 测试两个null对象
String str1 = null;
String str2 = null;
System.out.println(Objects.equals(str1, str2)); // 输出: true
// 测试一个null对象和一个非null对象
String str3 = "Hello";
System.out.println(Objects.equals(str1, str3)); // 输出: false
// 测试两个相同的非null对象
String str4 = "Hello";
System.out.println(Objects.equals(str3, str4)); // 输出: true
// 测试两个不同的非null对象
String str5 = "World";
System.out.println(Objects.equals(str3, str5)); // 输出: false
// 示例:在自定义对象中使用Objects.equals进行比较
Person person1 = new Person("Alice", 30);
Person person2 = new Person("Alice", 30);
// 注意:这里的比较是基于引用,而不是内容。要比较内容,需要重写Person类的equals方法。
// 但为了演示Objects.equals的用法,我们假设这里只比较引用。
System.out.println(Objects.equals(person1, person2)); // 输出: false,因为person1和person2引用不同的对象
// 如果Person类重写了equals方法以比较内容,则应该使用person1.equals(person2)或Objects.equals(person1, person2)
// 来获得基于内容的比较结果。
}
// 一个简单的Person类示例
static class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 这里应该重写equals和hashCode方法,但为了简单起见,我们省略了它们。
}
}
请注意,在上面的示例中,Person类没有重写equals方法,因此使用Objects.equals(person1, person2)将只比较两个Person对象的引用是否相同,而不是它们的内容。在实际应用中,如果你想要基于对象的内容来比较两个对象是否相等,你应该在类中重写equals方法(以及hashCode方法以保持一致性)。
然而,即使你没有重写equals方法,Objects.equals仍然是一个有用的工具,因为它可以避免在直接调用null.equals(Object)时可能发生的NullPointerException。
注意:
public class Test1 {
public static void main(String[] args) {
String s1=null;
String s2="xuboyuan";
// System.out.println(s1.equals(s2));报错
System.out.println(Objects.equals(s1,s2));//false
}
}
在Java中,当你尝试调用一个null对象的任何方法时,包括equals()方法,都会抛出NullPointerException。这是因为null并不指向任何有效的对象实例,因此没有可以调用的方法。
在你的第一个示例中:
这行代码尝试调用s1的equals()方法来与s2进行比较。但是,由于s1是null,没有对象实例可以调用equals()方法,因此Java运行时环境会抛出一个NullPointerException。
相反,在你的第二个示例中:
这里你使用了java.util.Objects类的equals()静态方法。这个方法是专门为处理可能为null的对象而设计的,它首先检查两个参数是否都为null,如果都是null则返回true;如果只有一个为null,则返回false;如果两个都不是null,则调用第一个参数的equals()方法与第二个参数进行比较。由于s1是null而s2不是,所以Objects.equals(s1, s2)返回false,而不会抛出NullPointerException。
这就是为什么第一个示例会报错而第二个示例不会的原因。Objects.equals()方法提供了一种安全的方式来比较可能为null的对象,避免了直接调用null对象的equals()方法时可能发生的异常。
2.isNull(Object obj)
检查对象是否为 null。
Object obj = null;
boolean isNull = Objects.isNull(obj); // 返回true
3.nonNull(Object obj)
检查对象是否不为 null。
Object obj = "Hello";
boolean nonNull = Objects.nonNull(obj); // 返回true