1、在接口声明中,接口中的所有 方法都自动地是 public。不过,在实现接口时, 必须把方法声明为 public。
接口绝不能含 有实例域。
Java 程序设计语言是一种强类型(strongly typed) 语言。在调用方法的时 候, 编译器将会检查这个方法是否存在。
如果 x 是一个 Employee 对象,y 是一个 Manager 对象, 调用 x.compareTo(y) 不会抛出异常, 它只是将 x 和 y 都作为雇员进行比较。 但是反过来, y.compareTo(x) 将会抛出一个 ClassCastException。这不符合“ 反对称” 的规则。两种修改情况:
1)如果子类之间的比较含义不一样, 那就属于不同类对象的非法比较。 每个 compareTo 方法都应该在开始时进行下列检测: if (getClassO != other.getClassO) throw new ClassCastExceptionO;
2)如果存在这样一种通用算法, 它能够对两个不同的子类对象进行比较, 则应该在超 类中提供一个 compareTo 方法,并将这个方法声明为 final 。
2、 接口特性
接口不是类,尤其不能使用 new运算符实例化一个接口: x = new Comparable(. . .); // ERROR
然而, 尽管不能构造接口的对象,却能声明接口的变量: Comparable x; // OK
接口变量必须引用实现了接口的类对象: x = new Employee(. . .); // OK provided Employee implements Comparable
可以使用 instance 检查一个对象是否实现了某个特定的接口: if (anObject instanceof Comparable) { . . . }
与可以建立类的继承关系一样,接口也可以被扩展。这里允许存在多条从具有较高通用 性的接口到较高专用性的接口的链。例如,假设有一个称为 Moveable 的接口: public interface Moveable { void move(double x, double y); }
然后,可以以它为基础扩展一个叫做 Powered 的接口: public interface Powered extends Moveable { double milesPerCallonO;
虽然在接口中不能包含实例域或静态方法,但却可以包含常量。例如: public interface Powered extends Moveable { double milesPerCallonO; double SPEED.LIHIT = 95; // a public static final constant }
与接口中的方法都自动地被设置为 public—样,接口中的域将被自动设为 public static final。
3、 接口与抽象类
使用抽象类表示通用属性存在这样一个问题: 每个类只能扩展于一个类。假 设 Employee类已经扩展于一个类, 例如 Person, 它就不能再像下面这样扩展第二个类了: class Employee extends Person, Comparable // Error 但每个类可以像下面这样实现多个接口: class Employee extends Person implements Comparable // OK 。
4、默认方法default
可以为接口方法提供一个默认实现。必须用 default 修饰符标记这样一个方法。
public interface Comparable{
default int compareTo(T other) { return 0; } // By default, all elements are the same
}
当然, 这并没有太大用处, 因为 Comparable 的每一个实际实现都要覆盖这个方法。
5、解决默认方法冲突
如果先在一个接口中将一个方法定义为默认方法, 然后又在超类或另一个接口中定义了 同样的方法, 会产生二义性:
1 ) 超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会 被忽略。
2 ) 接口冲突。 如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且 参数类型(不论是否是默认参数)相同的方法, 必须覆盖这个方法来解决冲突。
6、回调callback
在回调模式中,可以指出某个特定事件发 生时应该采取的动作。
在 java.swing 包中有一个 Timer 类,可以使用它在到达给定的时间间隔时发出通告。
在构造定时器时,需要设置一个时间间隔,并告之定时器,当到达时间间隔时需要做些 什么操作。 其他常用是定义一个函数然后调用,在 Java标准类库中的类采用的是面向对象方法。它将某个类的对象传递 给定时器,然后,定时器调用这个对象的方法。由于对象可以携带一些附加的信息,所以传递一个对象比传递一个函数要灵活得多。
例:每隔10s打印一条信息
class TinePrinter implements ActionListener {
public void actionPerformed(ActionEvent event) {
System.out.printlnfAt the tone, the time is " + new OateO);
Toolkit.getDefaultToolkit().beep();
}
}
接下来, 构造这个类的一个对象, 并将它传递给 Timer 构造器。
ActionListener listener = new TimePrinter();
Timer t = new Timer(10000, listener);
最后, 启动定时器:
t.start();
7、对象克隆
clone 方法是 Object 的一个 protected 方法,这说明你的代码不能 直接调用这个方法。只有 Employee 类可以克隆 Employee 对象。
Cloneable 接口的出现与接口的正常使用并没有关系。具体来说,它没有指定 done方法,这个方法是从 Object 类继承的。这个接口只是作为一个标记,指示类设计者了 解克隆过程。标记接口不包含任何方法; 它唯一的作用就是允许 在类型查询中使用 instanceof。。对象对于克隆很“ 偏执”, 如果一个对象请求克隆, 但没有实现这个接口, 就 会生成一个受査异常。
8、 lambda 表达式
lambda 表达式是一个可传递的代码块, 可以在以后执行一次或多次。
在 Java 中传递一个代码段并不容易, 不能直接传递代码段 _ Java 是一种面 向对象语言,所以必须构造一个对象,这个对象的类需要有一个方法能包含所需的代码。
lambda 表达式与这些接口是兼容的, 对于只有一个抽象方法的接口, 需要这种接口的对象时, 就可以提供一个 lambda 表达 式。这种接口称为函数式接口 (functional interface)。
函数式接口:它的第二个参数需要一个 Comparator 实例, Comparator 就是只有一个方法的接口, 所以可以提供一个 lambda 表达式: Arrays.sort(words, (first, second) -> first.lengthO - second.lengthO);
方法引用:能已经有现成的方法可以完成你想要传递到其他代码的某个动作。Timer t = new Timer(1000, event -> System.out.println(event)):
构造器引用(方法new):例如,int[]::new 是一个构造器引用,它有一个参数: 即数组的长度。这等价于 lambda 表达式 x-> new int[x]。Java 有一个限制,无法构造泛型类型 T 的数组。数组构造器引用对于克服这个限制很有 用。表达式 new T[n]会产生错误,因为这会改为 new Object[n]。
变量作用域: lambda 表达式有 3 个部分: 1 ) 一个代码块; 2 ) 参数; 3 ) 自由变量的值, 这是指非参数而且不在代码中定义的变量。 这个 lambda 表达式有 1 个自由变量 text。表示 lambda 表达式的数据 结构必须存储自由变量的值。
在 lambda 表达式中, 只能引用值不会改变的 变量。lambda 表达式就是闭包。
lambda 表达式中捕获的变量必须实际上是最终变量 (effectivelyfinal)。 实际上的最终变量是指, 这个变量初始化之后就不会再为它赋新值。
9、内部类
引用内部类原因:
1)内部类方法可以访问该类定义所在的作用域中的数据, 包括私有的数据。
2)内部类可以对同一个包中的其他类隐藏起来。
3)当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous) 内部类比较 便捷。
嵌套是一种类之间的关系, 而不是对象之间的关系。嵌套类有两个好处: 命名控制和访问控制。
内部类的对象有一个隐式引用, 它引用了实例化该内部对象的外围类对象。通 过这个指针, 可以访问外围类对象的全部状态。
10、编译器修改了所有的内部类的构造器, 添加一个外围类 引用的参数。因为 TimePrinter 类没有定义构造器,所以编译器为这个类生成了一个默认的构 造器,其代码如下所示:
public TimePrinter(TalkingGock clock) // automatically generated code
{
outer = clock;
}
请再注意一下, outer 不是 Java 的关键字。我们只是用它说明内部类中的机制 。
11、内部类的特殊语法规则
表达式 OwterC/ass.this 表示外围类引用。通常,this 限定词是多余的。不过,可以通过显式地命名 将外围类引用设置为其他的对象。
内部类中声明的所有静态域都必须是 final。原因很简单。我们希望一个静态域只 有一个实例, 不过对于每个外部对象, 会分别有一个单独的内部类实例。如果这个域不 是 final, 它可能就不是唯一的。
内部类不能有 static 方法。Java 语言规范对这个限制没有做任何解释。也可以允许有 静态方法,但只能访问外围类的静态域和方法。
12、局部内部类
public void start0 {
class TiiePrinter inpleients ActionListener {
public void actionPerforaed(ActionEvent event) {
Systei.out.println("At the tone, the tine is " + new DateO);
if (beep) Toolkit.getDefaultToolki10•beep():
}
}
…
}
局部类不能用 public 或 private访问说明符进行声明。它的作用域被限定在声明这个局部 类的块中。 局部类有一个优势, 即对外部世界可以完全地隐藏起来。 即使 TalkingClock 类中的其他 代码也不能访问它。除 start 方法之外, 没有任何方法知道 TimePrinter 类的存在。
13、由外部方法访问变量
与其他内部类相比较, 局部类还有一个优点。它们不仅能够访问包含它们的外部类, 还 可以访问局部变量。不过,那些局部变量必须事实上为 final。这说明,它们一旦赋值就绝不会改变。
public void start(int interval, boolean beep) {
class TimePrinter implements ActionListener {
public void actionPerformed(ActionEvent event) {
Systea.out.println("At the tone, the tiie is " + new DateO);
if (beep) Toolkit.getDefaultToolki10•beep();
}
}
ActionListener listener = new TimePrinterO;
Timer t = new Timer(interval,listener);
t.startO;
}
TalkingClock 类不再需要存储实例变量 beep 了,它只是引用 start 方法中的 beep 参数变量。
毕竟在 start 方法内部,为什么不能访问 beep 变量的值呢:
1 ) 调用 start 方法。
2) 调用内部类 TimePrinter 的构造器, 以便初始化对象变量 listener。
3 ) 将 listener 引用传递给 Timer 构造器,定时器开始计时, start 方法结束。此时,start 方法的 beep 参数变量不复存在。
4 ) 然后,actionPerformed方法执行 if(beep)…。
为了能够让 actionPerformed方法工作,TimePrinter 类在 beep 域释放之前将 beep 域用 start 方法的局部变量进行备份。
编译器必须检测对局部变量的访问, 为每一个变量建立相应的数据域, 并将局部变量拷贝到构造器中, 以便将这些数据域初始化为局部变量 的副本。
14、 匿名内部类
它的含义是:创建一个实现 ActionListener 接口的类的新 对象,需要实现的方法 actionPerformed定义在括号内。 通常的语法格式为:
new SuperType(constructionparameters) {
inner class methodsanddata
}
其中, SuperType 可以是 ActionListener 这样的接口, 于是内部类就要实现这个接口。 SuperType 也可以是一个类,于是内部类就要扩展它。
由于构造器的名字必须与类名相同, 而匿名类没有类名,所以,匿名类不能有构造器。 取而代之的是,将构造器参数传递给超类(superclass) 构造器。尤其是在内部类实现接口的 时候, 不能有任何构造参数。不仅如此,还要像下面这样提供一组括号:
new InterfaceType(){
methodsanddata
}
15、静态内部类
有时候, 使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用 外围类对象。为此,可以将内部类声明为 static, 以便取消产生的引用。
16、代理
利用代理可以在运行时创建一个实现了一组给 定接1J 的新类 : 这种功能只有在编译时无法确定需要实现哪个接 U时才冇必要使用。
创建代理对象:需要使用 Proxy 类的 newProxylnstance 方法。这个方法有三个 参数:
1)一个类加栽器(class loader)。作为 Java 安全模型的一部分, 对于系统类和从因特网 上下载下来的类,可以使用不同的类加载器。用 null 表示使用默认的类加载器。
2)一个 Class对象数组, 每个元素都是需要实现的接口。
3)一个调用处理器。
代理类的特性:
1) 代理类是在程序运行过程中创建的。然而, 一旦被创建, 就变成了常规类,与虚拟机中的任何其他 类没有什么区别。
2)所有的代理类都扩展于 Proxy类。一个代理类只有一个实例域— —调用处理器,它定义 在 Proxy 的超类中。
3)所有的代理类都覆盖了 Object 类中的方法 toString、equals 和 hashCode。如同所有的代 理方法一样,这些方法仅仅调用了调用处理器的 invoke。Object 类中的其他方法(如 clone 和 getClass) 没有被重新定义。
4)没有定义代理类的名字,Sun 虚拟机中的 Proxy类将生成一个以字符串 SProxy开头的类名。
5)对于特定的类加载器和预设的一组接口来说,只能有一个代理类。
6)代理类一定是 public 和 final。如果代理类实现的所有接口都是 public, 代理类就不属于 某个特定的包;否则, 所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。
Java核心技术-接 口、lambda 表达式与内部类
最新推荐文章于 2021-06-27 16:08:40 发布