zoukankan      html  css  js  c++  java
  • Java提高学习之Object(5)

    字符串形式的表现

    Q1toString() 方法实现了什么功能?
    A1toString() 方法将根据调用它的对象返回其对象的字符串形式,通常用于debug。

    Q2:当 toString() 方法没有被覆盖的时候,返回的字符串通常是什么样子的?
    A2:当 toString() 没有被覆盖的时候,返回的字符串格式是 类名@哈希值,哈希值是十六进制的。举例说,假设有一个 Employee 类,toString() 方法返回的结果可能是Empoyee@1c7b0f4d

    Q3:能提供一个正确覆盖 toString() 方法的例子吗?
    A3:见代码清单1:

     1 public class Employee
     2 {
     3    private String name;
     4    private int age;
     5  
     6    public Employee(String name, int age)
     7    {
     8       this.name = name;
     9       this.age = age;
    10    }
    11  
    12    @Override
    13    public String toString()
    14    {
    15       return name + ": " + age;
    16    }
    17 }

    代码清单1:返回一个非默认的字符串形式

        代码清单1声明了 Employee 类,被私有修饰符修饰的 name 和 age 变量,构造器将其初始化。该类覆盖了 toString() 方法,并返回一个包含对象值和一个冒号的 String 对象。

    字符串和 StringBuilder

    当编译器遇到 name + ": " + age 的表达时,会生成一个 java.lang.StringBuilder 对象,并调用 append() 方法来对字符串添加变量值和分隔符。最后调用 toString() 方法返回一个包含各个元素的字符串对象。

    Q4:如何得到字符串的表达形式?
    A4:根据对象的引用,调用引用的 toString() 。例如,假设 emp 包含了一个 Employee 引用,调用 emp.toString() 就会得到这个对象的字符串形式。

    Q5System.out.println(o.toString()); 和 System.out.println(o) 的区别是什么?
    A5System.out.println(o.toString()); 和 System.out.println(o) 两者的输出结果中都包含了对象的字符串形式。区别是,System.out.println(o.toString()); 直接调用toString() 方法,而System.out.println(o) 则是隐式调用了 toString()

    等待和唤醒

    Q6wait()notify() 和 notifyAll() 是用来干什么的?
    A6wait()notify() 和 notifyAll() 可以让线程协调完成一项任务。例如,一个线程生产,另一个线程消费。生产线程不能在前一产品被消费之前运行,而应该等待前一个被生产出来的产品被消费之后才被唤醒,进行生产。同理,消费线程也不能在生产线程之前运行,即不能消费不存在的产品。所以,应该等待生产线程执行一个之后才执行。利用这些方法,就可以实现这些线程之间的协调。从本质上说,一个线程等待某种状态(例如一个产品被生产),另一个线程正在执行,知道产生了某种状态(例如生产了一个产品)。

    Q7:不同的 wait() 方法之间有什么区别?
    A7:没有参数的 wait() 方法被调用之后,线程就会一直处于睡眠状态,直到本对象(就是 wait()被调用的那个对象)调用 notify() 或 notifyAll() 方法。相应的wait(long timeout)wait(long timeout, int nanos)方法中,当等待时间结束或者被唤醒时(无论哪一个先发生)将会结束等待。

    Q8notify() 和 notifyAll() 方法有什么区别?
    A8notify() 方法随机唤醒一个等待的线程,而 notifyAll() 方法将唤醒所有在等待的线程。

    Q9:线程被唤醒之后会发生什么?
    A9:当一个线程被唤醒之后,除非本对象(调用 notify() 或 notifyAll() 的对象)的同步锁被释放,否则不会立即执行。唤醒的线程会按照规则和其他线程竞争同步锁,得到锁的线程将执行。所以notifyAll()方法执行之后,可能会有一个线程立即运行,也可能所有的线程都没运行。

    Q10:为什么在使用等待、唤醒方法时,要放在同步代码中?
    A10::将等待和唤醒方法放在同步代码中是非常必要的,这样做是为了避免竞争条件。鉴于要等待的线程通常在调用wait()之前会确认一种情况存在与否(通常是检查某一变量的值),而另一线程在调用notify()`之前通常会设置某种情况(通常是通过设置一个变量的值)。以下这种情况引发了竞争条件:

    1. 线程一检查了情况和变量,发现需要等待。
    2. 线程二设置了变量。
    3. 线程二调用了notify()。此时,线程一还没有等待,所以这次调用什么用都没有。
    4. 线程一调用了wait()。这下它永远不会被唤醒了。

    Q11:如果在同步代码之外使用这些方法会怎么样呢?
    A11:如果在同步代码之外使用了这些情况,就会抛出java.lang.IllegalMonitorStateException异常。

    Q12:如果在同步代码中调用这些方法呢?
    A12:当 wait() 方法在同步代码中被调用时,会根据同步代码中方法的优先级先后执行。在wait()方法返回值之前,该同步代码一直持有锁,这样就不会出现竞争条件了。在wait()方法可以接受唤醒之前,锁一直不会释放。

    Q13:为什么要把wait()调用放在while循环中,而不是if判断中呢?
    A13:为了防止假唤醒,可以在 stackoverflow上了解有关这类现象的更多信息——假唤醒真的会发生吗?

    Q14:能提供一个使用等待与唤醒方法的范例吗?
    A14:见代码清单2:

     1 public class WaitNotifyDemo
     2 {
     3    public static void main(String[] args)
     4    {
     5       class Shared
     6       {
     7          private String msg;
     8  
     9          synchronized void send(String msg)
    10          {
    11             while (this.msg != null)
    12                try
    13                {
    14                   wait();
    15                }
    16                catch (InterruptedException ie)
    17                {
    18                }
    19             this.msg = msg;
    20             notify();
    21          }
    22  
    23          synchronized String receive()
    24          {
    25             while (msg == null)
    26                try
    27                {
    28                   wait();
    29                }
    30                catch (InterruptedException ie)
    31                {
    32                }
    33             String temp = msg;
    34             msg = null;
    35             notify();
    36             return temp;
    37          }
    38       }
    39  
    40       final Shared shared = new Shared();
    41  
    42       Runnable rsender;
    43       rsender = new Runnable()
    44                 {
    45                    @Override
    46                    public void run()
    47                    {
    48                       for (int i = 0; i < 10; i++)
    49                       {
    50                          shared.send("A"+i);
    51                          try
    52                          {
    53                             Thread.sleep((int)(Math.random()*200));
    54                          }
    55                          catch (InterruptedException ie)
    56                          {
    57                          }
    58                       }
    59                       shared.send("done");
    60                    }
    61                 };
    62       Thread sender = new Thread(rsender);
    63  
    64       Runnable rreceiver;
    65       rreceiver = new Runnable()
    66                   {
    67                      @Override
    68                      public void run()
    69                      {
    70                         String msg;
    71                         while (!(msg = shared.receive()).equals("done"))
    72                         {
    73                            System.out.println(msg);
    74                            try
    75                            {
    76                               Thread.sleep((int)(Math.random()*200));
    77                            }
    78                            catch (InterruptedException ie)
    79                            {
    80                            }
    81                         }
    82                      }
    83                   };
    84       Thread receiver = new Thread(rreceiver);
    85   
    86       sender.start();
    87       receiver.start();     
    88    }
    89 }

    代码清单2:发送与接收信息

    代码清单2声明了一个WaitNotifyDemo类。其中,main()方法有一对发送和接收信息的线程。

    main()方法首先声明了Shard本地类,包含接收和发送信息的任务。Share声明了一个String类型的smg私有成员变量来存储要发送的信息,同时声明了同步的 send()receive()方法来执行接收和发送动作。

    发送线程调用的是send()。因为上一次调用send()的信息可能还没有被接收到,所以这个方法首先要通过计算this.msg != null的值来判断信息发送状态。如果返回值为true,那么信息处于被等待发送的状态,就会调用 wait() 。一旦信息被接收到,接受的线程就会给msg赋值为null并存储新信息,调用notify()唤醒等待的线程。

    接收线程调用的是receive()因为可能没有信息处于被接收状态,这个方法首先会通过计算mas == null的值来验证信息有没有等待被接收的状态。如果表达式返回值为true,就表示没有信息等待被接收,此线程就要调用 wait() 方法。如果有信息发送,发送线程就会给 msg 分配值并且调用notify()唤醒接收线程。

    编译(javac WaitNotifyDemo.java)并运行(java WaitNotifyDemo)源代码,将会看到以下输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    A0
    A1
    A2
    A3
    A4
    A5
    A6
    A7
    A8
    A9

    Q15:我想更加深入的学习等待和唤醒的机制,能提供一些资源吗?
    A15:可以在artima参考Bill Venners的书《Inside the Java Virtual Machine(深入理解 Java 虚拟机)》中第20章 Chapter 20: Thread Synchronization

    Object接口和Java8

    Q16:在第一部分中提到过接口是不继承Object的。然而,我发现有些接口中声明了Object中的方法。比如java.util.Comparator接口有boolean.equals(Object.obj)。为什么呢?
    A16Java语言规范的9.6.3.4部分中清楚说明了,接口有相当于 Object 中成员那样的公共抽象成员。此外,如果接口中声明了Object中的成员函数(例如,声明的函数相当于覆盖 Object中的public方法),则认为是接口覆盖了他们,可以用 @Override注释。

    为什么要在接口中声明非finalpublic Object方法(可能还带有 @Override)呢?举例来说,Comparator接口中就有boolean equals(Object obj)声明,这个方法在接口中声明就是为了此接口的特殊情况。

    此外,这个方法只有在传入的类是一个比较规则相同的比较器的时候,才能返回 true

    因为这种情况是可选的,所以并不强制实现 Comparator。这取决于有没有equals,只有在遇到一个比较规则相同的比较器的时候才返回true的需求。尽管类并不要求覆盖equals,但是文档中却支持这样做来提高性能。

    注意,不覆盖 Object.equals(Object)是安全的。但是,覆盖这个方发可能在一些情况下提高性能,比如让程序判断两个不同的比较器是不是用的相同规则。

    Q17:哪一个equals()方法被覆盖了?是Object中的,还是接口中的?
    A17:更早的文档中说,被覆盖的方法是在Object中的。

    Q18:Java 8支持接口中的默认方法。可以在接口中默认实现Employee方法或者Object中的其他方法吗?
    A18:不可以。Object中的任何public的非final方法都是不允许在接口中默认实现的。这个限制的基本原理在Brian Goetz的允许默认方法覆盖Object中的方法一文中有说明。

  • 相关阅读:
    Codevs 2296 仪仗队 2008年省队选拔赛山东
    Codevs 1535 封锁阳光大学
    Codevs 1069 关押罪犯 2010年NOIP全国联赛提高组
    Codevs 1218 疫情控制 2012年NOIP全国联赛提高组
    Codevs 1684 垃圾陷阱
    洛谷 P1108 低价购买
    Vijos P1325桐桐的糖果计划
    Codevs 3289 花匠 2013年NOIP全国联赛提高组
    Codevs 2611 观光旅游(floyed最小环)
    C语言基础之彩色版C语言(内含linux)
  • 原文地址:https://www.cnblogs.com/sunfie/p/5134309.html
Copyright © 2011-2022 走看看