跳转到内容

Java 编程/API/java.lang.Object

来自 Wikibooks,开放世界中的开放书籍

java.lang.Object

[编辑 | 编辑源代码]

Object 类是所有 Java 类的超类。所有 Java 类都继承自此类。这使得我们可以拥有所有 Java 类中都可用的方法成为可能。与 C++ 不同,这简化了事情。

Object 类方法 描述
boolean equals( Object o ); 提供比较对象的一般方法
Class getClass(); Class 类为我们提供了更多关于对象的信息
int hashCode(); 返回一个哈希值,用于在集合中搜索对象
void notify(); 用于线程同步
void notifyAll(); 用于线程同步
String toString(); 可用于将对象转换为字符串
void wait(); 用于线程同步
protected Object clone() throws CloneNotSupportedException ; 返回一个与当前对象完全相同的新对象
protected void finalize() throws Throwable; 此方法在垃圾回收对象之前调用

equals() 方法

[编辑 | 编辑源代码]
  • boolean equals( Object o ) 方法提供了一种通用的方式来比较对象的相等性。您需要在您的类中重写它。然后你可以写
public boolean isCustomerExist( Customer newCustomer )
{
   boolean isRet = false;
   Iterator iter = _collAllCustomer.iterator();
   while ( iter.hasNext() )
   {
      if ( newCustomer.equals( (Customer) iter.next() )
      {
         // -- Customer was found ---
         isRet = true;
      }
   }
  return isRet;
}

请记住,当您重写 equals() 时,始终需要同时重写 hashCode() 以使这两个方法保持一致。如果两个对象相等,则它们必须具有相同的哈希码。

有关更多信息,另请参阅 Java 编程/比较对象

getClass() 方法

[编辑 | 编辑源代码]

程序中的每个类都有一个 Class 对象。每个数组也属于一个类,该类反映为一个 Class 对象,该对象由所有具有相同元素类型和维数的数组共享。Java 基本类型booleanbytecharshortintlongfloatdouble)以及关键字 void 也表示为 Class 对象。Class 没有公共构造函数。相反,当类加载时,Java 虚拟机会自动构造 Class 对象

有关更多信息,请参阅 Class

Class 最常用的用途是在运行时找出对象的类名。

import com.yourCompany.Customer;
...
Object obj = new Customer();
...
System.out.println( "Name:" + obj.getClass().getName() );

输出

Name: com.yourCompany.Customer

hashCode() 方法

[编辑 | 编辑源代码]

在大多数情况下,您不应该重写此方法,因为此方法的默认实现会为对象返回一个唯一编号。当对象放入集合时,将使用该编号。如果逐一顺序比较对象,则在大型集合中查找对象可能需要一段时间。为了加快搜索速度,可以将对象放置在树形结构中,并根据整数哈希码加权。在遍历树时比较哈希码,可以减少对象比较的次数。

   _______ A _____
   |              |  
__ B__          __C__
|     |        |     |
D     E        F     G
...  ...      ...   ...

为了让您大致了解其工作原理,请参见上图。假设我们正在搜索对象 G。如果在树的每个“节点”上我们都可以决定走哪条路,那么通过3 步我们就可以到达对象 G。

相比之下,在线性搜索中

A --- B  ----- C  ---- C  ---- D  ---- E ---- F ---- G

我们需要8 步才能到达对象 G。

因此,使用树形结构搜索速度会更快。但是,添加新对象会更慢,因为需要维护树形结构。首先必须找到新对象在树中的位置。

toString() 方法

[编辑 | 编辑源代码]

此方法可用于将对象转换为String。它在许多地方自动用于将对象转换为字符串;例如:在 PrintStream、StringBuffer 中,以及在对对象使用字符串连接运算符时。

默认实现返回一个带有类名和哈希码的奇怪字符串。

例如

String str = "This customer is " + objCust;

toString() 方法在 objCust 对象上调用。

toString() 方法也可用于调试

public class Customer
{
   private String _name;
   private String _address;
   private String _age;
...
   public String toString()
   {
       StringBuffer buf = new StringBuffer();
       buf.append( "Name   = " );  buf.append( _name );     buf.append( "\n" );
       buf.append( "Address= " );  buf.append( _address );  buf.append( "\n" );
       buf.append( "Age    = " );  buf.append( _age );      buf.append( "\n" );
    ...
       return buf.toString();
   }
...
}

之后,无论何时在您的代码中,您想查看客户对象是什么,只需调用

System.out.println( objCustomer );

线程同步方法

[编辑 | 编辑源代码]

在多线程环境中,当多个线程可以访问和修改资源时,结果可能是不可预测的。例如,让我们有一个计数器变量,它由多个线程递增。

注意!同步是一个模棱两可的术语。它不包括使所有线程同时执行相同的代码段。恰恰相反。它可以防止任何两个线程同时执行相同的代码段。它使一个处理的结束与第二个处理的开始同步。

Example 代码段 1.1:计数器实现
int counter = 0;
...
counter += 1;

以上代码由以下子操作构成

  • 读取;读取变量 counter
  • 添加;将 1 加到值上
  • 保存;将新值保存到变量 counter

假设两个线程需要执行该代码,如果 counter 变量的初始值为零,我们期望操作后该值变为 2。

线程 1   线程 2
         
读取 0   读取 0
         
添加 1   添加 1
         
保存 1   保存 1
         

在上述情况下,线程 1 的操作丢失了,因为线程 2 覆盖了它的值。我们希望线程 2 等待线程 1 完成操作。请参见下文

线程 1   线程 2
         
读取 0   阻塞
         
添加 1   阻塞
         
保存 1   解除阻塞
         
  读取 1
     
  添加 1
     
  保存 2
     
临界区
在上面的例子中,代码 counter+=1 必须在任何给定时间由一个且仅一个线程执行。这称为临界区。在编程过程中,在多线程环境中,我们必须识别属于临界区的所有这些代码段,并确保在任何给定时间只有一个线程可以执行这些代码。这称为同步。

线程同步
线程访问临界区代码必须在各个线程之间进行同步,也就是说要确保在任何给定时间只有一个线程可以执行它。
对象监视器
每个对象都有一个对象监视器。基本上它是一个信号量,指示临界区代码是否正在被某个线程执行。在执行临界区代码之前,线程必须获得对象监视器。一次只有一个线程可以拥有对象的监视器。
线程可以通过三种方式之一成为对象监视器的拥有者
  • 通过执行该对象的同步实例方法。参见synchronized关键字。
  • 通过执行同步语句的主体,该语句对对象进行同步。参见synchronized关键字。
  • 对于Class类型的对象,通过执行该类的同步静态方法。
对象监视器负责同步,那么为什么我们还需要“wait() 和 notify() 方法”呢?
对于同步,我们并不真正需要它们,但是对于某些情况来说,使用它们会很好。一个友好且体贴的线程会使用它们。在执行临界区代码期间,线程可能会卡住,无法继续执行。这可能是因为它正在等待IO和其他资源。无论如何,线程可能需要等待相对较长的时间。如果线程一直持有对象监视器并阻止其他线程工作,那将是自私的。因此,线程通过调用对象上的wait()方法进入“等待”状态。它必须是线程从中获取对象监视器的同一个对象。
另一方面,线程只有在至少存在另一个线程将在资源可用时调用notify()方法的情况下,才应该调用wait()方法,否则线程将永远等待,除非将时间间隔指定为参数。
让我们做一个类比。你去商店购买一些物品。你在柜台前排队,你获得了售货员的关注 - 你获得了她的“对象监视器”。你询问你想要的物品。其中一件物品需要从仓库中取来。这将花费超过五分钟的时间,因此你释放售货员(将她的“对象监视器”还给她),以便她可以为其他顾客服务。你进入等待状态。假设还有五位顾客已经在等待。另一位售货员从仓库中取来物品。当她这样做时,她会引起第一位售货员的注意,获得她的对象监视器并通知一位或所有等待的顾客,以便等待的顾客醒来并再次排队以获得第一位售货员的关注。
请注意等待的顾客和取来物品的售货员之间的同步。这是一种生产者-消费者同步。
还要注意,只有一个对象监视器,属于第一位售货员。在发生等待通知之前,必须首先获得该对象监视器/售货员的关注。


final void wait() 方法
当前线程必须拥有该对象的监视器。线程释放对该监视器的所有权,并等待另一个线程通过调用notify方法或notifyAll方法通知正在等待该对象监视器的线程唤醒。然后,线程等待直到它能够重新获得监视器的所有权并恢复执行。
final void wait(long time)
与wait相同,但线程在指定持续时间过去后唤醒,无论是否收到通知。
final void notify()
此方法只能由拥有该对象监视器的线程调用。唤醒一个正在等待该对象监视器的线程。如果多个线程正在等待该对象监视器,则选择其中一个唤醒。选择是任意的,并且由实现决定。线程通过调用其中一个wait方法来等待对象监视器。
被唤醒的线程在当前线程释放对该对象的锁之前无法继续执行。被唤醒的线程将以通常的方式与任何其他可能正在积极竞争同步该对象的线程竞争;例如,被唤醒的线程在成为下一个锁定该对象的线程方面没有任何可靠的特权或劣势。
final void notifyAll()
notify()相同,但它唤醒所有正在等待该对象监视器的线程。


sleep() 和 wait() 方法之间有什么区别?
Thread.sleep(millis)
这是Thread类的静态方法。导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数。线程不会丢失任何监视器的所有权。这意味着如果线程拥有对象监视器,则所有其他需要该监视器的线程都将被阻塞。无论线程是否拥有任何监视器,都可以调用此方法。
wait()
此方法继承自Object类。线程必须首先获得该对象的监视器,然后才能调用wait()方法。对象监视器由wait()方法释放,因此它不会阻塞其他想要此对象监视器的等待线程。


另请参阅

[编辑 | 编辑源代码]
华夏公益教科书