Java基础知识:接口、lambda表达式与内部类

  • 接口(interface)
  1. 接口技术:主要用来描述类具有什么功能,而并不给出每个功能的具体实现。
  2. 一个类可以实现(implements关键字)一个或多个接口。
  3. 接口不是类,而是对类的一组需求描述,这些类需要遵从接口描述统一格式进行定义。
  4. 接口中的所有方法自动属于public。不必特地声明为public。
  5. 接口绝能含有实例域。Java SE 8之后,可以在接口中提供简单方法。
  6. 在Java SE 5.0中,Comparable接口已经改进为泛型类型。(原始的Comparable接口需要进行强制类型转换
//原始的Comparable接口
public interface Comparable
{
    int compareTo( Object other );
}

//若 Class Employee implements Comparable, 则:
public int compareTo( Object otherObject )
{
    Employee other = (Employee) otherObject;
    return Double.compare( salary, other.salary );
}

//泛型Comparable接口
public interface Comparable
{
    int compareTo( T other );
}

//若 Class Employee implements Comparable
public int compareTo( Employee other )
{
    return Double.compare(salary, other.salary);
}
  • java是一种强类型语言。在调用方法时,编译器会检查这个方法是否存在。若实现了某接口,编译器就可以确保已经存在接口中声明的方法。
  • 若 Manager 扩展了 Employee ,而 Employee 实现的是 Comparable, 那么若 Manager 覆盖了 compareTo方法,就得让 经理与普通雇员 进行比较,而不能仅仅将雇员转换成经理(雇员不能转换成经理,会抛出ClassCastException)。
  • 如果子类之间的比较含义不同,每次调用compareTo方法的时候都需进行下列检测:if( getClass() != other.getClass() ) throw new ClassCastException();
  • 如果存在通用的算法,能够对两个不同的子类进行比较,那么就应该 在超类中定义一个 final 的 compareTo方法。
  • 示例程序:(对实现了Comparable的员工类对象进行排序)
package coreJava_chap6;

import java.util.*;
public class ComparableTest {
	public static void main( String[] args ) {
		Employee[] staff = new Employee[3];
		staff[0] = new Employee( "Cena", 5000, 2015, 3, 12 );
		staff[1] = new Employee( "Randy", 3000, 2014, 6, 2 );
		staff[2] = new Employee( "Edge", 4000, 2016, 7, 9 );
		System.out.println("Original:");
		for( Employee e : staff )
			System.out.println( "name: " + e.getName() + ", salary: " + e.getSalary() );
		
		Arrays.sort(staff);
		System.out.println();
		System.out.println("Sorted:");
		for( Employee e : staff )
			System.out.println( "name: " + e.getName() + ", salary: " + e.getSalary() );		
		
		
	}
}
package coreJava_chap6;

import java.time.*;
public class Employee implements Comparable {
	private String name;
	private double salary;
	private LocalDate hireDay;
	
	public Employee( String name, double salary, int year, int month, int day ) {
		this.name = name;
		this.salary = salary;
		hireDay = LocalDate.of(year, month, day);
	}
	
	public String getName() {
		return name;
	}
	
	public double getSalary() {
		return salary;
	}
	
	public LocalDate getHireDay() {
		return hireDay;
	}
	
	public void raiseSalary( double byPercent ) {
		double raise = salary * byPercent / 100;
		salary += raise;
	}
	
	public int compareTo( Employee other ) {
		return Double.compare(salary, other.salary);
	}
}

 

  • 接口的特性:
  1. 接口不是类,不能实例化。
  2. 但我们能够声明 接口变量。接口变量必须引用实现了接口的类对象。Comparable x = new Employee(...);
  3. instanceof 可以检查一个对象是否实现了某接口。 if( anObject instanceof Comparable ) {...} 。
  4. 与类一样, 接口也可以被扩展
  5. 接口中不能含有实例域或静态方法,但是可以包含常量。接口中的域将自动设置为 public static final
  6. 有些接口定义了常量,任何实现了这个接口的类都自动继承了这些常量。(但这样偏离了接口的初衷,最好不这么使用)
  7. 每个类只能拥有一个超类,但可以实现多个接口。
  • 接口可以提供多重继承的大多数好处,同时能避免多重继承的复杂性和低效性。
  • Java SE 8中,允许在接口中增加静态方法。但是这还是有违接口的初衷(静态方法一般放在伴随类里面)。
  • 可以为接口方法提供一个默认实现,但必须用 default修饰符 标记。默认方法可以调用任何其他方法(即使还没具体实现)。
public interface Comparable
{
    default int compareTo( T other ) { return 0; } 
}
  • 默认实现的应用场景:有些方法默认什么事情都不做,如果已经有默认实现了,就不再覆盖这些方法了。
  • 默认方法的一个重要用法是 “接口演化”(interface evolution)。若接口新增了一个非默认的方法,那么之前实现了这个接口的类就不能够编译通过,因为这个类还没有实现接口中新增的那个方法。简言之,接口中的默认方法可以解决兼容性的问题。
  • 解决默认方法冲突
  1. 超类优先
  2. 接口冲突:必须覆盖这个方法来解决冲突。

 

  • 接口与回调:
  1. 回调call back)是一种常见的程序设计模式,可以指定某个特定事件发生时应该采取什么动作。
  2. java.awt.event 包的 ActionListener接口
public interface ActionListener
{
    void actionPerformed( ActionEvent event );
}
  • javax.swing.JOptionPane
    • static void showMessageDialog( Component parent, Object message ) //对话框位于parent中央,若parent为null,则在屏幕中央
  • java.swing.Timer
    • Timer( int interval, ActionListener listener )
    • void start()
    • void stop()
  • java.awt.Tooikit
    • static Toolkit getDefaultToolkit()
    • void beep()
  • 示例程序:(编写程序,每5秒报时一次)
package timer;

import javax.swing.JOptionPane;
import java.util.*;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.*;

public class TimerTest {
	public static void main( String[] args ) {
		Timer t = new Timer( 5000, new TimePrinter() );
		t.start();
		JOptionPane.showMessageDialog(null, "Quit Program?");
		System.exit(0);
	}
}

class TimePrinter implements ActionListener{
	public void actionPerformed( ActionEvent event ) {
		System.out.println("Now the time is " + new Date() );
		Toolkit.getDefaultToolkit().beep();
	}
}

 

 

  • Comparator接口:
  1. 要实现以各种各样的方式对 String[]数组 进行排序,可以借助 Arrays.sort方法的第二版本,传入一个数组和一个比较器
  2. 比较器是实现了 Comparator接口 的类的实例
  3. 利用lambda表达式可以更容易地使用Comparator 。
public interface Comparator
{
    int compare( T first, T second );
}
  • 示例程序:(分别按字典序和按字符串长度对String[]数组的对象进行排序)
package comparator;

import java.util.*;
public class ComparatorTest {
	public static void main( String[] args ) {
		String[] names = { "Thor", "Tony Stark", "Peter Parker", "Steve" };
		System.out.println("Sorted by dictionary order:");
		Arrays.sort(names);
		for( String s : names ) {
			System.out.print(s + "    ");
		}
		System.out.println();
		System.out.println("Sorted by string length:");
		Arrays.sort(names, new LengthComparator() );
		for( String s : names ) {
			System.out.print(s + "    ");
		}
		System.out.println();		
	}
}

class LengthComparator implements Comparator
{
	public int compare( String str1, String str2 ) {
		return str1.length() - str2.length();
	}
}

 

 

  • 对象克隆:
  1. Cloneable接口,这个接口指示一个类提供了一个安全的clone方法。
  2. clone方法是 Object的一个protected方法,只有Employee类可以克隆Employee对象。
  3. 默认的克隆是“浅拷贝”,并没有克隆对象中引用的其他对象。
  4. 如果原对象和浅克隆对象共享的子对象是不可变的,那么这种共享就是安全的。
  5. 但是通常子对象都是可变的,必须重新定义clone方法来建立一个深拷贝
  6. 对于每一个类,需要确定:
    1. 默认的clone方法是否满足要求
    2. 是否可以在可变的子对象上调用clone来修补默认的clone方法
    3. 是否不该使用clone
  7. cloneable接口是 Java提供的一组标记接口(tagging interface)之一。它没有指定clone方法,这个方法是从Object类继承的。标记接口不包含任何办法,唯一的作用是允许类型检查时可使用instanceof。建议在自己的程序中不要使用标记接口。
  8. 即使clone的默认(浅拷贝)能够满足需求,还是要实现Cloneable接口,并把clone方法设置为public,再调用 super.clone() 。
  9. 如果在一个对象上调用clone,但是这个对象没有实现Cloneable接口,Object类的clone方法就会抛出一个 CloneNotSupportedException 。public Employee clone() throws CloneNotSupportedException 。
  10. 当心子类的克隆。一旦超类定义了public的clone方法,子类就能够直接继承,但是超类的clone方法不一定能够满足子类的拷贝。
  11. 所有的数组类型都有一个public的clone方法,可以轻松获得原数组的副本。
  • 示例程序:(拷贝测试)
package clone;

public class CloneTest {
	public static void main( String[] args ) {
		try {
			Employee original = new Employee( "Bryant", 50000 );
			original.setHireDay(2012, 3, 12);
			Employee cloned = original.clone();
			cloned.setHireDay(2005, 2, 23);
			System.out.println(original);
			System.out.println(cloned);
		}catch( Exception e ) {
			e.printStackTrace();
		}
	}
}
package clone;
import java.util.Date;
import java.util.GregorianCalendar;

public class Employee implements Cloneable {
	private String name;
	private double salary;
	private Date hireDay;
	
	public Employee( String name, double salary ) {
		this.name = name;
		this.salary = salary;
		this.hireDay = new Date();
	}
	
	public Employee clone() throws CloneNotSupportedException{
		Employee cloned = ( Employee) super.clone();
		cloned.hireDay = (Date) hireDay.clone();
		
		return cloned;
	}
	
	public void setHireDay( int year, int month, int day ) {
		Date newHireDay = new GregorianCalendar(year, month - 1, day ).getTime();
		hireDay.setTime(newHireDay.getTime());
	}
	
	public void raiseSalary( double byPercent ) {
		double raise = salary * byPercent / 100;
		salary += raise;
	}
	
	public String toString() {
		return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
	}
}

 

 

  • lambda表达式:
  1. lambda表达式是一个可传递的代码块,可以在以后执行一次或多次(将来的某个时间调用)。
  2. lambda表达式形式:参数 (→) {表达式} 。 没有参数也要提供空括号。如果可以推导出lambda表达式的参数类型,那么可以忽略其类型。
  3. 如果只有一个参数且该参数可以推导得出,可以省略 小括号"()" 。
  4. 无需指定lambda表达式的返回类型。
  • 示例程序:(测试lambda表达式,用lambda表达式 代替 比较器 以及 回调的 监听器
package lambda;

import java.util.Arrays;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;

public class LambdaTest {
	public static void main( String[] args ) {
		String[] strs = { "Hulk", "James", "Leo", "Bryant" };
		System.out.println("Sorted by dictionary order:");
		Arrays.sort(strs);
		System.out.println( Arrays.toString(strs) );
		
		System.out.println("Sorted by string length:");
		Arrays.sort(strs, (String first, String second) -> first.length() - second.length() ); 
		System.out.println( Arrays.toString(strs) );
		
		Timer t = new Timer( 3000, event -> System.out.println( "Now the time is " + new Date() ) );
		t.start();
		JOptionPane.showMessageDialog(null, "Quit now?");
		System.exit(0);
	}
}

 

  • 函数式接口:
  1. 对于只有一个抽象方法的接口(函数式接口),当需要这种接口的对象的时候,可以用lambda表达式替代。
  2. 最好把lambda表达式看作是一个函数,而不是一个对象。
  3. 与使用传统的内联类相比,lambda表达式高效得多。
  4. 不能把lambda表达式赋给类型为Object的变量,Object不是一个函数式接口。
  5. java.util.function包中定义了很多非常通用的函数式接口。
  6. Predicate接口,专门用来传递lambda表达式。
public interface Predicate
{
    boolean test(T t);
    //...addition default and static methods
}

 

  • 方法引用(method reference):
  1. Timer t = new Timer( 1000, System.out::println );  
  2. 表达式System.out::println就是一个方法引用。等价于  x -> System.out.println(x) 。
  3. 方法引用的三种情况:
    1. object::instanceMethod // x -> System.out.println(x) 
    2. Class::staticMethod // (x,y) -> Math.pow(x,y)
    3. Class::instanceMethod  // (x,y) -> x.compareTo(y), 第一个参数会成为方法的目标。
  4. 类似于lambda表达式,方法引用不能独立存在,总是会转换成函数式接口的实例
  5. 可以使用this参数和super参数
    1. this::instanceMethod
    2. super::instanceMethod
  6. 构造器引用:方法名为 new。如: Person::new 是Person构造器的一个引用。
  7. 可以用数组类型建立构造器引用,如:int[]::new 等价于 x -> new int[x] 。
  8. Java有一个限制,无法构造泛型类型T的数组。
  9. Person[] people = stream.toArray( Person[]::new ) 。
  • 变量作用域:
  1. 有时,希望在lambda表达式中访问外围方法或类中的变量
  2. lambda表达式的三个部分:
    1. 一个代码块
    2. 参数
    3. 自由变量的值(非参数且不在代码中定义的值)
  3. 表示lambda表达式的数据结构必须存储自由变量的值。在Java中,必须保证所捕获的值是明确定义的,lambda表达式中,只能引用值不会改变的变量(若可变,并发执行多次,可能会出现严重问题),捕获的变量必须实际上是最终变量
  4. lambda表达式与嵌套块有相同的作用域,不能声明与局部变量同名的变量或参数。
  5. 在一个lambda表达式中使用this关键字,是指创建这个lambda表达式的方法的this参数

 

  • 处理lambda表达式(如何编写方法处理 lambda 表达式):
  1. lambda表达式的重点是 延迟执行
  2. 常见的重要函数式接口:
    1. Runnable 无参数,无返回值,抽象方法名: run 。
    2. Supplier 无参数,返回T, 抽象方法名 :get 。
    3. Consumer 参数T,无返回值, 抽象方法名 : accept 。
    4. Function 参数T, 返回T,抽象方法名 : apply 。
    5. Predicate 参数T,返回boolean, 抽象方法名: test 。
  3. 基本类型的函数式接口:
    1. BooleanSupplier
    2. PSupplier
    3. PConsumer
    4. ObjPConsumer
    5. ...Page 241 in 《core Java(10th edition)》
  4. 如果设计自己的接口,其中只有一个抽象方法,可以用 @FunctionalInterface 注解来标记。好处:若无意中为接口增添了非抽象方法,编译器提醒报错;javadoc页会指出此接口是函数式接口。
  5. 实例程序:( 测试函数式接口,传入lambda表达式 )
package funcInterface;

import java.util.function.*;
public class FuncInterfaceTest {
	public static void main( String[] args ) {
		repeat( 10, ()->System.out.println("Life is a struggle!") );
		countDown( 10, i -> System.out.println("count down to " + (10-i)) );
		int[] intArr = { 4, 2, 5, 9, 6, 7, 1, 3 };
		boringTest( intArr, x -> x >= 5 );
	}
	
	public static void repeat( int n, Runnable action ) {
		for( int i = 0; i < n; i++ ) {
			action.run();
		}
	}
	
	public static void countDown( int n, IntConsumer action ) {
		for( int i = 0; i < n; i++ ) action.accept(i);
	}
	
	public static void boringTest( int[] a, IntPredicate action ) {
		for( int e : a ) {
			if( action.test(e) ) System.out.println( "Element " + e + " satisfies the test." );
			else System.out.println( "Element " + e + " dissatisfies the test." );
		}
	}
}

 

  • 再谈Comparator:
  1. Comparator接口包含很多方便的静态方法创建比较器
  2. 静态 comparing方法 取一个“键提取器”函数。将类型T映射成一个可比较的类型:如 Arrays.sort( people, Comparator.comparing( Person::getName ) );
  3. thenComparing方法。
  4. 如果键函数可以返回null,可能就要用到 nullsFirst 和 nullsLast 适配器
  5. nullsFirst方法 需要一个比较器。Comparator.naturalOrder() /reverseOrder()。
  6. Arrays.sort( people, Comparator.comparing( Person::getMiddleName, nullsFirst(Comparator.naturalOrder() ) ) ) 。
  7. 示例程序:(测试Comparator的(创建比较器的)静态方法)
package comparator;

import java.util.Arrays;
import java.util.Comparator;

public class ComparatorTest2 {
	public static void main( String[] args ) {
		Person[] people = new Person[3];
		people[0] = new Person( "Albus", null, "DumbleDore" );
		people[1] = new Person( "Tom", "Marvolo", "Riddle" );
		people[2] = new Person( "Albus", "Severus", "Potter" );
		
		Arrays.sort(people, Comparator.comparing(Person::getName));
		for( Person p : people ) {
			System.out.print(p.getName() + "   ");
		}
		System.out.println();
		Arrays.sort(people, Comparator.comparing( Person::getName, 
				(first,second) -> first.length() - second.length()  ) );
		for( Person p : people ) {
			System.out.print(p.getName() + "   ");
		}		
		System.out.println();
		Arrays.sort(people,Comparator.comparing(Person::getMiddleName, 
				Comparator.nullsFirst(Comparator.naturalOrder() )));
		for( Person p : people ) {
			System.out.print(p.getName() + "   ");
		}
		System.out.println();
		
	}
}

 

  • 内部类:
  1. 内部类(inner class)是定义在另一个类中的类。 
  2. 使用内部类的主要原因:
    1. 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。
    2. 内部类可以对同一个包中的其他类隐藏起来。
    3. 当想要定义一个回调函数且不想编写大量的代码时,使用 匿名内部类 比较便捷。
  3. 嵌套是一种类之间的关系而不是对象之间的关系。
  4. 内部类的对象有一个隐式引用,它引用了实例化该内部对象的外围类对象。通过这个指针,可以访问外围类对象的全部状态。
  5. static内部类没有上述的指针。

 

  • 使用内部类访问对象状态:
  1. 内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。
  2. outer并不是java的关键字,只是用来说明外围引用的概念。
  3. 外围类的引用在构造器中由编译器自动添加/修改
  4. public TimerPrinter( TalkingClock clock ) { outer = clock; } 
  5. 只有内部类可以是私有类
  6. 示例程序:(测试内部类,利用创建一个回调程序,每隔一定时间报时且响铃,并且响铃与否可以控制)
package innerClass;

import javax.swing.*;
public class InnerClassTest {
	public static void main( String[] args ) {
		TalkingClock clock = new TalkingClock(5000, true);
		clock.start();
		JOptionPane.showMessageDialog(null, "Quit now?");
		System.exit(0);
	}
}
package innerClass;

import javax.swing.*;
public class InnerClassTest {
	public static void main( String[] args ) {
		TalkingClock clock = new TalkingClock(5000, true);
		clock.start();
		JOptionPane.showMessageDialog(null, "Quit now?");
		System.exit(0);
	}
}

 

 

  • 内部类的特殊语法规则:
  1. 使用外围类的正规语法: OuterClass.this 。比如: if( TalkingClock.this.beep ) { ... } 。
  2. 在外围类的作用域之外,可以这样引用内部类: OuterClass.InnerClass 。
  3. 内部类声明的所有静态域都必须是final的。
  4. 内部类不能有static方法。

 

  • 内部类是否有用、必要与安全:
  1. 内部类是一种编译器现象,与虚拟机无关。
  2. 如果内部类访问了私有数据域,就有可能通过附加在外围类所在包中的其他类访问它们。
  • 局部内部类:类只在某方法中使用了一次,可以在一个方法中定义局部类。不能声明public或private,因为作用域就限定在方法以内。
  • 局部类的优势:对外部世界完全隐藏,除了定义局部类的方法之外,没有任何地方知道局部类的存在。
  • 局部类不仅能够访问包含它们的外部类,还可以访问局部变量(参数)不过那些局部变量必须事实上为final。方法调用结束后,局部变量会释放,而释放之前,编译器会进行备份,故需要前后保证一致。
  • 因为局部变量为final,那么如果局部变量需要更新,就可以使用长度为1的数组(final只限制了引用不可变,内容就可以进行更新了)。

 

  • 匿名内部类(anonymous inner class):
public void start( int interval, boolean beep )
{
    ActionListener listener = new ActionLister()
    {
        public void actionPerformed(ActionEvent event)
        {
            System.out.println("Now the time is " + new Date() );
            if( beep ) Toolkit.getDefaultToolkit().beep();
        }

    };
    Timer t = new Timer( 1000, listener );
    t.start();
}
//通用语法格式1:
new SuperType( construction parameters )
{
    inner class methods and data
}

//通用语法格式2:
new Interface()
{
    methods and data
}
  1. 匿名类不能有构造器,取而代之的是将构造器传给超类构造器
  2. Java程序员习惯的做法是用匿名内部类实现事件监听和其他回调,如今最好的做法是使用lambda表达式(简洁)。
  3. 示例程序:(使用匿名内部类实现事件监听回调)
package anonymousInnerClass;

import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.Timer;

public class AnonymousInnerClassTest {
	public static void main( String[] args ) {
		TalkingClock clock = new TalkingClock();
		clock.start(1000, true);
		JOptionPane.showMessageDialog(null, "Quit now?");
		System.exit(0);
	}
}

class TalkingClock{
	public void start( int interval, boolean beep ) {
		ActionListener listener = new ActionListener() {
			public void actionPerformed( ActionEvent event ) {
				System.out.println("Now the time is " + new Date());
				if( beep ) Toolkit.getDefaultToolkit().beep();
			}
		};
		Timer t = new Timer(interval,listener);
		t.start();
	}
	
}

 

  • 双括号初始化(利用了内部类语法):
ArrayList friends = new ArrayList();
friends.add("Harry");
friends.add("Tony");
invite(friends);

//等价于:
invite( new ArrayList(){ { add("Harry"); add("Tony"); } } );
//外层括号建立了 ArrayList 的一个 匿名子类。内层括号则是一个 对象构造块。
  • getEnclosingClass方法 静态方法得到外围类的方式: new Object(){}.getClass().getEnclosingClass() //get class of static method
  • 建立一个与 超类 大体类似但不完全相同的 匿名子类很方便,但是对于equals方法要特别当心(无法getClass,没有类名)。

 

  • 静态内部类:
  1. 有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用。为此,可以将内部类声明为static,以便取消产生的引用
  2. 应用:解决产生名字冲突的类的方法,将类定义为内部公有类
  3. 只有内部类可以声明为static。静态内部类的对象除了没有对生成它的外围类对象的引用特权之外,与其他内部类一样。
  4. 与常规的内部类不同,静态类可以有静态域和静态方法。
  5. 声明在接口中的内部类自动成为static和public类。
  6. 示例程序:(只对数组编译一次,得到数组的最大值与最小值)
package staticInnerClass;
import java.util.*;
public class StaticInnerClassTest {
	public static void main( String[] args ) {
		int[] arr = new int[10];
		for( int i = 0; i < 10; i++ )
			arr[i] = (int) ( 100 * Math.random() );
		System.out.println(Arrays.toString(arr));
		ArrayAlg.Pair ans = ArrayAlg.minmax(arr);
		System.out.println("min=" + ans.getFirst());
		System.out.println("max=" + ans.getSecond());
	}
}

class ArrayAlg{
	
	public static class Pair{
		private int first;
		private int second;
		
		public Pair( int first, int second ) {
			this.first = first;
			this.second = second;
		}
		
		public int getFirst() { return first; }
		public int getSecond() { return second; }
		
	}
	
	public static Pair minmax( int[] intArr ) {
		int max = Integer.MIN_VALUE;
		int min = Integer.MAX_VALUE;
		for( int d : intArr ) {
			if( d > max ) max = d;
			if( d < min ) min = d;
		}
		
		return new Pair(min,max);
	}
}

 

 

  • 代理(proxy)
  1. 利用代理可以在运行时创建一个实现了一组给定接口新类
  2. 这种功能只有在编译时无法确定实现哪个接口时才有必要使用。
  3. 对于系统程序设计的人员来说,代理带来的灵活性十分重要。
  4. 代理创建的类具有以下的方法:
    1. 指定接口所需要的全部方法
    2. Object类的全部方法
  5. 需要提供一个调用处理器,调用处理器是实现了 InvocationHandler接口的类对象。这个接口只有一个方法:Object invoke( Object proxy, Method method, Object[] args ) 。
  6. 无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用。
  • 创建代理对象:
  1. Proxy类的newProxyInstance方法,三个参数:
    1. 一个类加载器( class loader )。用 null 表示使用默认的类加载器。
    2. 一个Class对象数组,每个元素都是需要实现的接口
    3. 一个调用处理器
  2. 代理类是在程序运行过程创建的,然而,一旦被创建,就变成了常规类,与虚拟机中的任何其他类没有区别。
  3. 所有的代理类都扩展于Proxy类。一个代理类只有一个实例域——调用处理器,定义在Proxy的超类中。
  4. 代理类一定是public和final的。
  5. getProxyClass静态方法获得代理类。
  • 示例程序:(测试代理,对代理对象数组进行二分查找和打印,功能程序会将代理对象调用的所有方法全部打印出来
package proxy;

import java.lang.reflect.*;
import java.util.*;

public class ProxyTest {
	public static void main( String[] args ) {
		Object[] elements = new Object[1000];
		for( int i = 0; i < elements.length; i++ ) {
			Integer value = i + 1;
			InvocationHandler handler = new TraceHandler(value);
			Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class }, handler );
			elements[i] = proxy;
		}
		Integer key = new Random().nextInt(elements.length) + 1;
		int result = Arrays.binarySearch(elements, key);
		if( result >= 0 ) System.out.println( elements[result] );
	}
}

class TraceHandler implements InvocationHandler{
	private Object target;
	
	public TraceHandler( Object obj ) {
		target = obj;
	}
	
	public Object invoke( Object proxy, Method m, Object[] args ) throws Throwable{
		System.out.print(target);
		System.out.print("." + m.getName() + "(");
		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(")");
		
		return m.invoke(target, args);
	}
}

 

你可能感兴趣的:(Java知识学习,java)