Java核心技术 第六章 接口和内部类

Java核心技术  第六章  接口与内部类

接口:

任何实现Comparable接口的类都需要包含compareTo方法,并且这个方法的参数必须是一个Object对象,返回一个整数数值。

Java SE 5.0中,Comparable接口已经改进为泛型类型。

接口中所有的方法自动的属于public。因此,在接口中声明方法时,不必提供关键字public

接口中决不能含有实例域,也不能在接口中实现方法。

要让一个类使用排序服务,必须让它实现compareTo方法,因此必须实现Comparable接口。

接口的特性:

不能使用new运算符实例化一个接口。

可以声明接口的变量:

Comparable x ;

X = new Employee() ;

可以使用instanceof检查一个对象是否属于某个特定接口。

If(anObject instanceof Comparable)

与可以建立类的继承关系一样,接口也可以被扩展。

public interface Moveable {

  void move(double x, double y) ;

}

public interface Powered extends Moveable {

  double milePerGallon() ;

}

虽然接口中不能包含实例域或静态方法,但却可以包含常量。

public interface Powered extends Moveable {

  double milePerGallon() ;

  double SPEED_LIMIT = 95 ;

}

与接口中的方法都自动的被设置为public一样,接口中的域将被自动设为public static final .

尽管每个类只能拥有一个超类,但却可以实现多个接口。使用逗号将实现的各个接口隔开。

对象克隆:

Employee original = new Employee(“John Public”, 5000) ;

Employee copy = original ;

copy.raiseSalary(10) ;

copy original引用的是一个变量,改变copy将改变original

如果创建一个对象的新的copy, 它的最初状态与original一样,但以后将可以各自改变各自的状态,那就需要使用clone方法。

Employee copy = original.clone() ;

copy.raiseSalary(10) ;

clone 方法是Object类的一个protected方法。所以只有Employee类才能克隆Employee对象。克隆是对对象中的所有的数据域进行克隆。若果所有的数据域属于数值或基本类型,这样拷贝域没有任何问题。但是,如果在对象中包含了子对象的引用,拷贝的结果会使得两个域引用同一个子对象,因此原始对象与克隆对象共享这部分信息。

进行浅拷贝,如果原始对象与克隆对象共享的子对象是不可变的,将不会产生任何问题。然而更常见的情况是子对象可变,因此这种情况下需要重新定义clone方法,以便实现克隆子对象的深拷贝。

对每一个类,都需要作出下列判断:

  1. 默认的克隆方法是否满足要求
  2. 默认的clone方法是否能够通过调用可变子对象的clone得到修补
  3. 是否不应该使用clone

实际上,选项3是默认的。如果要选择12,类必须:

1.实现Cloneable接口

2.使用public访问修饰符重新定义clone方法

在这里,Cloneable接口的出现于接口的正常使用没有任何关系。尤其是,它并没有指定clone这个方法,这个方法是从Object类继承而来的。接口在这里只是作为一个标记,表明类设计者知道要进行克隆处理。如果一个对象需要克隆,而没有实现Cloneable接口,就会产生一个已检查异常。

即使clone的默认实现(浅拷贝能够满足要求),也应该实现Cloneable接口,将clone重新定义为public,并调用super.clone()。 例:

class Employee implements Cloneable {

  public Employee clone() throws CloneSupportedException {

    return (Employee) super.clone() ;

  }

}

上面实现的是浅拷贝,若想实现深拷贝,必须克隆所有可变的实例域。

下面是一个建立深拷贝clone方法的一个示例:

class  Employee implements Cloneable {

...

  public Employee clone() throws CloneNotSupportedException {

    Employee cloned = (Employee) super.clone() ;

    cloned.hireDay = (Date) hireDay.clone() ;

    return cloned ;

  }

}

只要在clone中含有没有实现Cloneable接口的对象,Object类的clone方法就会抛出一个CloneNotSupportedException 异常。当然EmployeeDate类都实现了Cloneable 接口,因此不会抛出异常。但是编译器并不知道这些情况,因此需要声明异常:

public Employee clone() throws CloneNotSupportedException

一旦为Employee类定义了clone方法,任何人都可以利用它克隆Manager对象。Employee的克隆方法能否完成这项重任,取决于Manager类中包含哪些域。如果Manager中包含一些需要深拷贝的域或者包含一些没有实现Cloneable接口的域,无法保证拷贝正确。

在标准类库中,只有不到5%的类实现了clone

接口与回调:

回调是一种常见的设计模式。在这种模式中,可以指出摸个特定事件发生时应该采取的动作。

内部类:

内部类是定义在另一个类中的类。

需要内部类的原因:

l 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据

l 内部类可以对同一个包中的其他类隐藏起来

l 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。

使用内部类访问对象状态:

public class TalkingClock {

  private int interval ;

  private boolean beep ;

  public TalkingClock(int interval, boolean beep) {...}

  public void start() {...}

  public class TimerPrinter implements ActionListener {

    public void actionPerformed(ActionEvent event) {

    Date now = new Date() ;

    System.out.println(“At the tone, the time is” + now) ;

    if (beep) Toolkit.getDefaultToolkit().beep() ;

    }

  }

}

需要注意,这里的TimerPrinter类位于TalkingClock类内部。

内部类的特殊语法规则:

使用外围类的表达式:

OuterClass.this

例:

public void actionPerformed(ActionEvent event) {

...

  if (TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep() ;

}

反过来,可以采用下列语法格式更加明确地编写内部对象的构造器:

outerObject.new InnerClass(construction parameters)

例如:

ActionListener listener = this.new TimePrinter() ;

通常this是多余的

TalkingClock jabberer = new TalkingClock(1000, true) ;

TalkingClock.TimePrinter listener = jabberer.new TimePrinter() ;

内部类是否有用、必要和安全:

内部类是一种编译器现象,与虚拟机无关。编译器将会把内部类翻译成$分隔外部类名与内部类名的常规文件,而虚拟机对此一无所知。

例如:在TalkingClock类内部的TimePrinter类将被翻译成类文件TalkingClock$TimePrinter.class.

编译器在外围类添加静态方法access$0.它将返回作为参数传递给它的对象域beep。(方法名取决于编译器)

if(beep) 相当于if(access$0(outer))

局部内部类:

TimePrinter这个类名只在start方法中创建这个类型的对象时使用了一次。当遇到这种情况时,可以在一个方法中定义局部类。

public void start() {

  public class TimerPrinter implements ActionListener {

    public void actionPerformed(ActionEvent event) {

    Date now = new Date() ;

    System.out.println(“At the tone, the time is” + now) ;

    if (beep) Toolkit.getDefaultToolkit().beep() ;

  }

}

  ActionListener listener = new TimePrinter() ;

  Timer t = new Timer(interval, listener) ;

  t.start() ;

}

局部类不能用publicprivate访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。

局部类有一个优势,即对外部世界可以完全地隐藏起来。即使TalkingClock类中的其它代码也不能访问它。除了start方法外,没有任何方法知道TimePrinter的存在。

由外部方法访问final变量:

与其他内部类相比较,局部类还有一个优点。它们不仅能够访问包含它们的外部类,还可以访问局部变量。不过那些局部变量必须被声明为final

例:将TalkingClock构造器的参数intervalbeep移至start方法中。

public void start(int interval, final boolean beep) {

  class TimePrinter implements ActionListener {

    public void actionPerformed(ActionEvent event) {

    Date now = new Date() ;

    System.out.println(“At the tone, the time is” + now) ;

    if (beep) Toolkit.getDefaultToolkit().beep() ;

  }

}

  ActionListener listener = new TimePrinter() ;

  Timer t = new Timer(interval, listener) ;

  t.start() ;

}

由于beep是局部变量,start方法结束,beep参数变量不复存在,为了能够让actionPerformed能够正常工作,TimerPrinter类在beep域释放之前将beep域用start方法的局部变量进行备份。final boolean val$beep ;该域放在TalkingClockTimePrinter中。

final关键字可以用于局部变量、实例变量和静态变量。在所有这些情况下,它们的含义都是:在创建这个变量后,只能够为之赋值一次。此后再也不能修改它的值了,这就是final

不过在定义final变量的时候,不必进行初始化。

final int[] counter = new int[] ;

这样可以更新counter

匿名内部类:

将局部类的使用再深一步。加入只创建这个类的一个对象,就不必命名了。这种情况称为匿名内部类。

public void start(int interval, final boolean beep) {

  ActionListener listener = new ActionListener()

  {

    public void actionPerformed(ActionEvent event) {

    Date now = new Date() ;

    System.out.println(“At the tone, the time is” + now) ;

    if (beep) Toolkit.getDefaultToolkit().beep() ;

  }

} ;

  Timer t = new Timer(interval, listener) ;

  t.start() ;

}

它的含义是:创建一个实现ActionListener接口的类的新对象,需要实现的方法actionPerformed定义在括号{}内。

通常的语法格式为:

new SuperType(construction parameters) {

  inner class methods and data

}

其中,SuperType可以是ActionListener这样的接口,于是内部类就要实现这个接口。SuperType也可以是一个类,于是内部类就要扩展它。

由于构造器的名字必须与类名相同,而匿名类没有类名,所以,匿名类不能有构造器。取而代之的是将构造器参数传递给超类构造器。

得到类名:

System.out.println(“Something awful happened in” +  getClass()) ;

不过对于静态方法无效。getClass()调用的是this.getClass(),静态方法没有this

静态方法应使用下面的表达式:

new Object(){}.getClass().getEnclosingClass()

在这里,new Object(){}会建立Object的一个匿名子类的一个匿名对象,getEnclosingClass则得到其外围类,也就是包含这个静态方法的类。

静态内部类:

有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static,以便取消产生的引用。

当然,只有内部类可以声明为static

静态内部类除的对象除了没有对生成它的外围对象的引用特权,与其他所有内部类完全一样。因为静态类生成在外部引用类形成之前。

外围类的静态方法中使用内部类可以使用外围类中定义的内部类,但给内部类必须是静态的。因为静态方法只能使用定义该静态方法的类的静态变量。

class ArrayAlg {

...

  public static class Pair {

  ...

  }

  public static Pair minmax(double[] values) {

  ....

  return new Pair(min, max) ;

  }

}

代理:

代理是Java SE 1.3新增的特性。

利用代理可以在运行时创建一个实现了一组给定接口的新类。

代理类可以在运行时创建全新的类,这样的代理类能够实现指定的接口。尤其是,它具有下列方法:

l 指定接口所需要的全部方法

l Object类中的全部方法,例如,toStringequals

然而,不能在运行时定义这些方法的新代码。而是要提供一个调用处理器。调用处理器是实现了InvocationHandler接口的对象。这个接口中只有一个方法:

Object invoke(Object proxy, Method method, Object[] args)

无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并且向其传递Method对象和原始的调用参数。调用处理器必须给出处理器调用方式。

要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。这个方法有三个参数:

l 一个类加载器。目前,用null表示默认的类加载器

l 一个Class对象数组,每个元素都是需要实现的接口

l 一个调用处理器

使用代理的原因:

l 路由对远程服务器的方法调用

l 在程序运行期间,将用户接口事件与动作关联起来

l 为调试,跟踪方法调用

要点:

传入一组接口类对象,运行时才能确定接口的实现方法。这样可以直接使用接口,解决接口不能直接实例化的问题。

 代理类的特性:

所有的代理类都扩展于Proxy类。一个代理类只有一个实例域——调用处理器,它定义在Proxy的超类中。为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中。

所有的代理类都覆盖了Object类中的方法toStringequalshashCode。如同所有的代理方法一样,这些方法仅仅调用了调用处理器的invokeObject的其他方法没有被重新定义。

对于特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说,如果使用同一个类加载器和接口数组调用两次newProxyInstance方法的话,那么只能得到同一个类的两个对象,也可以利用getProxyClass方法获得这个类:

Class proxyClass = Proxy.getProxyClass(null, interface) ;

代理类一定是publicfinal。如果代理类实现的所有接口都是public,代理类就不属于某个特定的包;否则,所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。

可以通过调用isProxyClass方法检测一个特定的Class对象是否代表一个代理类。

原文地址:https://www.cnblogs.com/chwy/p/5700812.html