- 接口(interface)
- 接口技术:主要用来描述类具有什么功能,而并不给出每个功能的具体实现。
- 一个类可以实现(implements关键字)一个或多个接口。
- 接口不是类,而是对类的一组需求描述,这些类需要遵从接口描述的统一格式进行定义。
- 接口中的所有方法自动属于public。不必特地声明为public。
- 接口绝不能含有实例域。Java SE 8之后,可以在接口中提供简单方法。
- 在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<T>
{
int compareTo( T other );
}
//若 Class Employee implements Comparable<Employee>
public int compareTo( Employee other )
{
return Double.compare(salary, other.salary);
}
- java是一种强类型语言。在调用方法时,编译器会检查这个方法是否存在。若实现了某接口,编译器就可以确保已经存在接口中声明的方法。
- 若 Manager 扩展了 Employee ,而 Employee 实现的是 Comparable<Employee>, 那么若 Manager 覆盖了 compareTo方法,就得让 经理与普通雇员 进行比较,而不能仅仅将雇员转换成经理(雇员不能转换成经理,会抛出ClassCastException)。
- 如果子类之间的比较含义不同,每次调用compareTo方法的时候都需进行下列检测:if( getClass() != other.getClass() ) throw new ClassCastException();
- 如果存在通用的算法,能够对两个不同的子类进行比较,那么就应该 在超类中定义一个 final 的 compareTo方法。
- 示例程序:(对实现了Comparable<Employee>的员工类对象进行排序)
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<Employee> {
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);
}
}
- 接口的特性:
- 接口不是类,不能实例化。
- 但我们能够声明 接口变量。接口变量必须引用实现了接口的类对象。Comparable x = new Employee(...);
- instanceof 可以检查一个对象是否实现了某接口。 if( anObject instanceof Comparable ) {...} 。
- 与类一样, 接口也可以被扩展。
- 接口中不能含有实例域或静态方法,但是可以包含常量。接口中的域将自动设置为 public static final 。
- 有些接口只定义了常量,任何实现了这个接口的类都自动继承了这些常量。(但这样偏离了接口的初衷,最好不这么使用)
- 每个类只能拥有一个超类,但可以实现多个接口。
- 接口可以提供多重继承的大多数好处,同时能避免多重继承的复杂性和低效性。
- Java SE 8中,允许在接口中增加静态方法。但是这还是有违接口的初衷(静态方法一般放在伴随类里面)。
- 可以为接口方法提供一个默认实现,但必须用 default修饰符 标记。默认方法可以调用任何其他方法(即使还没具体实现)。
public interface Comparable<T>
{
default int compareTo( T other ) { return 0; }
}
- 默认实现的应用场景:有些方法默认什么事情都不做,如果已经有默认实现了,就不再覆盖这些方法了。
- 默认方法的一个重要用法是 “接口演化”(interface evolution)。若接口新增了一个非默认的方法,那么之前实现了这个接口的类就不能够编译通过,因为这个类还没有实现接口中新增的那个方法。简言之,接口中的默认方法可以解决兼容性的问题。
- 解决默认方法冲突:
- 超类优先
- 接口冲突:必须覆盖这个方法来解决冲突。
- 接口与回调:
- 回调(call back)是一种常见的程序设计模式,可以指定某个特定事件发生时应该采取什么动作。
- 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接口:
- 要实现以各种各样的方式对 String[]数组 进行排序,可以借助 Arrays.sort方法的第二版本,传入一个数组和一个比较器。
- 比较器是实现了 Comparator接口 的类的实例。
- 利用lambda表达式可以更容易地使用Comparator 。
public interface Comparator<T>
{
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<String>
{
public int compare( String str1, String str2 ) {
return str1.length() - str2.length();
}
}
- 对象克隆:
- Cloneable接口,这个接口指示一个类提供了一个安全的clone方法。
- clone方法是 Object的一个protected方法,只有Employee类可以克隆Employee对象。
- 默认的克隆是“浅拷贝”,并没有克隆对象中引用的其他对象。
- 如果原对象和浅克隆对象共享的子对象是不可变的,那么这种共享就是安全的。
- 但是通常子对象都是可变的,必须重新定义clone方法来建立一个深拷贝。
- 对于每一个类,需要确定:
- 默认的clone方法是否满足要求
- 是否可以在可变的子对象上调用clone来修补默认的clone方法
- 是否不该使用clone
- cloneable接口是 Java提供的一组标记接口(tagging interface)之一。它没有指定clone方法,这个方法是从Object类继承的。标记接口不包含任何办法,唯一的作用是允许类型检查时可使用instanceof。建议在自己的程序中不要使用标记接口。
- 即使clone的默认(浅拷贝)能够满足需求,还是要实现Cloneable接口,并把clone方法设置为public,再调用 super.clone() 。
- 如果在一个对象上调用clone,但是这个对象没有实现Cloneable接口,Object类的clone方法就会抛出一个 CloneNotSupportedException 。public Employee clone() throws CloneNotSupportedException 。
- 当心子类的克隆。一旦超类定义了public的clone方法,子类就能够直接继承,但是超类的clone方法不一定能够满足子类的拷贝。
- 所有的数组类型都有一个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表达式:
- lambda表达式是一个可传递的代码块,可以在以后执行一次或多次(将来的某个时间调用)。
- lambda表达式形式:参数 (→) {表达式} 。 没有参数也要提供空括号。如果可以推导出lambda表达式的参数类型,那么可以忽略其类型。
- 如果只有一个参数且该参数可以推导得出,可以省略 小括号"()" 。
- 无需指定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);
}
}
- 函数式接口:
- 对于只有一个抽象方法的接口(函数式接口),当需要这种接口的对象的时候,可以用lambda表达式替代。
- 最好把lambda表达式看作是一个函数,而不是一个对象。
- 与使用传统的内联类相比,lambda表达式高效得多。
- 不能把lambda表达式赋给类型为Object的变量,Object不是一个函数式接口。
- java.util.function包中定义了很多非常通用的函数式接口。
- Predicate接口,专门用来传递lambda表达式。
public interface Predicate<T>
{
boolean test(T t);
//...addition default and static methods
}
- 方法引用(method reference):
- Timer t = new Timer( 1000, System.out::println );
- 表达式System.out::println就是一个方法引用。等价于 x -> System.out.println(x) 。
- 方法引用的三种情况:
- object::instanceMethod // x -> System.out.println(x)
- Class::staticMethod // (x,y) -> Math.pow(x,y)
- Class::instanceMethod // (x,y) -> x.compareTo(y), 第一个参数会成为方法的目标。
- 类似于lambda表达式,方法引用不能独立存在,总是会转换成函数式接口的实例。
- 可以使用this参数和super参数
- this::instanceMethod
- super::instanceMethod
- 构造器引用:方法名为 new。如: Person::new 是Person构造器的一个引用。
- 可以用数组类型建立构造器引用,如:int[]::new 等价于 x -> new int[x] 。
- Java有一个限制,无法构造泛型类型T的数组。
- Person[] people = stream.toArray( Person[]::new ) 。
- 变量作用域:
- 有时,希望在lambda表达式中访问外围方法或类中的变量。
- lambda表达式的三个部分:
- 一个代码块
- 参数
- 自由变量的值(非参数且不在代码中定义的值)
- 表示lambda表达式的数据结构必须存储自由变量的值。在Java中,必须保证所捕获的值是明确定义的,lambda表达式中,只能引用值不会改变的变量(若可变,并发执行多次,可能会出现严重问题),捕获的变量必须实际上是最终变量。
- lambda表达式与嵌套块有相同的作用域,不能声明与局部变量同名的变量或参数。
- 在一个lambda表达式中使用this关键字,是指创建这个lambda表达式的方法的this参数。
- 处理lambda表达式(如何编写方法处理 lambda 表达式):
- lambda表达式的重点是 延迟执行。
- 常见的重要函数式接口:
- Runnable 无参数,无返回值,抽象方法名: run 。
- Supplier<T> 无参数,返回T, 抽象方法名 :get 。
- Consumer<T> 参数T,无返回值, 抽象方法名 : accept 。
- Function<T,R> 参数T, 返回T,抽象方法名 : apply 。
- Predicate<T> 参数T,返回boolean, 抽象方法名: test 。
- 基本类型的函数式接口:
- BooleanSupplier
- PSupplier
- PConsumer
- ObjPConsumer<T>
- ...Page 241 in 《core Java(10th edition)》
- 如果设计自己的接口,其中只有一个抽象方法,可以用 @FunctionalInterface 注解来标记。好处:若无意中为接口增添了非抽象方法,编译器提醒报错;javadoc页会指出此接口是函数式接口。
- 实例程序:( 测试函数式接口,传入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:
- Comparator接口包含很多方便的静态方法来创建比较器。
- 静态 comparing方法 取一个“键提取器”函数。将类型T映射成一个可比较的类型:如 Arrays.sort( people, Comparator.comparing( Person::getName ) );
- thenComparing方法。
- 如果键函数可以返回null,可能就要用到 nullsFirst 和 nullsLast 适配器。
- nullsFirst方法 需要一个比较器。Comparator.<String>naturalOrder() /reverseOrder()。
- Arrays.sort( people, Comparator.comparing( Person::getMiddleName, nullsFirst(Comparator.<String>naturalOrder() ) ) ) 。
- 示例程序:(测试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();
}
}
- 内部类:
- 内部类(inner class)是定义在另一个类中的类。
- 使用内部类的主要原因:
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。
- 内部类可以对同一个包中的其他类隐藏起来。
- 当想要定义一个回调函数且不想编写大量的代码时,使用 匿名内部类 比较便捷。
- 嵌套是一种类之间的关系而不是对象之间的关系。
- 内部类的对象有一个隐式引用,它引用了实例化该内部对象的外围类对象。通过这个指针,可以访问外围类对象的全部状态。
- static内部类没有上述的指针。
- 使用内部类访问对象状态:
- 内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。
- outer并不是java的关键字,只是用来说明外围引用的概念。
- 外围类的引用在构造器中由编译器自动添加/修改。
- public TimerPrinter( TalkingClock clock ) { outer = clock; }
- 只有内部类可以是私有类。
- 示例程序:(测试内部类,利用创建一个回调程序,每隔一定时间报时且响铃,并且响铃与否可以控制)
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);
}
}
- 内部类的特殊语法规则:
- 使用外围类的正规语法: OuterClass.this 。比如: if( TalkingClock.this.beep ) { ... } 。
- 在外围类的作用域之外,可以这样引用内部类: OuterClass.InnerClass 。
- 内部类声明的所有静态域都必须是final的。
- 内部类不能有static方法。
- 内部类是否有用、必要与安全:
- 内部类是一种编译器现象,与虚拟机无关。
- 如果内部类访问了私有数据域,就有可能通过附加在外围类所在包中的其他类访问它们。
- 局部内部类:类只在某方法中使用了一次,可以在一个方法中定义局部类。不能声明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
}
- 匿名类不能有构造器,取而代之的是将构造器传给超类构造器。
- Java程序员习惯的做法是用匿名内部类实现事件监听和其他回调,如今最好的做法是使用lambda表达式(简洁)。
- 示例程序:(使用匿名内部类实现事件监听回调)
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<String> 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,没有类名)。
- 静态内部类:
- 有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用。为此,可以将内部类声明为static,以便取消产生的引用。
- 应用:解决产生名字冲突的类的方法,将类定义为内部公有类。
- 只有内部类可以声明为static。静态内部类的对象除了没有对生成它的外围类对象的引用特权之外,与其他内部类一样。
- 与常规的内部类不同,静态类可以有静态域和静态方法。
- 声明在接口中的内部类自动成为static和public类。
- 示例程序:(只对数组编译一次,得到数组的最大值与最小值)
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)
- 利用代理可以在运行时创建一个实现了一组给定接口的新类。
- 这种功能只有在编译时无法确定实现哪个接口时才有必要使用。
- 对于系统程序设计的人员来说,代理带来的灵活性十分重要。
- 代理创建的类具有以下的方法:
- 指定接口所需要的全部方法
- Object类的全部方法
- 需要提供一个调用处理器,调用处理器是实现了 InvocationHandler接口的类对象。这个接口只有一个方法:Object invoke( Object proxy, Method method, Object[] args ) 。
- 无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用。
- 创建代理对象:
- Proxy类的newProxyInstance方法,三个参数:
- 一个类加载器( class loader )。用 null 表示使用默认的类加载器。
- 一个Class对象数组,每个元素都是需要实现的接口。
- 一个调用处理器。
- 代理类是在程序运行过程创建的,然而,一旦被创建,就变成了常规类,与虚拟机中的任何其他类没有区别。
- 所有的代理类都扩展于Proxy类。一个代理类只有一个实例域——调用处理器,定义在Proxy的超类中。
- 代理类一定是public和final的。
- 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);
}
}