1.常用方法
1.clone方法
1.1 基本概念
- clone() 方法是属于 Object 类的一个方法,因此所有的类都可以重写该方法。
- clone() 方法被设计为返回一个新的对象,该对象和原始对象具有相同的状态。
- clone() 方法与 new 关键字不同,new 关键字会调用构造函数创建新的对象,而 clone() 方法则不会调用构造函数。
1.2 方法声明
protected native Object clone() throws CloneNotSupportedException;
- protected:clone() 方法的访问修饰符是 protected,意味着只能在同一包中或子类中访问。
- native:clone() 方法是一个本地方法,它使用 C/C++ 实现。
- Object:clone() 方法返回一个 Object 类型的对象,需要进行类型转换以获取其真实类型。
- throws CloneNotSupportedException:clone() 方法声明了 CloneNotSupportedException 异常,如果该方法所在的类没有实现 Cloneable 接口,则调用该方法时会抛出该异常。
1.3 工作原理
- 在默认情况下,clone() 方法会创建一个新的对象,该对象和原始对象具有相同的状态。
- 如果要创建的新对象是可变的,应该针对该对象进行深拷贝(deep copy),以避免新对象和原始对象引用相同的可变对象。
- 如果要创建的新对象是不可变的,可以进行浅拷贝(shallow copy),以节省时间和空间。
1.4 注意事项
- 要使用 clone() 方法,该方法所在的类必须实现 Cloneable 接口,并覆盖 clone() 方法。
- Cloneable 接口是一个标记接口,它没有任何方法,只是表示该类可以被克隆。
- 在重写 clone() 方法时,需要将方法的访问修饰符改为 public,并调用 super.clone() 方法以获取原始对象的副本。
- 在进行深拷贝时,需要递归遍历所有可变对象,并对其进行拷贝。如果拷贝的对象中包含其他可变对象的引用,则需要继续递归拷贝。
1.5 示例代码
下面是一个简单的示例代码,演示如何使用 clone() 方法创建对象的副本:
public class MyClass implements Cloneable {
private int value;
public MyClass(int value) {
this.value = value;
}
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
MyClass obj1 = new MyClass(10);
MyClass obj2 = (MyClass) obj1.clone();
System.out.println(obj1.getValue()); // 输出 10
System.out.println(obj2.getValue()); // 输出 10
obj2.setValue(20);
System.out.println(obj1.getValue()); // 输出 10
System.out.println(obj2.getValue()); // 输出 20
}
}
在上面的示例代码中,MyClass 类实现了 Cloneable 接口,并重写了 clone() 方法。在 Main 类中,我们创建了两个 MyClass 对象,分别为 obj1 和 obj2,并使用 clone() 方法创建了 obj2 的副本。最后,我们修改 obj2 的属性值,并打印出两个对象的属性值,可以看到它们是独立的。
2.finalize方法
2.1 基本概念
- finalize() 方法是属于 Object 类的一个方法,因此所有的类都可以重写该方法。
- finalize() 方法被设计为在对象即将被垃圾回收时调用,以便进行一些清理操作或资源释放。
2.2 方法声明
protected void finalize() throws Throwable
- protected:finalize() 方法的访问修饰符是 protected,意味着只能在同一包中或子类中访问。
- void:finalize() 方法没有返回值。
- throws Throwable:finalize() 方法声明了 Throwable 异常,允许抛出任何异常。
2.3 工作原理
- 当一个对象不再被引用时,垃圾回收器会在适当的时机对其进行回收。
- 在垃圾回收过程中,如果该对象的 finalize() 方法被重写并且未被调用过,垃圾回收器会先调用该方法。
- finalize() 方法只会被调用一次,因此在方法内部一般需要执行一些清理操作,如关闭打开的文件、释放占用的系统资源等。
2.4 注意事项
- 不建议过多依赖 finalize() 方法进行资源释放,因为垃圾回收的时间是不确定的,可能会延迟执行,从而导致资源长时间占用。
- finalize() 方法的执行时间和顺序是不可控的,无法保证在对象被销毁前立即执行。
- 由于 finalize() 方法是 protected 的,子类可以重写该方法以实现自定义的清理操作。
2.5 替代方案
- 更好的方式是使用 try-finally 结构或者实现 Closeable 接口,在合适的时候手动释放资源。
- 在 Java 7 引入的 try-with-resources 语句块中,自动关闭资源更加简洁和安全。
总结:finalize() 方法是在对象被垃圾回收之前调用的方法,用于执行一些清理操作或资源释放。然而,由于其执行时间和顺序不可控,并且可能导致资源长时间占用,因此建议使用其他替代方案来进行资源的释放操作。
3.equals方法
3.1 方法声明
public boolean equals(Object obj)
- equals() 方法是 Object 类的一个公共方法,因此所有的类都可以重写该方法。
- 该方法接收一个 Object 类型的参数,用于与调用该方法的对象进行比较。
3.2 默认行为
- 在 Object 类中,equals() 方法的默认实现是使用 == 运算符来比较两个对象的引用是否相同,即判断两个对象是否是同一个对象。
- 因此,默认情况下,如果没有在自定义类中重写 equals() 方法,那么两个对象只有在引用相同时才被认为相等。
3.3 重写规范
- 在自定义类中,通常需要根据类的语义去重新定义 equals() 方法,以实现对对象内容的比较而非引用的比较。
- 重写 equals() 方法时,通常需要满足以下约定:
-
- 自反性(Reflexive):对于任何非空引用值 x,x.equals(x) 应当返回 true。
-
- 对称性(Symmetric):对于任何非空引用值 x 和 y,如果 x.equals(y) 返回 true,则 y.equals(x) 也应当返回 true。
-
- 传递性(Transitive):对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 也返回 true,则 x.equals(z) 应当返回 true。
-
- 一致性(Consistent):对于任何非空引用值 x 和 y,只要 equals 的比较操作在对象中所用的信息没有被修改,多次调用 x.equals(y) 应当始终返回相同的结果。
-
- 非空性(Non-nullity):对于任何非空引用值 x,x.equals(null) 应当返回 false。
3.4 示例代码
下面是一个简单的示例代码,演示如何重写 equals() 方法:
public class MyClass {
private int value;
public MyClass(int value) {
this.value = value;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
MyClass myObj = (MyClass) obj;
return value == myObj.value;
}
}
3.5 注意事项
- 在重写 equals() 方法时,通常也需要重写 hashCode() 方法,以保证当两个对象相等时它们的哈希码也相等。
- 使用 IDE 自动生成 equals() 方法和 hashCode() 方法可以提高开发效率,减少出错的可能性。
总结起来,重写 equals() 方法是为了根据对象的内容来判断两个对象是否相等,而不仅仅是比较它们的引用。在重写该方法时,需要遵循一定的规范,以确保对象比较的正确性和一致性。
4.hashcode方法
在Java中,hashCode()方法是用来获取一个对象的哈希码(散列码)的方法。它返回的是一个int类型的数字,该数字是根据对象的特征计算出来的,并且是唯一的。因此,我们可以将这个数字作为该对象的标识符。
4.1 实现方式
hashCode()方法的实现方式如下:
-
对象的存储地址。默认情况下,hashCode()方法返回的就是对象在内存中的存储地址,也就是对象的唯一标识符。但是,这种方式有一个缺点,就是每次程序运行时,同一个对象的哈希码都会不同,因为它们可能被分配到不同的内存地址上。
-
对象的属性值。当对象的属性值改变时,hashCode()方法返回的哈希码也应该改变,以保证相同属性的对象拥有相同的哈希码。因此,在实现hashCode()方法时,我们通常会根据对象的属性值进行计算,并返回一个与属性值相关的哈希码。
在Java中,如果两个对象的equals()方法返回true,则它们的哈希码必须相等,即hashCode()方法返回的值相等。因此,在实现hashCode()方法时,我们需要确保对于相等的对象,它们的哈希码也相等。
Java中的一些类已经实现了hashCode()方法,例如String、Integer、Double等。对于自定义的类,我们需要根据实际情况来实现hashCode()方法。一般来说,实现hashCode()方法的方式如下:
-
声明一个int类型的变量result,并初始化为一个非零常数。
-
对象的每个重要属性,例如字符串、数字等,都需要计算它们的哈希码并与result进行结合运算(如乘法、异或等),以便得到一个新的哈希码。
-
最后返回result作为该对象的哈希码。
4.2 遵循原则
需要注意的是,实现hashCode()方法时应遵循以下原则:
-
如果两个对象的equals()方法返回true,则它们的hashCode()方法必须返回相同的值。
-
如果两个对象的equals()方法返回false,则它们的hashCode()方法不一定要返回不同的值,但最好是不同的,以提高哈希表的性能。
-
在计算哈希码时,尽可能使用所有重要的属性,以提高哈希表的性能。
总之,hashCode()方法的目的是为了加快查找对象的速度,因此在实现hashCode()方法时,我们需要确保它能够产生良好的分布,以减少哈希冲突和哈希表的性能问题。
4.3 示例代码
下面是一个比较详细的hashCode()方法的实现示例:
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 (obj == null) return false;
if (this == obj) return true;
if (obj.getClass() != this.getClass()) return false;
Person p = (Person) obj;
return this.name.equals(p.name) && this.age == p.age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + age;
return result;
}
}
上述代码中,hashCode()方法的实现遵循了以下原则:
-
首先声明了一个int类型的变量result,初始化为一个非零常数1。
-
然后使用常数31作为哈希码的乘数,这是因为31是一个素数,而且比较适合用于哈希码的计算。
-
对于对象的每个重要属性,例如字符串、数字等,都需要计算它们的哈希码并与result进行结合运算(如乘法、异或等),以便得到一个新的哈希码。
-
最后返回result作为该对象的哈希码。
在上述代码中,我们使用了name.hashCode()来计算字符串类型的属性name的哈希码,这是因为字符串类型已经实现了hashCode()方法。如果是其他类型,则需要根据实际情况计算其哈希码。
同时,在equals()方法中,我们也使用了相同的属性进行比较,以保证在两个对象相等时,它们的哈希码也相等。
总之,hashCode()方法的实现应该遵循上述原则,并且要注意根据实际情况合理选择哈希码的计算方式,以提高哈希表的性能。
5.wait方法
在 Java 中,wait() 是 Object 类的一个方法,可以用于线程同步和协作。它是一个阻塞方法,调用它的线程会被挂起,等待其他线程调用 notify() 或 notifyAll() 方法来唤醒它。
以下是使用 wait() 方法进行线程同步和协作的示例:
public class SharedResource {
private int counter = 0;
public synchronized void increment() throws InterruptedException {
while (counter == 1) {
wait(); // 如果计数器已经为 1,则等待其他线程调用 notify() 方法
}
counter++;
System.out.println("Counter incremented: " + counter);
notify(); // 唤醒其他可能在等待的线程
}
public synchronized void decrement() throws InterruptedException {
while (counter == 0) {
wait(); // 如果计数器已经为 0,则等待其他线程调用 notify() 方法
}
counter--;
System.out.println("Counter decremented: " + counter);
notify(); // 唤醒其他可能在等待的线程
}
}
在这个示例中,我们定义了一个名为 SharedResource 的类,其中包含了两个方法:increment() 和 decrement()。这两个方法都是同步方法,即它们使用了 synchronized 关键字,以确保线程安全。
在 increment() 和 decrement() 方法中,我们使用了 while 循环来判断是否满足某个条件(例如计数器是否为 0 或 1),如果不满足则调用 wait() 方法,挂起当前线程。
在等待其他线程唤醒它之后,当前线程会继续执行剩余的代码。在执行完毕之后,我们调用了 notify() 方法,唤醒其他可能在等待的线程。
需要注意的是,wait() 和 notify() 方法只能在已经获取了对象的锁的情况下使用,否则会抛出 IllegalMonitorStateException 异常。此外,wait() 和 notify() 方法也只能用于同步方法或同步块中。
6.notify方法
在Java中,notify()方法是Object类的一个方法,用于唤醒正在等待该对象锁的某个线程。当调用notify()方法时,会选择性地通知等待在该对象上的一个线程,告诉它可以继续执行了。
同步队列中的线程是给抢占 CPU 的线程,等待队列中的线程指的是等待唤醒的线程
以下是notify()方法的详细解释:
-
notify()方法的作用是唤醒等待在当前对象上的一个线程。如果有多个线程在等待,只会随机选择一个线程进行唤醒。被唤醒的线程将从等待状态转为可运行状态,但并不是立即执行,而是需要等待获取对象的锁才能继续执行。
-
调用notify()方法前,必须先获得当前对象的锁。通常情况下,notify()方法应该在synchronized代码块或synchronized方法中使用,以确保在调用notify()方法时拥有对象的锁。
-
如果没有等待在当前对象上的线程,即没有调用wait()方法进入等待状态的线程,调用notify()方法不会有任何效果。因此,在调用notify()方法前,通常需要检查是否有线程处于等待状态。
-
notify()方法不会立即释放锁,而是继续执行当前的synchronized代码块或synchronized方法,直到离开同步块,才会释放锁。这意味着,被唤醒的线程只有在获取到锁之后才能继续执行。
以下是notify()方法的示例代码:
public class Example {
public static void main(String[] args) {
Object lock = new Object();
// 线程1
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程1等待");
lock.wait(); // 线程1等待,并释放lock对象的锁
System.out.println("线程1被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 线程2
Thread thread2 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程2开始");
Thread.sleep(2000); // 线程2休眠2秒钟
lock.notify(); // 唤醒等待在lock对象上的线程1
System.out.println("线程2结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
}
在这个例子中,我们创建了一个名为Example的类。在main()方法中,我们创建了一个共享对象lock,并创建了两个线程thread1和thread2。
线程1首先获取lock对象的锁,并调用wait()方法进行等待,这会释放lock对象的锁。接着,线程2获取lock对象的锁,并调用notify()方法来唤醒等待在lock对象上的线程1。最后,线程1被唤醒后继续执行。
当我们运行上述代码时,将会输出以下结果:
线程1等待
线程2开始
线程2结束
线程1被唤醒
可以看到,通过调用wait()和notify()方法,我们可以实现线程间的通信。wait()方法使线程进入等待状态并释放对象锁,而notify()方法则唤醒等待在该对象上的某个线程。
总之,notify()方法是Java中用于线程间通信的重要方法之一,但需要注意的是,notify()方法只会唤醒等待在当前对象上的一个线程。如果想要唤醒所有等待的线程,可以使用notifyAll()方法。notifyAll()方法会唤醒所有等待在当前对象上的线程,让它们从等待状态转为可运行状态。
7.notifyAll方法
notifyAll()方法是Java中用于线程间通信的方法之一。它属于Object类,用于唤醒等待在当前对象上的所有线程。
以下是notifyAll()方法的详细解释:
-
notifyAll()方法的作用是唤醒等待在当前对象上的所有线程。被唤醒的线程将从等待状态转为可运行状态,但并不是立即执行,而是需要等待获取对象的锁才能继续执行。
-
调用notifyAll()方法前,必须先获得当前对象的锁。通常情况下,notifyAll()方法应该在synchronized代码块或synchronized方法中使用,以确保在调用notifyAll()方法时拥有对象的锁。
-
如果没有等待在当前对象上的线程,即没有调用wait()方法进入等待状态的线程,调用notifyAll()方法不会有任何效果。因此,在调用notifyAll()方法前,通常需要检查是否有线程处于等待状态。
-
notifyAll()方法不会立即释放锁,而是继续执行当前的synchronized代码块或synchronized方法,直到离开同步块,才会释放锁。这意味着,被唤醒的线程只有在获取到锁之后才能继续执行。
需要注意的是,notifyAll()方法会唤醒所有等待在当前对象上的线程,包括已经获取了锁但是还没有执行的线程。因此,在使用notifyAll()方法时,需要仔细考虑是否需要唤醒所有线程,以及如何确保线程安全。
线程间通信是多线程编程中非常重要的概念,通过使用wait()、notify()和notifyAll()等方法,可以实现线程之间的协调和同步。这些方法的使用需要注意正确的同步机制,以避免死锁和竞态条件等问题。
8.toString方法
在Java中,toString() 方法是Object类的一个方法,它用于返回表示对象的字符串表示形式。当我们打印一个对象或将其转换为字符串时,实际上是调用了对象的toString()方法。
如果我们没有在自定义的类中重写toString()方法,那么默认情况下会使用Object类中的toString()方法。Object类的toString()方法返回的字符串包含对象的类名和哈希码的十六进制表示。
以下是一个使用toString()方法的示例:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 重写toString()方法
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public static void main(String[] args) {
Person person = new Person("John", 25);
System.out.println(person.toString()); // 调用toString()方法
System.out.println(person); // 简化的调用方式,实际上也是调用toString()方法
}
}
在上面的示例中,我们创建了一个Person类,并重写了toString()方法。在toString()方法中,我们返回了一个包含name和age属性的字符串表示。
在main()方法中,我们创建了一个Person对象,并调用了toString()方法来打印对象的字符串表示。此外,我们还使用了简化的方式直接打印对象,实际上也是调用了toString()方法。
当我们运行上述代码时,将会输出以下结果:
Person{name='John', age=25}
Person{name='John', age=25}
可以看到,通过重写toString()方法,我们可以自定义对象的字符串表示形式,使其更易读和有意义。在实际开发中,通常会根据需要重写toString()方法,以便更好地表示对象的状态和属性。
9.getClass方法
在 Java 中,getClass() 方法是Object类的一个方法,它用于返回表示对象所属类的Class对象。每个对象都可以调用getClass()方法来获取其所属类的信息。
以下是getClass()方法的详细解释:
public class Example {
public static void main(String[] args) {
String str = "Hello";
Class<? extends String> clazz = str.getClass();
System.out.println(clazz.getName());
}
}
在这个例子中,我们创建了一个名为Example的类。在main()方法中,我们创建了一个字符串对象str,然后调用了getClass()方法来获取该对象的类信息。最后,我们使用getName()方法打印了类的名称。
当我们运行上述代码时,将会输出以下结果:
java.lang.String
可以看到,通过调用getClass()方法,我们可以获取对象的运行时类型信息,然后使用Class对象提供的方法来获取类的名称、方法、字段等信息。
需要注意的是,由于getClass()方法是从Object类继承而来的,因此所有的Java对象都可以调用getClass()方法。但是,如果对象是null,调用getClass()方法将会导致NullPointerException异常。
另外,对于数组对象而言,getClass()方法返回的是描述数组类型的Class对象,而不是数组中元素的类型的Class对象。如果想要获取数组中元素的类型,可以使用Class对象的getComponentType()方法。
总之,getClass()方法是一个非常重要的方法,它允许我们在运行时获取对象的类型信息,并进行相应的操作。