Java核心技术 卷一
6.1 接口
- 接口概念
在Java程序设计语言中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。
例如:
public interface Comparable
{
int compareTo(Object other);
}
任何实现Comparable接口的类都需要包含compareTo方法,并且这个方法的参数必须是一个Object对象,返回一个整型数组。
接口中的所有方法自动属于public。因此,在接口中声明方法时,不必提供关键字public。
为了让类实现一个接口,需要下面两个步骤:
1.将类声明为实现给定的接口。
2.对接口中的所有方法进行定义。
例如:
public int compareTo(Object otherObject)
{
Employee other = (Employee) otherObject;
return Double ,compare(sal ary, other,sal ary);
}
- 接口的特性
接口不是类,尤其不能使用new运算符实例化一个接口,然而,尽管不能构造接口的对象,却能声明接口的变量。
Comparable x; // OK
接口变量必须引用实现了接口的类对象:
x = new Employee(. . .); // OK provided Employee implements Comparable
接口也可以拓展:
例如:假设有一个称为Moveable的接口
public interface Moveable
{
void move(double x, double y);
}
然而,可以以它为基础拓展一个叫做Powered的接口:
public interface Powered extends Moveable
{
double milesPerCallon();
}
虽然在接口中不能包含实例域或静态方法,但却可以包含常量。
public interface Powered extends Moveable
{
double milesPerCallon();
double SPEED. LIHIT = 95; // a public static final constant
}
与接口的方法都自动地被设置为public一样,接口中地域将被自动设为public static final。
- 接口与抽象类
- 静态方法
目前为止,通常做法斗志将静态方法房子伴随类中。
例如:
public interface Path//Path.get("jdk1.8.0", "jre", "bin")
{
public static Path get(String first, String... more) {
return Fi1eSystems.getDefault().getPath(first, more);
}
...
}
- 默认方法
可以为接口方法提供一个默认实现。必须使用default修饰符。
例如:
public interface Comparable<T>
{
default int compareTo(T other) { return 0; }
// B y default, all elements are the same
}
有些时候默认方法很有用。例如,如果希望在发生鼠标点击时间时得到通知,就要实现包含5个接口:
public interface MouseListener
{
void mousedieked(MouseEvent event);
void mousePressed(MouseEvent event);
void mouseReleased(MouseEvent event);
void mouseEntered(MouseEvent event);
void mouseExited(MouseEvent event);
}
可以把所有方法声明为默认方法,这些默认方法什么也不做:
public interface MouseListener
{
default void mousedieked(MouseEvent event) {}
default void mousePressed(MouseEvent event) {}
default void mouseReleased(MouseEvent event) {}
default void mouseEntered(MouseEvent event) {}
default void mouseExited(MouseEvent event) {}
}
默认方法可以调用任意其他方法。
例如,Collection接口可以定义一个便利方法:
public interface Collection
{
int size(); // An abstract method
default boolean isEmpty()
{
return size() == 0;
}
...
}
这样实现Collection地程序员就不用操心实现isEmpty方法了。
- 解决默认方法冲突
规则:
1.超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型地默认方法回被忽略。
2.接口冲突。如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同地方法,必须覆盖这个方法来解决冲突。
6.2 接口示例
- 接口与回调
回调是一种常见的程序设计模式。在模式中,可以指出某个特定事件发生时应该采取的动作。
- Comparator接口
- 对象克隆
Cloneable接口中提供了一个安全地clone方法。
对于每一个类,需要确定:
1. 默认的 clone 方法是否满足要求;
2.是否可以在可变的子对象上调用 clone 来修补默认的 clone 方法;
3. 是否不该使用 clone
实际上第 3 个选项是默认选项。如果选择第 1 项或第 2 项,类必须:
1.实现 Cloneable 接口;
2 .重新定义 clone 方法,并指定 public 访问修饰符。
6.3 lambda表达式
- 为什么引入lambda表达式
lambda表达式时一个可传递地代码块,可以在以后执行一次或多次。
了解了如何按指定时间间隔完成工作,将这个工作放在一个ActionListener 的 actionPerformed方法中:
class Worker implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
// do some work
}
}
想要反复执行相和歌代码时,可以构造Worker类地一个实例。然后把这个实例提交给一个Timer对象。
如果想按长度而不是默认的字典顺序对字符串排序,可以向sort方法传入一个Comparator对象:
class LengthComparator implements Comparator<String>
{
public int compare(String first, String second)
{
return first.lengthQ - second.length();
}
}
. .
Arrays.sort(strings, new LengthComparator());
compare不是立即调用。实际上,在数组完成排序之前,sort方法会一直调用compare方法,只要元素地顺序不正确就会重新排列元素。
- lambda表达式地语法
例如:
(String first, String second)
-> first.length() - second.length()
lambda表达式就是一个代码块,以及必须传入代码地变量规范。
(String first, String second) ->
{
if (first.length()< second.length()) return -1;
else if (first.length() > second.length()) return 1;
else return 0;
}
即使lambda表达式没有参数,仍然要提供空括号,就像无参方法一样:
() -> { for (int i = 100;i >= 0;i ) System.out.println(i); }
- 函数式接口
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这种接口称为函数式接口。
Timer t = new Timer(1000, event ->
{System.out.println("At the tone,thetime is " + new Date());
Toolkit.getDefaultToolkit().beep();
};
BiFunction<String, String, Integer> comp
= (first, second) -> first.length() - second.length();
- 方法引用
例如,假设你希望 只要出现一个定时器时间就打印这个事件对象,可以调用:
Timer t = new Timer(1000, event -> System.out.println(event)):
将print方法传递到Timer构造器就更好了。
Timer t = new Timer(1000, Systey.out::println);
表达式Systey.out::println是一个方法引用。等价于lambda表达式x 一> System.out.println(x)
假设想对字符串排序,而不考虑字母的大小写。
Arrays.sort(strings,String::conpareToIgnoreCase)
使用::操作符分隔方法名与对象或类名。主要有3种情况:
•object::instanceMethod
•Class::staticMethod
•Class::instanceMethod
可以在方法引用中使用this参数。例如,this::equals 等同于 x -> this.equals(x)。
使用super也是合法的:super::instanceMethod
例如:
class Greeter
{
public void greet()
{
System.out.println("Hello, world!");
}
}
class TimedCreeter extends Greeter
{
public void greet()
{
Timer t = new Timer(1000, super::greet);
t.start();
}
}
- 构造器引用
构造器引用与方法很类似,只不过方法名为new。例如,Person::new是Person构造器的一种引用。
假设你有一个字符串列表,可以把它转换为一个Person对象数组,为此要在各个字符串上调用构造器,调用如下:
ArrayList<String> names = . . .;
Stream<Person> stream = names.stream().map(Person::new);
List<Person> people = stream.collect(Collectors.toList());
可以用数组类型建立构造器引用。例如,int[]::new 是一个构造器引用,它有一个参数:即数组的长度。这等价于 lambda 表达式 x -> new int[x]。
Java有一个限制,无法构建泛型类型T的数组。
- 变量作用域
lambda表达式由3个部分:
1.一个代码块;
2.参数;
3.自由变量的值,这是指非参数而且不在代码中定义的变量。
- 处理lambda表达式
使用lambda表达式的重点是延迟执行。如果想要立即执行代码,完全可以可以直接执行,而无需把它包装在一个lambda表达式中。
原因:
1.在一个单独的线程中允许代码;
2.多次运行代码;
3.在算法的适当位置允许代码;
4.发生某种情况时执行代码;
5.指在必要时才运行代码。
假设,你想重复一个动作n次。将这个动作和重复次数传递带一个repeat方法:
repeat(10, () -> System.out.println("Hello, World!"));
6.4 内部类
内部类时定义在另一个类中的类。
使用原因:
1.内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
2.内部类可以对同一个包中的其他类隐藏起来。
3.当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
嵌套是一种类之间的关系,而不是对象之间按的关系。嵌套类有两个好处:命名控制和访问控制。
- 使用内部类访问对象状态
以TimerTest示例,并抽象出一个TalkingClock类。构造一个语音时钟时需要提供两个参数:发布通告的间隔和开关铃声的标志。
public class TalkingClock
{
private int interval:
private boolean beep;
public TalkingClock(int interval, boolean beep) { . . . }
public void start() { . . . }
public class TimePrinter implements ActionListener
// an inner class
{
...
}
}
需要注意,这里的TimePrinter类位于TalkingClock类内部。
actionPerformed方法在发生铃声之前检查了beep标志。
public class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone, thetimeis" + new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
TimePrinter类没有实例域或者名为beep的变量,取而代之的是beep引用了创建TimePrinter的TalkingClock对象的域。
内部类的特殊语法规则
外围类引用的正规语法:OuterClass.this
例如:
public void actionPerformed(ActionEvent event)
{
...
if (TalkingClock.this.beep) Toolkit.getDefaultToolkitO.beep();
}
采用下列语法格式更明确地编写内部对象的构造器:
outerObject.new InnerClass(construction parameters)
例如:
ActionListener listener = this.new TimerPrinter();
TalkingClock jabberer = new TalkingClock(1000, true);
TalkingOock.TiiePrinter listener= jabberer.newTimePrinter();
内部类中声明的所有静态域都必须是final。
内部类不能用static方法。
- 内部类是否有用、必要和安全
- 局部内部类
- 由外部方法访问变量
局部类不仅能够访问包含他们的内部类,还可以访问局部变量。
例如:将TalkingClock 构造器的参数 interval和 beep 移至 start方法中。
public void start(int interval, boolean beep)
{
class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone, the time is" +new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
流程:
1.调用start方法
2.调用内部类TimePrinter的构造器,一边初始化对象变量listener
3.将listener引用传递给Timer构造器,定时器开始计数时,start方法结束。
4.然后actionPerformed方法执行if
- 匿名内部类
只创建这个类的一个对象,就不必命名,这种类被称之为匿名内部类。
public void start(int interval, boolean beep)
{
ActionListener listener = new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone, the time is" + new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
}
};
Timer t = new Timer(interval, listener);
t.start();
}
含义:创建一个实现ActionListener接口的类的新对象,需要实现的方法actionPerformed定义在括号{}内。
语法格式:
new SuperType(construction parameters)
{
inner class methods and data
}
- 静态内部类
将内部类声明为static。
考虑一下计算数组中的最小值和最大值的问题。同时计算出最小值和最大值。
double min = Double.POSITIVE_INFINITY;
double max = Double.NECATIVE_INFINITY;
for (double v : values)
{
if (min > v) min = v;
if (max < v) max = v;
}
class ArrayAlg
{
public static Pair minmax(doublet] values)
{
…
return new Pair(min, max) ;
}
}
使用方法:
Pair p = ArrayAlg.minmax(d);
System,out.println("min = " + p.getFirst());
System,out.println("max = " + p.getSecond());
6.5 代理
利用代理可以在运行时创建一个实现了一组给定接口的新类。
- 何时使用代理
代理具有下列方法:
1.指定接口所需要的全部方法。
2.Object类中的全部方法。
- 创建代理对象
想要创建一个代理对象,需要使用Proxy类的newProxyInstance方法。参数为:
1.一个类加载器。
2.一个Class对象数组,每个元素都是需要实现的接口。
3.一个调用处理器。