接口概念与特性
Java接口时一系列方法的声明,是一些特征方法的集合,一个接口只有方法的特征而没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以有不同的行为。
以下以Comparable接口为例,该接口有一个compareTo方法,接受一个Object对象,返回一个整型数值。(用于比较大小)
让一个类实现一个接口的步骤:
1)将类声明为实现给定的接口,使用implements关键字;
2)对接口中所有方法进行定义。
class A implements Comparable{ public int ComparaTo(Object obj){ ...... } }
接口的一些特性
1)接口中所有方法自动地属于public,因此,在接口声明方法中,不必提供关键字public。
2)接口不能包含实例域或静态方法,可以包含常量,常量自动地属于public static final。
3)实现接口时,必须将方法声明为public,与1)比较,该处为实现时。
public int compareTo(Object obj){...}
4)接口不是类,不能实例化接口;可以声明接口的变量,但该变量必须引用实现了该接口的类对象。
c = new Comparable(...) //错误 Comparable c;//正确 c = new Employee(...) //若Employee类实现了Comparable接口,此语句正确,否则错误
5)接口允许被拓展,使用extends关键字可以将接口拓展成另一个接口。
public interface A{...} public interface B extends A{...} //此时,接口B将包含A的方法和它自己定义的方法
6)尽管每个类只能有一个超类,但却可以实现多个接口;使用逗号分割开多个接口。
class A implements Comparable, Cloneable
一个实例:Employee类实现Comparable接口,使得能够使用Array.sort方法对工资排序。
1 package interfaces; 2 3 public class Employee implements Comparable<Employee> { 4 private String name; 5 private double salary; 6 7 public Employee(String name, double salary){ 8 this.name = name; 9 this.salary = salary; 10 } 11 12 public String getName() { 13 return name; 14 } 15 16 public double getSalary() { 17 return salary; 18 } 19 20 public void raiseSalary(double byPercent){ 21 double raise = salary * byPercent/100; 22 salary += raise; 23 } 24 25 public int compareTo(Employee other){ 26 return Double.compare(salary,other.salary); 27 } 28 }
1 package interfaces; 2 3 import java.util.Arrays; 4 5 public class EmployeeSortTest { 6 public static void main(String[] args){ 7 Employee[] staff = new Employee[3]; 8 staff[0] = new Employee("Harry",30000); 9 staff[1] = new Employee("Tony", 52000); 10 staff[2] = new Employee("Tom", 36000); 11 12 Arrays.sort(staff); 13 14 for (Employee e:staff){ 15 System.out.println("name="+e.getName()+",salary="+e.getSalary()); 16 } 17 } 18 }
1 name=Harry,salary=30000.0 2 name=Tom,salary=36000.0 3 name=Tony,salary=52000.0
接口与抽象类
在c++中,一个类允许有多个超类,该特性称为多继承,而Java不支持多继承。因而,使用抽象类作为通用属性就存在这样的问题:当一个类已经继承于一个类,就不能再拓展于第二个类了。接口解决了这个问题,因为一个类可以实现多个接口。
以下为抽象类和接口的区别与相似性:
抽象类 | 接口 |
不能被实例化 | |
被子类继承 | 被子类实现 |
包含或不包含抽象方法 | 包含或不包含抽象方法(java se 8后可以在接口中实现方法) |
抽象方法必须被子类实现,否则子类也必须声明为抽象的 | 抽象方法必须被子类实现 |
一个类只能有一个抽象(或普通)的超类 | 一个类可以实现多个接口 |
表示该对象是什么 | 一般表示该对象能做什么 |
abstract修饰 | implements修饰 |
默认方法与冲突
可以为使用default关键字为接口方法提供一个默认的实现。
public interface A{ default int func(){...} }
这样,实现这个接口时可以选择不覆盖该方法。
如果一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义了同样的方法。这样就可能会出现命名冲突,Java解决办法
1)超类优先。如果一个超类提供了具体的方法,接口中的同名同参数的方法将被忽略。
2)接口冲突。若两个接口有同样的方法的实现,那么必须在子类中覆盖该方法。
接口与回调
回调时一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取的动作。例如按下鼠标产生什么效果。下面是一个例子:
java.swing中有一个Timer类,可以使用它在到达指定时间间隔发出通告。创建一个对象实现java.awt.event包中的ActionListener接口。到达指定间隔时间发生一些事情。
1 package timer; 2 3 import javax.swing.*; 4 import java.awt.*; 5 import java.awt.event.ActionEvent; 6 import java.awt.event.ActionListener; 7 import java.util.Date; 8 9 public class TimerTest { 10 11 public static void main(String[] args) { 12 ActionListener listener = new TimePrinter(); 13 14 Timer t = new Timer(10000, listener);//定时器对象,传递对象 15 t.start(); 16 JOptionPane.showMessageDialog(null, "quit program");//产生消息框 17 System.exit(0); 18 } 19 } 20 21 class TimePrinter implements ActionListener{ 22 public void actionPerformed(ActionEvent event){//实现事件响应方法 23 System.out.println("at the time, the time is "+ new Date()); 24 Toolkit.getDefaultToolkit().beep();//发出铃响 25 } 26 }
接口与深浅拷贝
浅拷贝:原变量和副本都是同一个对象的引用。
Employee e = new Employee(); Employee copy = e;
深拷贝:元变量和副本属于不同的引用,有相同的状态。
这里需要实现Cloneable接口,这个接口指示一个类提供clone方法。它是Object的一个protected方法。这说明不能直接调用这个方法。
设计为protected的原因:Object类对某个对象进行clone,它逐一地拷贝所有域。如果域是基本类型自然没有问题,如果域包含一个对象的引用,那么拷贝这个域将得到相同的引用。如此一来,拷贝对象仍然和原对象共享一些信息。
也就是说,如果默认clone方法不能满足需求(课变得而子对象),那么必须:
1)实现Cloneable接口
2)重新定义clone方法,指定public修饰符。
以下例子:拷贝一个Employee实例,修改日期
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; hireDay = new Date(); } public Employee clone() throws CloneNotSupportedException{//实现克隆方法 Employee cloned = (Employee) super.clone();//此时name和hireDay是引用 cloned.hireDay = (Date) hireDay.clone();//拷贝hireDay 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 + "]"; } }
1 package clone; 2 3 public class CloneTest { 4 public static void main(String[] args){ 5 try{ 6 Employee original = new Employee("John", 50000); 7 original.setHireday(2000,1,1); 8 Employee copy = original.clone(); 9 copy.setHireday(2002,12,31); 10 System.out.println("original="+original); 11 System.out.println("copy="+copy); 12 }catch (CloneNotSupportedException e){ 13 e.printStackTrace(); 14 } 15 } 16 }
结果:
original=Employee[name=John,salary=50000.0,hireDay=Sat Jan 01 00:00:00 CST 2000]
copy=Employee[name=John,salary=50000.0,hireDay=Tue Dec 31 00:00:00 CST 2002]