1、接口
接口主要用来描述类具有什么功能,并且不给出每个功能的具体实现,接口不是类。
一个类可以实现(implement)一个或多个接口(interface),并在需要接口的地方,随时使用实现了相应接口的对象。
接口中的所有方法自动地属于 public。
接口绝不能含有实例域,也不能在接口中实现方法。提供实例域和方法实现的任务由实现接口的那个类完成。
可以将接口看成是没有实例域的抽象类。
实现一个接口,必须:1)将类声明为实现给定的接口
2)对接口中的所有方法进行定义
实例:Arrays 类中的 sort 方法可以对对象数组进行排序,但对象所属的类必须实现了 Comparable 接口,如下:
public interface Comparable<T>
{
int compareTo(T other);
}
实现这个接口,必须提供 compareTo(Employee other) 方法。
实现接口:
class Employee implements Comparable<Employee>
{
//......
public int compareTo(Employee other)
{
if (salary < other.salary) return -1;
if (salary > other.salary) return 1;
return 0;
}
//.....
}
完整程序:
import java.util.*;
public class EmployeeSortTest
{
public static void main(String[] args)
{
Employee[] staff = new Employee[3];
staff[0] = new Employee("Ai",35000);
staff[1] = new Employee("Bi",75000);
staff[2] = new Employee("Ci",38000);
Arrays.sort(staff);
for (Employee e : staff)
System.out.println("name="+e.getName()+",salary="
+e.getSalary());
}
}
class Employee implements Comparable<Employee> //实现接口的类
{
public Employee(String n,double s)
{
name = n;
salary = s;
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
public int compareTo(Employee other)
{
if (salary < other.salary) return -1;
if (salary > other.salary) return 1;
return 0;
}
private String name;
private double salary;
}
运行结果
(1)接口的特性
接口不是类,尤其不能使用 new 运算符实例化一个接口,但尽管不能构造接口的对象,却能声明接口的变量,接口的变量必须引用实现了接口的类对象:
Comparable x;
x = new Employee(...);
也可以使用 instance 检查一个对象是否实现了某个特定的接口:if(anObject instanceof Comparable){...}
接口也可以被扩展(继承)。
在接口中不能包含实例域或静态方法,但却可以包含常量。
尽管每个类智能够拥有一个超类,但却可以实现多个接口(使用逗号隔开)。
(2)接口与抽象类
为何不直接使用抽象类而使用接口?
因为每个类智能扩展于一个类(java不支持C++中的多继承),但每个类可以实现多个接口。
2、对象克隆
当拷贝一个变量时,原始变量与拷贝变量引用同一个对象,改变引用的对象时,会同步改变两个变量。如想要创建一个
对象的新的 copy 之后,不再相互影响,就需要使用 clone 方法。
clone 方法是 Object 类的一个 proteced 方法,若子对象可变,则必须重新定义 clone 方法,实现克隆子对象的深拷贝。
所有的数组类型均包含一个 clone 方法(public),可以利用这个方法创建一个包含所有数据元素拷贝的一个新数组。
对于每一个类,都需要做出如下判断:
1)默认的 clone 方法是否满足要求
2)默认的 clone 方法是否能够通过调用可变子对象的 clone 得到修补
3)是否不应该使用 clone ,实际上这个选择是默认的,如果要选前两项,类必须:
1)实现 Cloneable 接口(一种标记接口)
2)使用 public 访问修饰符重新定义 clone 方法。
实例:
import java.util.*;
public class CloneTest
{
public static void main(String[] args)
{
try
{
Employee original = new Employee("Ai.Public",50000);
original.setHireDay(1991,1,1);
Employee copy = original.clone(); //克隆
copy.raiseSalary(10);
copy.setHireDay(1993,12,31);
System.out.println("original="+original);
System.out.println("copy="+copy);
}
catch (CloneNotSupportedException e)
{
e.printStackTrace();
}
}
}
class Employee implements Cloneable//实现接口
{
public Employee(String n,double s)
{
name = n;
salary = s;
hireDay = new Date();
}
//只要在 clone 中没有实现 Cloneable 接口的对象,Object 类的 clone 方法就会
//抛出一个 ClontNot-SupportException 异常,实现之后,还需声明异常如下:
public Employee clone() throws CloneNotSupportedException
{
Employee cloned = (Employee) super.clone();//调用 Object.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+"]";
}
private String name;
private double salary;
private Date hireDay;
}
运行结果:
3、接口与回调
回调(callback)是一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取的动作。
在 java.swing 包中有一个 Timer 类(定时器),使用时要求相应的类实现了 java.awt.event 包的 ActionListener 接口:
public interface ActionListener
{
void actionPerformed(ActionEvent event);
}
java 有函数指针的对应物:Method 对象,但使用比较困难,因此,在任何使用 C++ 函数指针的地方,都应该考虑使用java中的接口
完整程序:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
public class TimerTest
{
public static void main(String[] args)
{
ActionListener listener = new TimePrinter();
//构造一个调用 listener 的定时器,10秒钟每次
//Time(int interval,AvtionListener listener):构造
//一个定时器,每隔 interval 毫秒钟通告 listener 一次。
Timer t = new Timer(10000,listener);
t.start();//启动定时器
//显示一个包含一条消息和 OK 按钮的对话框,位于屏幕的中央
JOptionPane.showMessageDialog(null,"Quit program?");
System.exit(0);
}
}
class TimePrinter implements ActionListener //实现 ActionListener 接口的类
{
public void actionPerformed(ActionEvent event)
{
Date now = new Date();
System.out.println("At the tone,the time is "+now);
Toolkit.getDefaultToolkit().beep();
//Toolkit.getDefaultToolkit():获得默认工具箱,beep():发出一声铃响
}
}
程序启动后,弹出 "Quit program?" 字样对话框,每10秒显示一次定时器消息,按 OK 退出
运行结果:
4、内部类
内部类(inner class)是定义在另一个类中的类,使用内部类的原因:
a)内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据
b)内部类可以对同一个包中的其他类隐藏起来
c)当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷
(1)使用内部类访问对象状态
内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。这个引用在内部类的定义中是不可见的。为了说明这个概念
我们将外围类对象的引用称为 outer,下面的 if(beep)相当于 if(outer.beep)
实例:
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[] agrs)
{
TalkingClock clock = new TalkingClock(1000,true);
clock.start();
JOptionPane.showMessageDialog(null,"Quit program?");
System.exit(0);
}
}
class TalkingClock
{
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();
}
private int interval;
private boolean beep;
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(); //这里的 beep 是引用外围类对象的数据域
}
}
}
运行结果:
(2)局部内部类
上例中,TimePrinter 这个类名字只在 start 方法中创建这个类型的对象时使用了一次,因此,也可以就在此方法中定义局部类:
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();
}
局部类不能用 public 或 private 访问说明符进行声明,它的作用域被限定在声明这个局部类的块中。
局部类有一个优势,即对外部世界(即使是 TalkingClock 类中的其他代码)可以完全地隐藏起来。
(3)匿名内部类
假如只创建这个局部内部类的一个对象,就不必命名了,这种类被称为匿名内部类。
匿名类不能有构造器,取而代之的是将构造器参数传递给超类构造器。下面实例中的匿名内部类的含义是:创建一个实现 ActionListener 接口的类的新对象,需要实现的方法 actionPerformed 定义在括号内。
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
public class AnonymousInnerClassTest
{
public static void main(String[] agrs)
{
TalkingClock clock = new TalkingClock();
clock.start(1000,true);
JOptionPane.showMessageDialog(null,"Quit program?");
System.exit(0);
}
}
class TalkingClock
{
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.getDefaultToolkit().beep();
}
};
Timer t = new Timer(interval,listener);
t.start();
}
}
(4)静态内部类
有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围对象,为此,可以将内部类声明为 static。
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
{
public static class Pair //包含最大值最小值的类
{
public Pair(double f, double s)
{
first = f;
second = s;
}
public double getFirst()
{
return first;
}
public double getSecond()
{
return second;
}
private double first;
private double second;
}
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 和 public。
5、代理
利用代理可以在运行时创建一个实现了一组给定接口的新类。这种功能只有在编译时无法确定要实现哪个接口时才有必要使用。