核心技术:接口、lambda表达式与内部类
接口
接口(interface),是对类的一组需求描述(一组行为定义):
- 接口的所有方法自动属于public(不用提供public关键字);
- 接口中可以有常量,但是不能有实例域;
- 即,域自动设置为:“public static final”
- 接口中可以有实现方法(Java SE 8 之后),但方法中不能引用实例域;
- 默认:“public abstract”
提供实例域和方法实现,应该由实现接口的类来完成。
实现接口:
- 声明实现给定的接口:
class Employee implements Comparable
- 对接口中的所有方法进行定义;
- 所有方法都必须有实现;
- 实现方法时,必须把方法声明为“public”;
接口的特性
- 接口不是类,不能用“new”进行实例化;
- 可以用接口声明变量,变量只能引用接口实现类的对象;
- 可以使用“instanceof”检查一个对象是否实现了某个特定接口(类似于检查对象是否属于某个类);
- 接口可以被继承,用新的接口扩展旧接口;
接口与抽象类
Java不支持多重继承(multiple inheritance):引入抽象类,避免多重继承的复杂性和低效性。
静态方法
Java 8 之后,允许接口中增加静态方法:
- 静态方法无法实现多态,形式上重写,实际上是新方法。
默认方法
可以为接口方法提供一个默认实现(也可以不实现),并用“default”修饰该方法:
- 可以不用实现接口所有方法,只关注于需要的某个或某几个方法(可以不再使用伴随类):
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) {} }
- 在为旧接口增加方法时,使用默认方法可以保证“源代码兼容”(source compatible);
- (增加非默认方法,会导致接口现有实现类不能正常工作)
关于伴随类
在JavaAPI 中,你会看到很多接口都有相应的伴随类,这个伴随类中实现了相应接口的部分或所有方法, 如 Collection/AbstractCollection 或 MouseListener/MouseAdapter。在JavaSE 8 中,这个技术已经过时。现在可以直接在接口中实现方法。
使用抽象类(伴随类)对接口进行部分实现,而后用户类并不之间实现接口(需要实现全部方法),而是继承与该抽象类(只重写需要的类即可)。如:
public interface Collection<E> extends Iterable<E> { public abstract class AbstractCollection<E> implements Collection<E> {
public interface MouseListener extends EventListener { public abstract class MouseAdapter implements MouseListener, MouseWheelListener, MouseMotionListener {
- 又见,SpringMVC 中的拦截器(见[1]):
public interface HandlerInterceptor { public interface AsyncHandlerInterceptor extends HandlerInterceptor { public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
解决默认方法冲突
冲突:现在接口中将一个方法定义为默认方法,又在超类或另一个接口中定义了同样的方法;
规则:
- 超类优先:接口中的方法(不论是否默认方法)会被忽略;
- 接口冲突:一个接口提供默认方法,一个接口提供同名同参数方法(不论是否默认方法),则在实现类必须覆盖这个方法;
接口示例
接口与回调
回调的思想是:
- 类A的a()方法调用类B的b()方法
- 类B的b()方法执行完毕主动调用类A的callback()方法
- 回调的核心就是回调方将本身即this传递给调用方
回调与接口:
- 将回调方法抽象为接口,用回调方去实现需要的回调接口
Comparator 接口
- 若需要将对象数组排序,则必须数组的每一个元素都实现了Comparable 接口,并实现其 compareTo 方法:
package java.lang; import java.util.*; public interface Comparable<T> { public int compareTo(T o); }
然而,如果需要对 String 数组排序(已经实现了 Comparable<String> 接口),但又不想按照字典顺序排序(String.compareTo 的实现是按字典顺序比较):
- 首先我们不能使 String 类实现两种不同的 compareTo 方法,
- 其次 String 类也不应该由我们修改。
即:如何使用 Comparable.compareTo 方法之外的比较方法,来进行对象数组的排序?【如何自定义排序】
所以,需要用到:Comparator<T> 接口(不同于 Comparable):
package java.util; ... public interface Comparator<T> { int compare(T o1, T o2); ... }
- Comparator<T> 接口的方法很多,但并不用全部实现,因为一部分方法有实现,一部分为默认方法;
示例一:对 String 数组自定义排序:
- 定义一个 Comparator<String> 接口的实现类:
class LengthComparator implements Comparator<String> { public int compare(String first, String second) { return first.length() - second.length(); } }
- 使用 Arrays.sort 进入数组比较:
String[] friends = { "Peter", "Paul", "Mary" }; Arrays.sort(friends, new LengthComparator()):
- Arrays.sort 是静态方法,可以类名直接调用;
// 对 Arrays 默认排序 public static <T> void sort(T[] a) // 对 Arrays 使用自定义排序(Comparator) public static <T> void sort(T[] a, Comparator<? super T> c)
示例二:单独对两个对象或两个数组元素进行比较:
Comparator<String> comp = new LengthComparator(); if (conp.compare(words[i], words[j]) > 0) ...
- Comparator.compare 并非静态方法,所以使用时需要 Comparator 实例;
利用lambda 表达式可以更容易地使用 Comparator
对象克隆
拷贝和克隆
- 为一个包含对象引用的变量建立副本,直接赋值即可,原件副本都是同一个对象的引用;
- 若希望为对象也创建一个副本,则应该使用克隆;
浅拷贝(Object.clone())
默认的克隆操作(“Object.clone()”)是“浅拷贝”:如果对象包含子对象的引用,拷贝域会得到相同子对象的另一个引用;
- 即,原对象域浅克隆对象会引用相同的子对象;
- Object.clone() 是Object的一个protected方法;
- 若,子对象属于一个不可变的类,或在生命周期中状态不变,则是安全的;否则不应该使用浅拷贝;
深拷贝(Cloneable 接口)
Cloneable 接口时Java提供的一组标记接口(tagging interface,或称记号接口 marker interface):
- 标记接口不包含任何方法,它的唯一作用就是允许在类型查询中使用 instanceof;
- 即使 clone 的默认实现(浅拷贝)满足要求,还是需要实现 Cloneable 接口,将 clone 重新定义为 public,再调用 super.clone():
class Employee implements Cloneable
{
// raise visibility level to public, change return type
public Employee clone() throws CloneNotSupportedException
{
return (Employee) super.clone();
}
...
}
深克隆的实现:
- 实现 Cloneable 接口,将 clone 重新定义为 public;
- 重写 clone 方法,克隆需要的子对象(分别调用子对象的克隆方法);
class Employee implements Cloneable
{
...
public Employee cloneO throws Cl oneNotSupportedExcepti on
{
try {
// call Object, clone0
Employee cloned = (Employee) super.cloneO ;
// clone mutable fields
cloned.hireDay = (Date) hireDay.clone() ;
return cloned;
} catch (CloneNotSupportedException e) {
return null;
}
// this won't happen, since we are Cloneable
}
}
lambda
为什么引入 lambda
lambda :带参数变量的表达式,用于传递一个代码块到某个对象;
- Java 是面向对象的语言,所以必须构造一个对象,用对象的类的方法来包含所需的代码块;
lambda表达式的语法
lambda 表达式形式:
参数 -> 表达式
如:
(String first, String second) -> { if (first.length() < second.length()) return -1; else if (first.length() > second.length()) return 1; else return 0; }
参数:
- 如果没有参数,仍然要保留括号。
- 比如:
() -> { for (int i = 100; i >= 0;i ) System.out.println(i); }
- 如果可以推导出参数类型,可以忽略参数类型。
- 比如:
Comparator<String> comp = (first, second) // Same as (String first, String second) -> first.length() - second.length();
- 如果方法只有一个参数,且参数类型可以推导,可以只保留参数名。
- 比如:
ActionListener listener = event -> System.out.println("The time is " + new Date()"); // Instead of (event) -> . . . or (ActionEvent event) -> . . .
表达式:
- 无需指定 lambda 表达式的返回类型。【lambda 表达式的返回类型总是会由上下文推导得出】
- 比如:如下表达式可以在 int 类型结果的上下文使用:
(String first, String second) -> first.length() - second.length()
- lambda 表达式不能只在某些分支返回值,而存在分支不返回值。
- 如下,是不合法的:
(int x)-> { if (x >= 0) return 1; }
函数式接口
函数式接口:有且仅有一个抽象方法,但可以有多个非抽象方法 的接口。(即,接口“只有一个需要实现的方法”)
- 接口中没有具体实现、没有“public static”修饰的也是抽象方法,因为 接口中的方法默认为“public abstract”(见“接口”一节的描述);
- Java API 在 java.util.function 包中定义很多非常通用的函数式接口;
需要函数式接口的对象时,可以提供一个 lambda 表达式。【即,用 lambda 表达式来代替函数式接口的抽象方法(“那个唯一需要实现的方法”)】
- 最好把 lambda 看作是一个函数,而不是一个对象;
- Java中,lambda所能做的也只是能转换为函数式接口;
设计函数式接口时,可以使用“@FunctionalInterface”注解来标记这个接口。这样做:
- 当接口不符合函数式接口定义时(如:无意中增加了另一个抽象方法),编译器会报错;
- javaDoc 页中会指示该接口是一个函数式接口;
示例1:
Arrays.sort (words ,
(first , second) -> first.length() - second.length()) ;
- Arrays.sort 方法的第二个参数为 Comparator<String>接口的实现类对象;
- 该 lambda表达式 即为 “Comparator<String>”接口的“compare(T, T)”方法的实现;
示例2:
Timer t = new Timer(1000, event ->
{
System.out.println("At the tone, the time is " + new DateO);
Toolkit.getDefaultToolkit().beep();
});
- Timer 构造函数的第二个参数为 ActionListener接口的实现类对象;
- 该 lambda表达式 即为 “ActionListener”接口的“actionPerformed(ActionEvent e)”方法的实现;
示例3:
list.removelf(e -> e == null);
- (list 对象属于 ArrayList类)
- list.removelf 方法(removeIf(Predicate<? super E> filter))的参数为 “Predicate<T>”接口的实现类对象;
- 该 lambda表达式 即为 “Predicate<T>”接口的“boolean test(T t)”方法的实现:
方法引用
利用已有类的方法,来实现函数式接口的抽象方法。形式:
类或对象::方法
- 函数接口抽象方法的参数列表,必须与 方法引用的参数列表 一致
主要有三种情况:
object::instanceMethod
Class::staticMethod
Class::instanceMethod
- 对于前两种,方法引用等价于提供方法参数的 lambda 表达式:
System.out::println
等价于x -> System.out.println(x);
Math::pow
等价于(x, y) -> Math.pow(x, y)
- 对于第三种:第一个参数会成为方法的目标【?即:“para1.instanceMethod(para2)”】:
String::compareToIgnoreCase
等价于(x, y) -> x.compareToIgnoreCase(y)
示例:
Timer t = new Timer(1000, Systei.out::println); // 等同于 Timer t = new Timer(1000, event -> System.out.println(event));
Arrays.sort(strings,String::conpareToIgnoreCase); // 等同于 Arrays.sort(strings,(x, y) - > x.compareToIgnore(y));
构造器引用
构造器引用 与方法引用类似,只不过方法名为“new”,即:
类或对象::new
- 可以用数组类型建立构造器引用。
- 例如,“
int[]::new
”是一个构造器引用,它有一个参数(即数组的长度)。等价于lambda 表达式“x-> new int[x]
”;
- 例如,“
示例:(代码中的“stream”得看核心技术卷2)
ArrayList<String> names = . . .;
Stream<Person> stream = names.stream().map(Person::new);
List<Person> people = stream.col1ect(Col1ectors.toList());
map 方法会为各个列表元素调用 Person(String) 构造器。如果有多个 Person 构造器,编译器会选择有一个 String 参数的构造器, 因为它从上下文推导出这是在对一个字符串调用构造器。
关于Java中创建泛型数组:(泛型是Java的语法糖,在编译时会被替换为实际类型)
Java 有一个限制,无法构造泛型类型 T 的数组。数组构造器引用对于克服这个限制很有用。表达式“new T[n]”会产生错误,因为这会改为“new Object[n]”。 对于开发类库的人来说,这是一个问题。例如, 假设我们需要一个 Person 对象数组。 Stream 接口有一个 toArray 方法可以返回 Object 数组: Object[] people = stream.toArray(); 不过,这并不让人满意。用户希望得到一个 Person 引用数组,而不是 Object 引用数组。流库利用构造器引用解决了这个问题。可以把 Person[]::new 传人 toArray 方法: Person[] people = stream.toArray(Person::new); toArray 方法调用这个构造器来得到一个正确类型的数组。然后填充这个数组并返回。
变量作用域
- 关于“闭包”:闭包就是能够读取其他函数内部变量的函数。如:Java中的lambda,javascript中函数内部的子函数;
lambda 表达式的三个部分:
- 一个代码块
- 参数
- 自由变量的值(指非参数,且不在lambda中定义的变量)
- 自由变量,能使在 lambda 表达式中访问外围方法或类中的变量:
public static void repeatMessage(String text, int delay) { ActionListener listener = event -> { System.out.println(text): Toolkit.getDefaultToolkit().beep() : }; new Timer(delay, listener).start(); }
- 调用 repeatMessage("Hello", 1000); 则会每秒打印一个 Hello;
- lambda 表达式中捕获的变量必须实际上是最终变量(effectivelyfinal)。,即这个变量初始化之后就不会再为它赋新值:
public static void countDown(int start, int delay) { ActionListener listener = event -> { start--; // Error: Can't mutate captured variable System.out.println(start); }; new Timer(delay, listener).start(); }
- 如上,在 lambda 内改变变量 start 的值,是不合法的;
public static void repeat(String text , int count) { for (int i = 1; i <= count; i ++) { ActionListener listener = event -> { System.out.print1n(i + ": " + text) ; // Error: Cannot refer to changing i }; new Timer(1000, listener).start(); } }
- 以上同样不合法:lambda 捕获的变量 i,也不能在外部被改变;
- lambda 表达式的体与嵌套块有相同的作用域:
Path first = Paths.get("usr/Mn"); Couparator<String> comp = (first, second) -> first.length() - second.length(); // Error: Variable first already defined
- 如上,在lambda中声明与一个局部变量同名的参数或局部变量是不合法的;
- lambda 中的 this 关键字是指创建这个 lambda 表达式的方法的 this 参数;
public class Application() { public void init() { ActionListener listener = event -> { System.out.println(this.toString()); ... } ... } }
- 如上,表达式 this.toString() 会调用 Application 对象的 toString 方法,而不是 ActionListener 实例的方法;
处理lambda表达式
使用lambda 表达式的重点是延迟执行(deferred execution),如以下情况:
- 在一个单独的线程中运行代码;
- 多次运行代码;
- 在算法的适当位置运行代码(例如,排序中的比较操作);
- 发生某种情况时执行代码(如,点击了一个按钮,数据到达,等等);
- 只在必要时才运行代码;
使用 lambda 表达式,需要选择(偶尔可能需要提供)一个函数式接口:
函数式接口 | 参数类型 | 返回类型 | 抽象方法名 | 描述 | 其他方法 |
---|---|---|---|---|---|
Runnable | 无 | void | run | 作为无参数或返 | |
Supplier<T> | 无 | T | get | 提供一个 T 类型的值 | |
Consumer<T> | T | void | accept | 处理一个 T 类型的值 | andThen |
BiConsumer<T, U> | T, U | void | accept | 处理 T 和 U 类型的值 | andThen |
Function<T, R> | T | R | apply | 有一个 T 类型参数的函数 | compose, andThen, identity |
BiFunction<T, U, R> | T, U | R | apply | 有 T 和 U 类型参数的函数 | andThen |
UnaryOperator<T> | T | T | apply | 类型 T 上的一元操作符 | compose, andThen, identity |
BinaryOperator<T> | T,T | T | apply | 类型 T 上的二元操作符 | andThen, maxBy, minBy |
Predicate<T> | T | boolean | test | 布尔值函数 | and, or, negate, isEqual |
BiPredicate<T, U> | T, U | boolean | test | 有两个参数的布尔值函数 | and, or, negate |
函数式接口 | 参数类型 | 返回类型 | 抽象方法名 |
---|---|---|---|
BooleanSupplier | none | boolean | getAsBoolean |
*Supplier | none | + | getAs* |
*Consumer | + | void | accept |
Obj*Consumer<T> | T,+ | void | accept |
*Function<T> | + | T | apply |
*To#Function | + | - | applyAs# |
To*Function<T> | T | + | applyAs* |
To*BiFunction<T,U> | T, U | + | applyAs* |
*UnaryOperator | + | + | applyAs* |
*BinaryOperator | +,+ | + | applyAs* |
*Pedicate | + | boolean | test |
- “+-” 为 int, long, double; “*,#” 为 Integer, Long, Double
再谈 Comparator
Comparator 接口包含很多方便的静态方法来创建比较器。这些方法可以用于 lambda 表达式或方法引用。 以下的 comparing()、thenComparing()、nullsFirst()、nullsLast()、naturalOrder()、reverseOrder() 等方法,返回的都是一个比较器(Comparator<T>)。
- 静态 comparing 方法取一个“键提取器” 函数,它将类型 T 映射为一个可比较的类型(如 String)。对要比较的对象应用这个函数,然后对返回的键完成比较。
- 例如:假设有一个 Person 对象数组,可以如下按名字对这些对象排序:
Arrays.sort(peop1e, Comparator.comparing(Person::getName));
- 可以把比较器与 thenComparing 方法串起来,如果第一次比较相同则使用下一个比较器。
- 例如:
Arrays.sort(peop1e, Comparator.comparing(Person::getLastName) .thenComparing(Person::ge FirstName));
- 可以为 comparing 和 thenComparing 方法提取的键指定一个比较器。
- 例如:可以如下根据人名长度完成排序:
Arrays.sort(peop1e, Comparator.comparing(Person::getName, (s, t) -> Integer.compare(s.length(), t.length())));
- comparing 和 thenComparing 方法都有变体形式,可以避免 int、long 或 double 值的装箱。
- 如上例:根据人名长度完成排序,还有一种更容易的做法:
Arrays.sort(people, Camparator.comparingInt (p -> p.getName().1ength()));
- 如果键函数可以返回 null,可能就要用到 nullsFirst 和 nullsLast 适配器。这些静态方法会修改现有的比较器,从而在遇到 null 值时不会抛出异常,而是将这个值标记为小于或大于正常值。
- “nullsFirst”:null < non-null,从小到大时 null 在前;
- “nullsLast”:null > non-null,从小到大时 null 在后;
- 例如,假设一个人没有中名时 getMiddleName 会返回一个 null,就可以使用
Comparator.comparing(Person::getMiddleName(), Comparator.nullsFirst( ... ))
- nullsFirst、nullsLast 方法需要一个比较器,在这里就是比较两个字符串的比较器。
- naturalOrder 方法可以为任何实现了 Comparable 的类建立一个比较器。【自然排序,实现 Comparable 接口时方法作为比较器】
- 注意 naturalOrder 的类型可以推导得出。
- 如上例:可以按可能为 null 的中名进行排序,下面是一个完整的调用:
Arrays.sort(peop1e, comparing(Person::getMiddleNarne, nullsFirst(naturalOrder())));
- 静态 reverseOrder 方法会提供自然顺序(naturalOrder)的逆序。
- 要让比较器逆序比较,可以使用 reversed 实例方法。
- 例如:
naturalOrder().reversed()
等同于reverseOrder()
。
- 例如:
- 要让比较器逆序比较,可以使用 reversed 实例方法。
内部类
内部类( inner class ) 是定义在另一个类中的类。
使用内部类主要原因:
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
- 内部类可以对同一个包中的其他类隐藏起来。
- 想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous) 内部类比较便捷。
- 只有内部类可以是私有类(“private class xxx{}”),而常规类只可以具有包可见性(“class xxx{}”),或公有可见性(“public class xxx{}”);
内部类访问外围类的实例域
内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。
- 通过这个对象,内部类既可以访问自身的数据域,也可以访问创建它的外围类的数据域;
- 但是除了局部内部类,其他内部类不能访问方法的局部变量;
内部类的特殊语法规则
内部类的使用:
- 内部类中的外围类引用为:“outerClass.this”(outerClass为外围类类名)。
- 在外围类中可以这样构造内部类对象:“outerObject.new InnerClass{construction parameters)”(this可以省略)
- 对于公有内部类,任意的外部类对象都可以构造一个内部类;
TalkingClock jabberer = new Ta1kingClock(1000, true) ; TalkingClock.TiiePrinter listener = jabberer.new TimePrinter();
- 在外围类的作用域中,可以用“OuterClass.InnerClass”来引用内部类;
public class TalkingClock
{
private int interval:
private boolean beep;
public TalkingClock(int interval , boolean beep)
{
this,interval = interval;
this.beep = beep;
}
public void start()
{
ActionListener listener = new TimePrinter();
// 或 ActionListener listener = this.new TimePrinter();
Timer t = new Timer(interval , listener);
t.start();
}
public class TimePrinter implements ActionListener
{
public void actionPeformed(ActionEvent event)
{
System.out.println("At the tone, the time is " + new Date());
// 访问外部类的域
if (beep) Toolkit.getDefaultToolki1().beep();
// 使用外部类引用
if (TalkingClock.this,beep) Toolkit.getDefaultToolkitO.beep();
}
}
}
内部类的规则:【???说的啥都是】
- 内部类中声明的所有静态域都必须是 final(即常量);
- 如果不为final,则由于每个外部实例都有一个内部类实例,那么静态域可能不唯一(静态域的意义就是多个实例共享一个值)
- 内部类不能有静态方法(除了静态内部类);
- 非static的内部类,在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法。
- 因为“static类型的属性和方法,在类加载的时候就会存在于内存中”,“要使用某个类的static属性或者方法,那么这个类必须要加载到jvm中”,如果一个非static的内部类如果具有static的属性或者方法,那么就会出现:内部类未加载(非静态内部类在外围类实例构建之后加载?),但是却试图在内存中创建static的属性和方法,这当然是错误的。
内部类的类文件
- 内部类是一种编译器现象, 与虚拟机无关。编译器将会把内部类翻译成用“$”分隔外部类名与内部类名的常规类文件。
- 如:TalkingClock类内部的TimePrinter类的类文件为“TalkingClock$TimePrinter.class”
public class TalkingClock$TimePrinter
{
public TalkingGock$TimePrinter(TalkingCtock); // 构造函数:用外围类构造内部类
public void actionPerformed(java.awt.event.ActionEvent); // 内部类方法
final TalkingClock this$O; // 外围类引用
}
局部内部类
局部内部类:定义在方法中的内部类:
- 局部内部类不用 public、private 进行声明;
- 局部内部类的作用域限定在所在代码块中;
- 对代码块外完全隐藏;
public void start()
{
class TimePrinter implements ActionListener
{
public void actionPerforaed(ActionEvent event)
{
Systei.out.println("At the tone, the tine is " + new DateO);
if (beep) Toolkit.getDefaul tToolki10•beep():
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interva1, listener);
t.start();
}
- 访问外部方法局部变量:与其他内部类相比较,局部类还有一个优点:它们不仅能够访问包含它们的外部类,还可以访问局部变量:
- 编译器会为局部内部类构造具有参数的构造函数和域,用于保存外围方法的局部变量:
class TalkingClock$ITimePrinter { TalkingClock$ITinePrinter(TalkingClock, boolean); // 以外围方法局部变量作为参数的构造函数 public void actionPerformed(java.awt.event.ActionEvent); // final boolean val$beep; // 变量:用于存储外围方法的局部变量 final TalkingClock this$O; // 外围类引用 }
匿名内部类
匿名内部类(anonymous inner class):没有类名,只用于构造一个该类的实例。形式为:
- 由于构造器的名字必须与类名相同,而匿名内部类没有类名,所以,匿名内部类不能有构造器。
- 继承超类的匿名内部类:将构造器参数传给超类构造器;
new SuperType(construction parameters) { inner class methods and data }
- 实现接口的匿名内部类:不能有构造参数。
new InterfaceType() { methods and data }
- 继承超类的匿名内部类:将构造器参数传给超类构造器;
如下,创建一个了实现ActionListener 接口的类的新对象,需要实现的方法 actionPerformed 定义在括号内。
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();
}
使用匿名内部类与实例化对象:
Person queen = new Person("Mary"); // 这是在构造对象
Person count = new Person("Dracula") { ... } // 这是在使用匿名内部类
双括号初始化
双括号初始化(double brace initialization):
ArrayList<String> friends = new ArrayListoO;
friends,add("Harry");
friends,add("Tony") ;
invite(friends) ;
可以被替换为:
invite(new ArrayList<String>0 {{ add("Harry") ; add("Tony") ; }}) ;
- 外部大括号用于建立一个ArrayList<String>的匿名内部类;
- 内层大括号则是一个初始化块(见[2]);
静态内部类
有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static, 以便取消产生的引用。
- 只有内部类可以被声明为 static
- 没有外围类的对象的引用
- 可以有静态域和静态方法(其他内部类不可以)
代理
参考:动态代理、CGLIB 与 切面编程 如果要为一个没有实现接口的类生成动态代理类,那么可以使用 CGLIB 库。
代理(Proxy):利用代理可以在运行时,创建一个实现了一组给定接口的新类。(动态代理)
- (静态代理:通过聚合、组合的方式,对需要的类或接口进行前后处理,提供新的调用)
- 代理类可以在运行时创建全新的类;
- 只有在编译时无法确定需要实现那个接口时才有必要使用。
创建一个代理对象
要想创建一个代理对象,需要使用Proxy类的newProxylnstance方法。这个方法有三个参数:
- 类加载器(“ClassLoader”):用 null 表示使用默认的类加载器;(有关类加载器,查阅:卷2第9章)
- 接口数组(“Class[]”):一个Class对象数组,每个元素都是需要实现的接口。
- 调用处理器(“InvocationHandler”):定义对代理类方法的处理。
- 调用处理器需要实现 InvocationHandler 接口:仅有一个方法 invoke,用于对代理实例的方法进行处理:
Object invoke(Object proxy, Method method, Object[] args)
- 代理类属于在运行时定义的类(名字如“$Proxy0”),这个类实现了给定的接口(如“Comparable”),它的方法(如“compareTo”)调用了代理对象处理器的“invoke”方法。
示例
ProxyTest:
package Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Random;
public class ProxyTest {
public static void main(String[] args) {
Object[] elements = new Object[1000];
// fill elements with proxies for the integers 1 ... 1000
for (int i = 0; i < elements.length; i++) {
Integer value = i + 1;
InvocationHandler handler = new TraceHandler(value);
// 调用 Proxy.newProxyInstance
// 1、构造实现指定接口(“Comparable”)的代理类的一个新实例
// 2、所有方法会调用给定处理器对象(“handler”)的 invoke 方法
Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class }, handler);
// elements的每个元素都是一个代理类实例
elements[i] = proxy;
}
// elements的元素类型为:“class com.sun.proxy.$Proxy0”
//System.out.println(elements[1].getClass());
// construct a random integer
// 生成随机数key
Integer key = new Random().nextInt(elements.length) + 1;
// search for the key
// 在elements中使用二分查找key,使用的方法是“binarySearch(Object[] a, Object key)”
// binarySearch方法会用到,代理类实例所(即每个elements元素)实现的Comparable接口下compareTo方法
// 而代理类实例的每个方法都会调用 “TraceHandler”的“invoke”(打印内容)
/* 打印的内容如:
* 985.compareTo(973)
* 977.compareTo(973)
* 973.compareTo(973)
*/
int result = Arrays.binarySearch(elements, key);
// print match if found
if (result >= 0)
// 调用了代理类实例的“toString()”
// 而该“toString()”依然会调用 “TraceHandler”的“invoke”
/* 打印的内容如:1、先调用了 “TraceHandler”的“invoke”,2、toString打印了代理实例的
* 973.toString()
* 973
*/
System.out.println(elements[result]);
}
}
TraceHandler:
package Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TraceHandler implements InvocationHandler {
private Object target;
/**
* Constructs a TraceHandler
*
* @param t
* the implicit parameter of the method call
*/
public TraceHandler(Object t) {
target = t;
}
// (所有代理类实例的方法都会调用这个方法)
// 其中定义了代理对象调用方法时希望执行的动作:打印“target.调用的方法(参数列表)”
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
// print implicit argument
System.out.print(target);
// print method name
System.out.print("." + m.getName() + "(");
// print explicit arguments
if ((args != null)) {
for (int i = 0; i < args.length; i++) {
System.out.print(args[i]);
if (i < args.length - 1)
System.out.print(", ");
}
}
System.out.println(") ");
// invoke actual method
// 这次调用实例的方法
// 说明可以在这里进行前处理、后处理
return m.invoke(target, args);
}
}
代理类的特性
代理类是在程序运行过程中创建的。然而,一旦被创建,就变成了常规类,与虚拟机中的任何其他类没有什么区别。
- 所有的代理类都扩展于Proxy 类。
- 一个代理类只有一个实例域(调用处理器),它定义在Proxy 的超类中。
- 代理类所需要的任何附加数据都必须存储在调用处理器中。
- 所有的代理类都覆盖了Object 类中的方法 toString、equals 和hashCode。如同所有的代理方法一样, 这些方法仅仅调用了调用处理器的invoke。
- Object 类中的其他方法(如 clone 和 getClass ) 没有被重新定义。
- 没有定义代理类的名字,Sun 虚拟机中的Proxy 类将生成一个以字符串SProxy 开头的类名。
- 对于特定的类加载器和预设的一组接口来说,只能有一个代理类。
- 也就是说,如果使用同一个类加载器和接口数组调用两次newProxylustance 方法的话, 那么只能够得到同一个类的两个对象,也可以利用getProxyClass 方法获得这个类:
- “Class proxyClass = Proxy.getProxyClass(null, interfaces);”
- 代理类一定是public 和final。
- 如果代理类实现的所有接口都是public,代理类就不属于某个特定的包;否则, 所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。
- 可以通过调用Proxy 类中的isProxyClass 方法检测一个特定的Class 对象是否代表一个代理类。
java.Iang.reflect.InvocationHandler
- Object invoke(Object proxy,Method method,0bject[]args)
- 定义了代理对象调用方法时希望执行的动作。【对代理类对象的方法进行处理】
java.Iang.reflect.Proxy
- static Class<?> getProxyClass(Cl assLoader loader, Class<?>...interfaces)
- 返回实现指定接口的代理类。【返回代理类】
- static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces, InvocationHandler handler)
- 构造实现指定接口的代理类的一个新实例。【返回代理类的实例】
- 所有方法会调用给定处理器对象的invoke 方法。
- static boolean isProxyClass(Class<?> cl)
- 如果cl 是一个代理类则返回true。