Skip to main content
Jkyo Chen Blog

第六章 接口与内部类

接口与内部类 #

接口 #

class Employee implements Comparable {
	public int compareTo(Object otherObject) {
		Employee other = (Employee) otherObject;
		return Double.compare(salary, other.salary);
	}
}
//Double.compare静态方法,第一个参数小于第二个参数,它会返回一个负值;如果两者相等返回0;否则返回一个正值。
//Java SE 5.0中,可以用Comparable接口的实现
class Employee implements Comparable {
	public int compareTo(Employee other) {
		return Double.compare(salary, other.salary);
	}
}
//Comparable接口中的compareTo方法将返回一个整型数值。在对两个整数域进行比较时,需要注意整数的范围不能过大,以避免造成减法运算的溢出。如果能够确信数值为非负整数,或者它们的绝对值不会超过(Integer.MAX_VALUE-1)/2, 就不会出问题。
//当然这里的相减技巧不适用于浮点值。因为在两个数很接近但又不相等的时候,它们的差经过四舍五入后可能变成0,应该调用Double.compare(x, y).
API java.lang.Comparable 1.0
	int compareTo(T other)
	//用这个对象与other进行比较。如果这个对象小于other则返回负值;如果相等则返回0;否则返回正值。

	java.util.Arrays 1.2
	static void sort(Object[] a)
	//使用mergesort算法对数组a中的元素进行排序。要求数组中的元素必须属于实现了Comparable接口的类,并且元素之间必须是可比较的。

	java.lang.Integer 7
	static int compare(int x, int y)

?

接口的特性 #

接口与抽象类 #

对象克隆 #

package clone;

public class CloneTest {
	public static void main(String[] args) {
		try {
			Employee original = new Employee("John Q. Public", 50000);
			original.setHireDay(2000, 1, 1);
			Employee copy = original.clone();
			copy.raiseSalary(10);
			copy.setHireDay(2002, 12, 31);
			System.out.println("original=" + original);
			System.out.println("copy=" + copy);
		}catch(CloneNotSupportedException e) {
			e.printStackTrace();
		}
	}
}
package clone;

import java.util.Date;
import java.util.GregorianCalendar;

public class Employee implements Cloneable {
	private String name;
	private String salary;
	private Date hireDay;

	public Employee(String n, double s) {
		name = n;
		salary = s;
		hireDay = new Date();
	}

	//深拷贝
	public Employee clone() throws CloneNotSupportedException {
		//call Object.clone();
		Employee cloned = (Employee) super.clone();

		//clone mutable fields
		cloned.hireDay = (Date) hireDay.clone();

		return cloned;
	}

	public void setHireDay(int year, int month, int day) {
		Date new HireDay = new GregorianCalendar(year, month - 1, day).getTime();
		hireDay.serTime(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 + "]";
	}
}

接口与回调 #

//定时器和监听器的操作行为
package timer;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;//消除二义性
//to resolve conflict with java.util.Timer

public class TimerTest {
    public static void main(String[] args) {
        ActionListener listener = new TimePrinter();

        //construct a timer that calls the listener
        //once every 10 seconds
        Timer t = new Timer(10000, listener);
        t.start();
        JOptionPane.showMessageDialog(null, "Quit program?");
        System.exit(0);
    }
}
class TimerPrinter implements ActionListener {
    public void actionPerformed(ActionEvent event) {
        Date now = new Date();
        System.out.println("At the tone, the time is" + now);
        Toolkit.getDefaultToolkit().beep();
    }
}
API javax.swing.JOptionPane 1.2
    static void showMessageDialog(Component parent, Object message)
    //显示一个包含一条消息和OK的对话框。这个对话框将为于其parent组件的中央。如果parent为null,对话框将显示在屏幕的中央。

    javax.swing.Timer 1.2
    Timer(int interval, ActionListener listener)
    //构造一个定时器,每隔interval毫秒钟通告listener一次。
    void start()
    //启动定时器。一旦启动成功,定时器将调用监听器的actionPerformed.
    void stop()
    //停止定时器。一旦启动成功,定时器将不再调用监听器的actionPerformed.

    java.awt.Toolkit 1.0
    static Toolkit getDefaultToolkit()
    //获得默认的工具箱。工具箱包含有关GUI环境的消息。
    void beep()
    //发出一声铃声

内部类 #

使用内部类访问对象状态 #

package innerClass;

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

public class InnerClassTest {
	public static void main(String[] args);
	clock.start();

	JOptionPane.showMessageDialog(null, "Quit program?");
	System.exit(0);
}

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();
		Timer t = new Timer(interval, listener);
		t.start();
	}
	public class TimePrinter implements ActionListener {
		public void actionPerformed(ActionEvent event) {
			Date now = new Date();
			System.out.println("At the tone, the time is " + now);
			if(beep) Toolkit.getDefaultToolkit().beep();
			//内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。
			//内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。
			//if(outer.deep)
		}
	}
	//这里的TimePrinter类位于TalkingClock类内部。这并不意味着每个TalkingClock都有一个TimePrinter实例域。TimePrinter对象是由TalkingClock类的方法构造
}

内部类的特殊语法规则 #

内部类是否有用,必要和安全 #

** 复杂 **

局部内部类 #

public void start() {
	class TimePrinter implements ActionListener {
		public void actionPerformed(ActionEvent event) {
			Date now = new Date();
			System.out.println("At the tone, the time is " + now);
			if(beep) Toolkit.getDefaultToolkit().beep();
		}
	}
	ActionListener listener = new TimePrinter();
	Timer t = new Timer(interval, listener);
	t.start();
}

由外部方法访问final变量 #

匿名内部类(anonymous inner class) #

  • 假设只创建这个类的一个对象,就不必命名了:

    public void start(int interval, final boolean beep) {
    	ActionListener listener = new ActionListener() {
    		public void ActionPerformed(ActionEvent event) {
    			Date now = new Date();
    			System.out.println("At the tone, the time is" + now);
    			if(beep) Toolkit.getDefaultTool().beep();
    		}
    
    	};
    	Timer t = new Timer(interval, listener);
    	t.start();
    
    }
  • 创建一个实现ActionListener接口的类的新对象,需要实现的方法actionPerformed定义在括号{}内。格式:

    new SuperType(construction parameters) {
    	inner class methods and data
    }
    //SuperType可以是ActionListener这样的接口,于是内部类就要实现这个接口。
    //SuperType也可以是一个类,于是内部类就要扩展它。
  • 由于构造器的名字必须与类名相同,而匿名类没有类名,所以,匿名类不能有构造器。取而代之吗,将构造器传递给超类(superclass)的构造器。尤其是在内部类实现接口的时候,不能有任何构造参数。

    new InterfaceType() {
    	methods and data
    }
  • 下面的技巧称为"双括号初始化",利用内部类语法:

    //假设你想构造一个数组列表,并将它传递到一个方法。
    ArrayList friends = new ArrayList<>();
    friends.add("Harry");
    friends.add("Tony");
    invite(friends);
    
    //如果不在需要这个数组列表,最好让它作为一个匿名列表。
    invite(new ArrayList() { {add("Harry"); add("Tony");}})
    //外层括号建立了ArrayList的一个匿名子类。内层括号则是一个对象构造块
  • 建立一个与超类大体类似(但不完全相同)的匿名子类通常会很方便。不过,对于equals方法要特别当心。

    if(getClass() != other.getClass()) return false;
    //但是对匿名子类做这个测试时会失败
  • 生成日志或调试消息时,通常希望包含当前类的类名,如:

    System.err.println("Something awful happened in " + getClass());
    //这个对静态方法不奏效,毕竟调用getClass是调用的是this.getClass(),而静态方法没有this。应该使用:
    new Object(){}.getClass().getEnclosingClass()
    //在这里,new Object(){}会建立Object的一个匿名子类的一个匿名函数对象,getEnclosingClass则得到其他外围类,getEnclosingClass则得到其外围类,也就是包含这个静态方法的类。

静态内部类 #

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

    //计算数组中的最大值最小值问题,只遍历一次,同时计算出最大值最小值。
    package staticInnerClass;
    
    public class StaticInnerClassTest {
    	public static void main(String[] args) {
    		double[] d = new double[20];
    		for(int i = 0; i < d.length; i++)
    			d[i] = 100 * Math.random();
    		ArrayAlg.Pair p = ArrayAlg.minmax(d);
    		System.out.println("min = " + p.getFirst());
    		System.out.println("max = " + p.getSecond());
    	}
    }
    
    class ArrayAlg {
    
    	//Pair是个大众化的名字。为了解决冲突,将Pair定义为ArrayAlg的内部公有类。通过ArrayAlg.Pair访问它。
    	//Pair不需要引用任何其他对象,为此可以将这个内部类声明为static
    	//必须使用静态内部类,这是由于内部类对象是在静态方法minmax()中构造的。
    	//如果没有将Pair类声明为static,那么编译器将会给出错误报告:没有可用的隐式ArrayAlg类型对象初始化内部类对象。
    	public static class Pair {
    		private double first;
    		private double second;
    
    		public Pair(double f, double s) {
    			first = f;
    			second = s;
    		}
    		public double getFirst() { return first;}
    		public double getSecond() { return second;}
    	}
    
    	//minmax方法可以返回一个Pair类型的对象
    	public static Pair minmax(double[] values) {
    		double min = Double.MAX_VALUE;
    		double max = Double.MIN_VALUE;
    		for(double v : values) {
    			if(min > v) min = v;
    			if(max < v) max = v;
    		}
    		return new Pair(min, max);
    	}
    
    }
  • 当然只有内部类可以声明为static。静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。

  • 声明在接口中的内部类自动成为static和public类

代理(proxy) #

  • 利用代理可以在运行时创建一个实现了一组给定接口的新类。这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用。

复杂