Object和Objects

发布时间:2024年01月16日

其他常见API

5. Object

Object类所在包是java.lang包。Object 是类层次结构的根,每个类都可以将 Object 作为超类(父类)。

5.1 概述及功能

所有类都直接或者间接的继承自Object类。Object类定义了一些基本的方法,对于所有对象都是通用的。

以下是Object类的主要功能:

  1. equals()方法:Object类提供了用于比较对象是否相等的equals()方法。在默认情况下,equals()方法通过比较对象的引用来确定它们是否相等。但是,它可以被子类重写以提供自定义的相等性比较规则。

  2. hashCode()方法:Object类定义了hashCode()方法,用于返回对象的散列码(哈希码)。hashCode()方法经常与equals()方法一起使用,以确保相等的对象具有相等的哈希码。hashCode()方法也可以被子类重写以提供自定义的哈希码实现。

  3. toString()方法:Object类提供了toString()方法,用于返回对象的字符串表示。默认实现返回由类名、@符号和对象的散列码组成的字符串。可以通过在子类中重写toString()方法来提供更具信息性的字符串表示形式。

  4. getClass()方法:Object类的getClass()方法返回对象的运行时类。它返回一个Class对象,该对象包含有关类的元数据信息。

  5. 垃圾回收:Object类定义了finalize()方法,当垃圾回收器准备回收对象时,会调用该方法。finalize()方法允许对象在被销毁之前执行一些清理操作。

  6. wait()、notify()和notifyAll()方法:Object类提供了用于线程间通信的wait()、notify()和notifyAll()方法。这些方法可以与synchronized关键字一起使用,以实现多线程之间的同步和协作。

5.2 常用方法

下表列出了Object类的所有方法,并提供简单的说明和示例:

方法名说明示例
boolean equals(Object obj)比较对象是否相等。默认通过比较引用来判断,可以被子类重写。Integer x = 5; Integer y = 5; x.equals(y); // true
int hashCode()返回对象的哈希码。默认实现基于对象的内存地址,可以被子类重写。String str = "Hello"; str.hashCode(); // 69609650
Class<?> getClass()返回对象的运行时类。String str = "Hello"; str.getClass(); // class java.lang.String
String toString()返回对象的字符串表示。默认返回类名、@符号和散列码的组合,可以被子类重写。Integer num = 10; num.toString(); // "10"
void notify()唤醒在该对象上等待的单个线程。synchronized (obj) { obj.notify(); }
void notifyAll()唤醒在该对象上等待的所有线程。synchronized (obj) { obj.notifyAll(); }
void wait()使当前线程等待,直到该对象收到notify或notifyAll通知。synchronized (obj) { obj.wait(); }
void finalize()垃圾回收器准备回收对象时调用,允许对象进行一些清理操作。protected void finalize() throws Throwable {...}

这些方法为所有Java对象提供了通用的行为和功能。equals()hashCode()方法用于比较对象的相等性和生成唯一标识。toString()方法返回对象的字符串表示形式,方便输出调试信息。getClass()方法返回对象的运行时类,用于获取类的元数据信息。wait()notify()notifyAll()方法用于实现线程间通信和同步。finalize()方法在对象被垃圾回收前执行清理操作。

5.2.1 toString()方法的重写

重写toString()方法的格式为:

@Override
public String toString() {
    // 生成对象字符串表示的逻辑
}

重写toString()方法的原因是为了提供更有意义和可读性的对象字符串表示,方便调试和输出信息。

例如:假设我们有一个Student类,表示一个学生的信息,包含学生的姓名、年龄和学号属性。

  • 没有重写toString()方法的情况

    调用默认toString()方法返回的是对象的类名、@符号和散列码的组合。

    示例代码如下:

    public class Student {
        private String name;
        private int age;
        private int studentNumber;
    
        public Student(String name, int age, int studentNumber) {
            this.name = name;
            this.age = age;
            this.studentNumber = studentNumber;
        }
    
        // 没有重写toString()方法
    
        public static void main(String[] args) {
            Student student = new Student("Tom", 18, 12345);
            System.out.println(student.toString());
        }
    }
    

    输出结果:

    Student@<散列码>
    

    这个输出结果不够直观,无法直观地看到一个学生的具体信息。

  • 重写toString()方法的情况

    返回一个更有意义和可读性的对象字符串表示。

    示例代码如下:

    public class Student {
        private String name;
        private int age;
        private int studentNumber;
    
        public Student(String name, int age, int studentNumber) {
            this.name = name;
            this.age = age;
            this.studentNumber = studentNumber;
        }
    
        @Override
        public String toString() {
            return "Student [name=" + name + ", age=" + age + ", studentNumber=" + studentNumber + "]";
        }
    
        public static void main(String[] args) {
            Student student = new Student("Tom", 18, 12345);
            System.out.println(student.toString());
        }
    }
    

    输出结果:

    Student [name=Tom, age=18, studentNumber=12345]
    

    通过重写toString()方法,我们返回了一个包含学生姓名、年龄和学号的字符串,这样就更方便看到一个学生的具体信息。

5.2.2 equals方法的重写

重写equals()方法的格式为:

@Override
public boolean equals(Object obj) {
    // 判断传入的对象是否与当前对象相等的逻辑
}

重写equals()方法的原因是为了自定义对象之间的相等性判断逻辑,以便符合需求。

例如:假设我们有一个Person类表示一个人的信息,包含姓名和年龄属性。

  • 没有重写equals()方法的情况

    默认情况下,调用equals()方法会使用对象的引用地址进行比较,判断两个对象是否为同一个对象。

    示例代码如下:

    public class Person {
        private String name;
        private int age;
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        // 没有重写equals()方法
        
        public static void main(String[] args) {
            Person person1 = new Person("Alice", 25);
            Person person2 = new Person("Alice", 25);
    
            System.out.println(person1.equals(person2));        
        }
    }
    

    输出结果:

    false
    

在上述例子中,尽管person1person2的属性值相同,但由于默认的equals()方法比较的是对象的引用地址,所以返回的结果为false,不能正确判断对象之间的相等性。

  • 重写equals()方法的情况

    现在我们要重写equals()方法,自定义判断两个对象相等的条件。可以比较两个对象的属性值是否相等。

    示例代码如下:

    public class Person {
        private String name;
        private int age;
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            
            if (obj == null || getClass() != obj.getClass()) {
                return false;
            }
            
            Person person = (Person) obj;
            
            return age == person.age && Objects.equals(name, person.name);
        }
        
        public static void main(String[] args) {
            Person person1 = new Person("Alice", 25);
            Person person2 = new Person("Alice", 25);
    
            System.out.println(person1.equals(person2));        
        }
    }
    

    输出结果:

    true
    

    通过重写equals()方法,我们自定义了判断两个Person对象相等的条件,即姓名和年龄都相同。这样,当person1person2的属性值相同时,调用equals()方法返回的结果为true,能够正确判断对象之间的相等性。

5.2.3 对象克隆

对象克隆是指创建一个与原始对象具有相同状态和行为的新对象。

在Java中,对象克隆可以通过`Cloneable`接口和`clone()`方法实现。

在 Java 中,通过重写 clone() 方法来实现对象的克隆。clone() 方法是在 Object 类中定义的,因此所有的类都可以调用 clone() 方法进行克隆。

对象克隆可分为两种分类:浅克隆(Shallow Clone)和深克隆(Deep Clone)。

Object类默认的是浅克隆

克隆方法的格式如下:

protected Object clone() throws CloneNotSupportedException {
    // 实现克隆的逻辑
}

clone() 方法的访问修饰符是 protected,这是因为 clone() 方法被设计为在子类中进行重写,而且只能在类的内部或其子类中进行调用。

在实现 clone() 方法时,首先需要调用 super.clone() 方法获得一个浅拷贝的副本,然后可以对需要深拷贝的数据进行独立副本的创建,以确保复制的对象与原对象没有关联。

重写 clone() 方法时,还需要注意抛出 CloneNotSupportedException 异常,这是因为 Cloneable 接口是一个标记接口,对于没有实现 Cloneable 接口的对象调用 clone() 方法,会抛出该异常。

示例:

class MyClass implements Cloneable {
    private int number;

    public MyClass(int number) {
        this.number = number;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

上述示例是一个简单的自定义类 MyClass,它实现了 Cloneable 接口,并重写了 clone() 方法。在 clone() 方法中,我们只调用了 super.clone(),获得一个浅拷贝的副本。

注意】上述示例中的 clone() 方法并没有实现深拷贝的逻辑,如果需要进行深拷贝,需要根据具体的需求,在 clone() 方法中编写相应的代码逻辑进行深拷贝操作。

5.2.3.1 浅克隆(Shallow Clone)
  • 浅克隆是指在克隆过程中,只复制对象的基本类型属性和对其他对象的引用。新对象与原始对象共享引用类型属性,因此对引用类型属性的修改会影响到原始对象和克隆对象。

  • 不管对象内部的属性是基本数据类型还是引用数据类型,都完全拷贝过来

  • 基本数据类型拷贝过来的是具体的数据,引用数据类型拷贝过来的是地址值。

  • 浅克隆可以通过调用Object类的clone()方法来实现,前提是要实现Cloneable接口并覆盖clone()方法。

     示例代码如下:
    
    public class Person implements Cloneable {
        private String name;
        private int age;
        private Address address;
    
        public Person(String name, int age, Address address) {
            this.name = name;
            this.age = age;
            this.address = address;
        }
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    public class Address {
        private String city;
    
        public Address(String city) {
            this.city = city;
        }
    }
    
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("London");
        Person person1 = new Person("Alice", 25, address);
        Person person2 = (Person) person1.clone();
        
        // 修改 person2 的属性
        person2.setName("Bob");
        person2.getAddress().setCity("New York");
    
        System.out.println(person1.getName());                  // 输出 "Alice"
        System.out.println(person1.getAddress().getCity());     // 输出 "New York"
    }
    

    在上述代码中,Person类实现了Cloneable接口,并重写了clone()方法。在main方法中,创建了一个Person对象person1,并将其克隆给person2。然后,修改了person2的姓名和地址。

    由于浅克隆只复制引用,因此person1person2的地址引用指向相同的地址对象。因此,修改person2的地址会同时影响person1的地址。

5.2.3.2 深克隆(Deep Clone)
  • 深克隆是指在克隆过程中,除了复制对象的基本类型属性外,还要复制对象的引用类型属性,使得新对象与原始对象拥有各自独立的引用类型属性。

  • 可以实现 Cloneable 接口和重写 clone() 方法来实现深克隆。

  • 基本数据类型拷贝过来,字符串复用,引用数据类型会重新创建新的

  • 深克隆可以通过手动实现或使用第三方库(如Apache Commons Lang、Gson等)来实现。

    示例代码如下:

  • 通过手动实现深克隆

    下面是一个简单的 Java 示例:

    import java.util.ArrayList;
    import java.util.List;
    
    class Person implements Cloneable {
        private String name;
        private int age;
        private List<String> hobbies;
    
        public Person(String name, int age, List<String> hobbies) {
            this.name = name;
            this.age = age;
            this.hobbies = hobbies;
        }
    
        // 重写clone()方法实现深克隆
        @Override
        public Person clone() throws CloneNotSupportedException {
            // 先使用super.clone()创建浅克隆的对象
            Person clonedPerson = (Person) super.clone();
    
            // 对于要深克隆的引用类型(如List),进行独立副本的创建
            clonedPerson.hobbies = new ArrayList<>(this.hobbies);
    
            return clonedPerson;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", hobbies=" + hobbies +
                    '}';
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            List<String> hobbies = new ArrayList<>();
            hobbies.add("Reading");
            hobbies.add("Traveling");
    
            Person person1 = new Person("Alice", 25, hobbies);
    
            try {
                Person person2 = person1.clone();
    
                person2.setName("Bob");
                person2.setAge(30);
                person2.getHobbies().add("Swimming");
    
                System.out.println(person1);  // 输出: Person{name='Alice', age=25, hobbies=[Reading, Traveling]}
                System.out.println(person2);  // 输出: Person{name='Bob', age=30, hobbies=[Reading, Traveling, Swimming]}
    
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
        }
    }
    

    在上面的示例中,Person 类实现了 Cloneable 接口,并重写了 clone() 方法来实现深克隆。在 clone() 方法中,首先调用 super.clone() 方法创建一个浅克隆的对象,然后对于需要深克隆的引用类型(这里是 List),使用新的副本来替换原始对象的引用。这样可以确保克隆后的对象与原始对象没有任何关联。

    然后在 Main 类的 main 方法中,创建了一个 Person 对象 person1,通过调用 person1.clone() 方法创建了一个深克隆的对象 person2。然后对 person2 进行修改,发现对 person1 没有任何影响,证明了深克隆的效果。

    注意】在这个示例中,clone() 方法中的 throws CloneNotSupportedException 是必需的,因为 Cloneable 接口是一个标记接口,没有实际的方法,而是起到了一个标识作用,告诉编译器这个类可以进行克隆操作。

    • 使用Apache Commons Lang库
    import org.apache.commons.lang3.SerializationUtils;
    
    public static void main(String[] args) {
        Address address = new Address("London");
        Person person1 = new Person("Alice", 25, address);
        Person person2 = SerializationUtils.clone(person1);
        
        // 修改 person2 的属性
        person2.setName("Bob");
        person2.getAddress().setCity("New York");
    
        System.out.println(person1.getName());                  // 输出 "Alice"
        System.out.println(person1.getAddress().getCity());     // 输出 "London"
    }
    

    在上述代码中,person1通过Apache Commons Lang库的SerializationUtils.clone()方法来进行深克隆。person2person1的一个副本,但它与person1具有不同的地址引用。所以,修改person2不会影响person1

5.2.3.3 对象克隆的注意事项

对象克隆的的注意事项:

  1. 实现 Cloneable 接口:要实现对象克隆,类必须实现 Cloneable 接口。否则,在调用 clone() 方法时会抛出 CloneNotSupportedException 异常。

  2. 重写 clone() 方法:在类中重写 clone() 方法,并使用 super.clone() 获得浅拷贝。如果需要进行深拷贝,则还需要对引用类型的成员变量进行独立的拷贝操作。

  3. 深拷贝和浅拷贝:克隆有两种方式,一种是浅拷贝,另一种是深拷贝。浅拷贝只复制对象及其引用类型的成员变量的引用,而深拷贝会创建一个新对象,并将所有成员变量都复制一份。根据需求选择合适的拷贝方式。

  4. 对象引用问题:克隆对象后,克隆结果是否与原对象共享引用类型的成员变量,取决于对引用类型的处理。如果希望克隆结果与原对象完全独立,需要对引用类型成员进行深拷贝,确保新对象拥有自己的独立副本。

  5. 异常处理:在重写 clone() 方法时,需要注意抛出 CloneNotSupportedException 异常。这是因为 Cloneable 接口是一个标记接口,并没有提供具体的实现,只是作为一个标识来告诉 clone() 方法是否能够进行克隆操作。

  6. 构造函数调用:对象克隆并不会调用构造函数,它是通过 clone() 方法直接复制对象的内存状态。如果需要在克隆过程中进行一些其他的初始化操作,可以考虑使用其他方式,例如拷贝构造函数、工厂方法等。

  7. 性能考虑:克隆操作可能会涉及到大量的对象复制,特别是在深拷贝的情况下。因此,在进行对象克隆时,需要仔细考虑性能和内存使用情况,避免因为频繁的克隆操作导致性能下降。

5.2.4 clone()方法的重写

当需要在自定义的类中实现对象克隆时,应该重写 clone() 方法。

以下是重写 clone() 方法的格式:

@Override
public Object clone() throws CloneNotSupportedException {
    // 克隆对象的实现逻辑
    // 首先调用 super.clone() 方法以获得浅拷贝的对象
    // 然后对需要深拷贝的属性进行复制
    // 最后返回克隆后的对象
    // 注意要处理可能抛出的 CloneNotSupportedException 异常
}
  • 重写clone()方法之前
// 重写前
public class Person implements Cloneable {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 克隆方法
    public Object clone() throws CloneNotSupportedException {
        return super.clone(); // 默认的 clone() 方法只能浅拷贝对象
    }
}

public class Main {
    public static void main(String[] args) {
        Person person1 = new Person("Alice", 25);
        
        try {
            Person person2 = (Person) person1.clone();
            
            System.out.println(person1 == person2); // 输出:false
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

在上面的示例中,有一个 Person 类,它实现了 Cloneable 接口。Person 类重写了 clone() 方法,但方法内部调用了 super.clone(),默认的克隆方法只能进行浅拷贝,即复制对象的引用。在 main() 方法中,创建了一个 Person 对象 person1,然后通过调用 clone() 方法复制了它。

输出结果如下:

false

可以看到,person1person2 是两个不同的对象,它们的引用地址不同。

  • 重写clone()方法之后
// 重写后
public class Person implements Cloneable {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 重写 clone() 方法
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone(); // 自定义的 clone() 方法,进行深拷贝
    }
}

public class Main {
    public static void main(String[] args) {
        Person person1 = new Person("Alice", 25);
        
        try {
            Person person2 = person1.clone();
            
            System.out.println(person1 == person2); // 输出:false
            System.out.println(person1.getName() == person2.getName()); // 输出:true
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,Person 类重写了 clone() 方法,它使用强制类型转换来确保在复制过程中返回的是 Person 对象。而且,它在进行克隆时,进行了深拷贝,即复制了对象的所有属性。在 main() 方法中,创建了一个 Person 对象 person1,然后通过调用自定义的 clone() 方法进行复制。

输出结果如下:

false
true

可以看到,person1person2 是两个不同的对象,它们的引用地址不同。而且,person1person2name 属性引用同一个对象,说明进行了深拷贝。

5.3 Object的注意事项

Object 的注意事项:

  1. equals() 方法:Object 类中的 equals() 方法用于比较两个对象是否相等。默认情况下,equals() 方法比较的是对象的引用地址,而不是对象的内容。如果需要比较对象的内容,需要重写 equals() 方法。

  2. hashCode() 方法:Object 类中的 hashCode() 方法返回对象的哈希码。当需要将对象用作散列结构(例如散列表)中的键时,需要重写 hashCode() 方法来确保相等的对象具有相等的哈希码。

  3. toString() 方法:Object 类中的 toString() 方法返回对象的字符串表示。默认情况下,toString() 方法返回的是对象的类名,后跟 “@” 符号和对象的哈希码。可以重写 toString() 方法来返回自定义的字符串表示。

  4. getClass() 方法:Object 类中的 getClass() 方法返回对象所属的类。可以使用这个方法来获取对象的类名、包名等信息。

  5. finalize() 方法:Object 类中的 finalize() 方法是一个被垃圾回收器调用的方法,用于在对象被销毁之前执行清理操作。不建议在应用程序中重写 finalize() 方法,因为它的调用时机和频率不确定。

  6. clone() 方法:Object 类中的 clone() 方法用于创建对象的副本。默认情况下,clone() 方法执行的是浅拷贝,即复制对象的引用。如果需要进行深拷贝,需要在自定义类中重写 clone() 方法。

  7. wait()notify()notifyAll() 方法:Object 类中的这些方法用于线程间的通信和同步。wait() 方法使当前线程等待,notify() 方法唤醒等待的线程,而 notifyAll() 方法唤醒所有等待的线程。

6. Objects

Objects 是 Java 提供的一个实用类,所在包是在java.util包下,因此在使用的时候需要进行导包。并且Objects类是被final修饰的,因此该类不能被继承。

它提供了一些静态方法来操作对象,主要用于对象的比较、处理空值和生成对象的哈希码。

6.1 概述及功能

Objects类中无无参构造方法,因此不能使用new关键字去创建Objects的对象。同时Objects类中所提供的方法都是静态的。因此可以通过类名直接去调用这些方法。

以下是 Objects 类的一些常用方法及其功能概述:

  1. equals(Object a, Object b):比较两个对象是否相等。该方法处理了空值(null)的情况,当两个对象都为空或者相等时返回 true,否则返回 false

  2. hashCode(Object obj):生成对象的哈希码。该方法会根据对象的内容生成一个哈希码值,可用于在哈希表等数据结构中进行对象的存储和查找。

  3. toString(Object obj):将对象转换为字符串表示。如果对象为 null,则返回字符串 "null",否则调用对象的 toString() 方法。

  4. requireNonNull(T obj):检查指定的对象是否为 null,如果为 null,则抛出 NullPointerException 异常,否则返回该对象。

  5. requireNonNull(T obj, String message):功能同上,同时可以自定义异常消息。

  6. isNull(Object obj):检查指定的对象是否为 null,返回 truefalse,用于判断对象是否为空。

  7. nonNull(Object obj):检查指定的对象是否不为 null,返回 truefalse,用于判断对象是否非空。

  8. compare(T a, T b, Comparator<? super T> c):使用指定的比较器来比较两个对象。该方法允许使用自定义的比较器来对对象进行比较。

6.2 常用方法

方法说明示例
equals(Object a, Object b)比较两个对象是否相等。如果两个对象都为 null 或相等,则返回 true;否则返回 falseObjects.equals("abc", "abc")
hashCode(Object obj)生成对象的哈希码。如果对象为 null,则返回 0;否则调用对象的 hashCode() 方法Objects.hashCode("abc")
toString(Object obj)将对象转换为字符串表示。如果对象为 null,则返回字符串 "null",否则调用对象的 toString() 方法Objects.toString("abc")
requireNonNull(T obj)检查指定对象是否为 null,如果为 null,则抛出 NullPointerException 异常,否则返回该对象Objects.requireNonNull("abc")
requireNonNull(T obj, String message)检查指定对象是否为 null,如果为 null,则抛出 NullPointerException 异常,并可自定义异常消息Objects.requireNonNull("abc", "不能为空")
isNull(Object obj)检查指定对象是否为 null,返回 truefalseObjects.isNull("abc")
nonNull(Object obj)检查指定对象是否为非 null,返回 truefalseObjects.nonNull("abc")
compare(T a, T b, Comparator<? super T> c)使用指定的比较器来比较两个对象。Objects.compare("abc", "def", String.CASE_INSENSITIVE_ORDER)

6.3 注意事项

Objects 类的注意事项:

  1. 空值检查Objects 类提供了许多用于空值检查的方法,如 requireNonNullisNullnonNull。在使用这些方法时,要确保传入的对象不为 null,否则可能会抛出 NullPointerException 异常。

  2. 自定义对象的 equals() 方法Objects.equals(Object a, Object b) 方法用于比较两个对象是否相等。对于自定义的类,如果需要比较对象是否相等,需要重写该类的 equals() 方法,并在其中使用适当的逻辑进行比较。

  3. 哈希码生成Objects.hashCode(Object obj) 方法用于生成对象的哈希码。如果需要在自定义的类中使用哈希表或需要哈希码作为对象标识符,则需要重写该类的 hashCode() 方法,并遵循一致性原则,即如果两个对象相等,它们的哈希码应该相等。

  4. 比较器的使用Objects.compare(T a, T b, Comparator<? super T> c) 方法用于使用指定的比较器对两个对象进行比较。当需要比较不具备自然顺序的对象或需要自定义顺序时,可以提供一个比较器来指定比较规则。

  5. toString() 方法Objects.toString(Object obj) 方法用于将对象转换为字符串表示。对于自定义的类,要确保在该类中正确实现 toString() 方法,以便在需要打印对象时获得有意义的输出。

其他常见API

文章来源:https://blog.csdn.net/kuxiaoyang/article/details/135630749
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。