“核心技术:接口、lambda表达式与内部类”的版本间差异

来自Wikioe
跳到导航 跳到搜索
第733行: 第733行:


=== 创建一个代理对象 ===
=== 创建一个代理对象 ===
ProxyTest:
<syntaxhighlight lang="java">
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]);
}
}
</syntaxhighlight>
TraceHandler:
<syntaxhighlight lang="java">
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);
}
}
</syntaxhighlight>


=== 代理类的特性 ===
=== 代理类的特性 ===

2020年10月18日 (日) 11:27的版本


接口

接口(interface),是对类的一组需求描述(一组行为定义):

  1. 接口的所有方法自动属于public(不用提供public关键字);
  2. 接口中可以有常量,但是不能有实例域;
    即,域自动设置为:“public static final”
  3. 接口中可以有实现方法(Java SE 8 之后),但方法中不能引用实例域;
    默认:“public abstract”

提供实例域和方法实现,应该由实现接口的类来完成。

实现接口:

  1. 声明实现给定的接口:
    class Employee implements Comparable
    
  2. 对接口中的所有方法进行定义;
  • 所有方法都必须有实现;
  • 实现方法时,必须把方法声明为“public”;

接口的特性

  1. 接口不是类,不能用“new”进行实例化;
  2. 可以用接口声明变量,变量只能引用接口实现类的对象;
  3. 可以使用“instanceof”检查一个对象是否实现了某个特定接口(类似于检查对象是否属于某个类);
  4. 接口可以被继承,用新的接口扩展旧接口;

接口与抽象类

Java不支持多重继承(multiple inheritance):引入抽象类,避免多重继承的复杂性和低效性。

静态方法

Java 8 之后,允许接口中增加静态方法:

  • 静态方法无法实现多态,形式上重写,实际上是新方法。

默认方法

可以为接口方法提供一个默认实现(也可以不实现),并用“default”修饰该方法:

  1. 可以不用实现接口所有方法,只关注于需要的某个或某几个方法(可以不再使用伴随类):
    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) {}
    }
    
  2. 在为旧接口增加方法时,使用默认方法可以保证“源代码兼容”(source compatible);
    (增加非默认方法,会导致接口现有实现类不能正常工作)


关于伴随类

在JavaAPI 中,你会看到很多接口都有相应的伴随类,这个伴随类中实现了相应接口的部分或所有方法, 如 Collection/AbstractCollection 或 MouseListener/MouseAdapter。在JavaSE 8 中,这个技术已经过时。现在可以直接在接口中实现方法。

使用抽象类(伴随类)对接口进行部分实现,而后用户类并不之间实现接口(需要实现全部方法),而是继承与该抽象类(只重写需要的类即可)。如:

  1. public interface Collection<E> extends Iterable<E> {
    
    public abstract class AbstractCollection<E> implements Collection<E> {
    
  2. public interface MouseListener extends EventListener {
    
    public abstract class MouseAdapter implements MouseListener, MouseWheelListener, MouseMotionListener {
    
  3. 又见,SpringMVC 中的拦截器(见[1]):
    public interface HandlerInterceptor {
    
    public interface AsyncHandlerInterceptor extends HandlerInterceptor {
    
    public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
    

解决默认方法冲突

冲突:现在接口中将一个方法定义为默认方法,又在超类或另一个接口中定义了同样的方法;
规则:

  1. 超类优先:接口中的方法(不论是否默认方法)会被忽略;
  2. 接口冲突:一个接口提供默认方法,一个接口提供同名同参数方法(不论是否默认方法),则在实现类必须覆盖这个方法;

接口示例

接口与回调

回调的思想是:

  1. 类A的a()方法调用类B的b()方法
  2. 类B的b()方法执行完毕主动调用类A的callback()方法
  • 回调的核心就是回调方将本身即this传递给调用方


回调与接口:

  1. 将回调方法抽象为接口,用回调方去实现需要的回调接口


关于Java调用

Comparator 接口

  • 若需要将对象数组排序,则必须数组的每一个元素都实现了Comparable接口:
    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方法之外的比较方法,来进行对象数组的排序?


所以,我们需要用到:

  1. Arrays.sort 方法
        public static <T> void sort(T[] a, Comparator<? super T> c) {
            if (c == null) {
                sort(a);
            } else {
                if (LegacyMergeSort.userRequested)
                    legacyMergeSort(a, c);
                else
                    TimSort.sort(a, 0, a.length, c, null, 0, 0);
            }
        }
    
  2. 以及,Comparator<T>接口
    package java.util;
    ...
    public interface Comparator<T> {
        int compare(T o1, T o2);
        ...
    }
    


如:

  1. 定义一个 Comparator<String> 接口的实现类:
    Comparator<T> 接口的方法很多,但并不用全部实现,因为一部分方法有实现,一部分为默认方法;
    class LengthComparator implements Comparator<String>
    {
        public int compare(String first, String second) {
            return first.lengthO - second.lengthO
        }
    }
    
  2. 使用Arrays.sort进入数组比较:
        String[] friends = { "Peter", "Paul", "Mary" };
        Arrays,sort(friends, new LengthComparatorO):
    
  3. 也可以,单独对两个对象或两个数组元素进行比较:
        Comparator<String> comp = new LengthComparator。;
        if (conp.compare(words[i], words[j]) > 0)
    
  • Comparator.compare 并非静态方法,所以使用时需要 Comparator实例;

对象克隆

拷贝和克隆

拷贝和克隆.png

  1. 为一个包含对象引用的变量建立副本,直接赋值即可,原件副本都是同一个对象的引用;
  2. 若希望为对象也创建一个副本,则应该使用克隆;

浅拷贝(Object.clone()) 

浅拷贝.png

默认的克隆操作(“Object.clone()”)是“浅拷贝”:如果对象包含子对象的引用,拷贝域会得到相同子对象的另一个引用;

  • 即,原对象域浅克隆对象会引用相同的子对象;
  1. Object.clone() 是Object的一个protected方法;
  2. 若,子对象属于一个不可变的类,或在生命周期中状态不变,则是安全的;否则不应该使用浅拷贝;

深拷贝(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();
    }
    ...
}


深克隆的实现:

  1. 实现 Cloneable 接口,将 clone 重新定义为 public;
  2. 重写 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.lengthO < second.lengthO) return -1;
	else if (first.lengthO > second.lengthO) return 1;
	else return 0;
}


参数:

  1. 如果没有参数,仍然要保留括号:
    () -> { for (int i = 100; i >= 0;i ) System.out.println(i); }
    
  2. 如果可以推导出参数类型,可以忽略参数类型:
    Comparator<String> comp
    	= (first, second) // Same as (String first, String second)
    		-> first.length() - second.length();
    
  3. 如果方法只有一个参数,且参数类型可以推导,可以只保留参数名:
    ActionListener listener = event ->
    	System.out.println("The time is " + new Date()");
    		// Instead of (event) -> . . . or (ActionEvent event) -> . . .
    


表达式:

  1. 如果返回值可以由上下文推导得出,可以省略返回类型:
    (String first, String second) -> first.length() - second.length()
    
    可以在 int 类型结果的上下文使用;


  • lambda 表达式不能只在某些分支返回值,而存在分支不返回值。如下,是不合法的:
    (int x)-> { if (x >= 0) return 1; }
    

函数式接口

函数式接口:有且仅有一个抽象方法,但可以有多个非抽象方法 的接口。(即,接口“只有一个需要实现的方法”)

  • 接口中没有具体实现、没有“public static”修饰的也是抽象方法,因为 接口中的方法默认为“public abstract”(见“接口”一节的描述);
  • Java API 在 java.util.function 包中定义很多非常通用的函数式接口;


需要函数式接口的对象时,可以提供一个 lambda 表达式。【即,用 lambda 表达式来代替函数式接口的抽象方法(“那个唯一需要实现的方法”)】

  • 最好把 lambda 看作是一个函数,而不是一个对象;
  • Java中,lambda所能做的也只是能转换为函数式接口


设计函数式接口时,可以使用“@FunctionalInterface”注解来标记这个接口。这样做:

  1. 当接口不符合函数式接口定义时(如:无意中增加了另一个抽象方法),编译器会报错;
  2. javaDoc 页中会指示该接口是一个函数式接口;


示例1:

Arrays.sort (words ,
    (first , second) -> first.length() - second.length()) ;
  1. Arrays.sort 方法的第二个参数为 Comparator<String>接口的实现类对象;
  2. 该 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();
    })
  1. Timer 构造函数的第二个参数为 ActionListener接口的实现类对象;
  2. 该 lambda表达式 即为 “ActionListener”接口的“actionPerformed(ActionEvent e)”方法的实现;


示例3:

list.removelf(e -> e == null);
  1. (list 对象属于 ArrayList类)
  2. list.removelf 方法(removeIf(Predicate<? super E> filter))的参数为 “Predicate<T>”接口的实现类对象;
  3. 该 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(stringsString::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(PersonD::new);
   toArray 方法调用这个构造器来得到一个正确类型的数组。然后填充这个数组并返回。

变量作用域

  • 关于“闭包”:闭包就是能够读取其他函数内部变量的函数。如:Java中的lambda,javascript中函数内部的子函数;


lambda 表达式的三个部分:

  1. 一个代码块
  2. 参数
  3. 自由变量的值(指非参数,且不在lambda中定义的变量)


  • 自由变量,能使在 lambda 表达式中访问外围方法或类中的变量:
    public static void repeatMessage(String text, int delay)
    {
       ActionListener listener = event ->
       {
          System.out.println(text):
          Toolkit.getDefaultToolkitO.beep() :
       }
       new Timer(delay, listener) .start0;
    }
    
    调用 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(del ay, listener) ,start();
    }
    
    如上,在lambda内改变变量start的值,是不合法的;
    public static void repeat(String text , int count)
    {
       for (int i = 1; i <= count; i ++)
       {
          ActionListener listener = event ->
             {
                System.out.pn'nt1n(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.lengthO ;
       // Error: Variable first already defined
    
    如上,在lambda中声明与一个局部变量同名的参数或局部变量是不合法的;


  • lambda 中的 this 关键字是指创建这个lambda表达式的方法的this参数
    public class ApplicationO
    {
       public void init ()
       {
          ActionListener listener * event ->
             {
                System.out.print n(thi s.toString());
                ...
             }
          ...
       }
    }
    
    如上,表达式this.toString()会调用Application对象的toString方法, 而不是ActionListener实例的方法;

处理lambda表达式

使用lambda 表达式的重点是延迟执行(deferred execution),如以下情况:

  1. 在一个单独的线程中运行代码;
  2. 多次运行代码;
  3. 在算法的适当位置运行代码(例如,排序中的比较操作);
  4. 发生某种情况时执行代码(如,点击了一个按钮,数据到达,等等);
  5. 只在必要时才运行代码;


使用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 + etAs*
*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

内部类

内部类( inner class ) 是定义在另一个类中的类
使用内部类主要原因:

  1. 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
  2. 内部类可以对同一个包中的其他类隐藏起来。
  3. 想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous) 内部类比较便捷。


  • 只有内部类可以是私有类(“private class xxx{}”),而常规类只可以具有包可见性(“class xxx{}”),或公有可见性(“public class xxx{}”);

内部类访问外围类的实例域

内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。

  • 通过这个对象,内部类既可以访问自身的数据域,也可以访问创建它的外围类的数据域;
  • 但是除了局部内部类,其他内部类不能访问方法的局部变量;

内部类对象的外围类对象引用.png

内部类的特殊语法规则

内部类的使用:

  • 内部类中的外围类引用为:“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 tToolki10beep():
      }
   }
   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):没有类名,只用于构造一个该类的实例。形式为:

  • 由于构造器的名字必须与类名相同,而匿名内部类没有类名,所以,匿名内部类不能有构造器
    1. 继承超类的匿名内部类:将构造器参数传给超类构造器;
      new SuperType(construction parameters)
      {
         inner class methods and data
      }
      
    2. 实现接口的匿名内部类:不能有构造参数
      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") ; }}) ;
  1. 外部大括号用于建立一个ArrayList<String>的匿名内部类;
  2. 内层大括号则是一个初始化块(见[2]);

静态内部类

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

  • 只有内部类可以被声明为 static
  • 没有外围类的对象的引用
  • 可以有静态域和静态方法(其他内部类不可以)

代理

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

  • 代理类可以在运行时创建全新的类;
  • 只有在编译时无法确定需要实现那个接口时才有必要使用。


参考 Spring 的 AOP(Aspect Oriented Programming)

何时使用代理

创建一个代理对象

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);
	}
}

代理类的特性