概述
使用的教材是java核心技术卷1,我将跟着这本书的章节同时配合视频资源来进行学习基础java知识。
day040 接口示例(接口与回调、Comparator接口、对象克隆)
1.接口与回调
回调(callback)是一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取的动作。例如,可以指出在按下鼠标或选择某个菜单项时应该采取什么行动。
在java.swing包中有一个Timer类,可以使用它在到达给定的时间间隔时发出通告。例如,假如程序中有一个时钟,就可以请求每秒钟获得一个通告,以便更新时钟的表盘。
在构造定时器时,需要设置一个时间间隔,并告之定时器,当到达时间间隔时需要做些什么操作。
如何告之定时器做什么呢?在很多程序设计语言中,可以提供一个函数名,定时器周期性地调用它。但是,在Java标准类库中的类采用的是面向对象方法。它将某个类的对象传递给定时器,然后,定时器调用这个对象的方法。由于对象可以携带一些附加的信息,所以传递一个对象比传递一个函数要灵活得多。
当然,定时器需要知道调用哪一个方法,并要求传递的对象所属的类实现了java.awt.event包的ActionListener接口。下面是这个接口:
public interface ActionListener
{
void actionPerfonned(ActionEvent event);
}
当到达指定的时间间隔时,定时器就调用actionPerformed方法。
假设希望每隔10秒钟打印一条信息“At the tone,the time is...”,然后响一声,就应该定义一个实现ActionListener接口的类,然后将需要执行的语句放在actionPerformed方法中。
class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
System.out.println ("At the tone,the time is"+newOate());
Toolkit.getDefaultToolkit().beep();
}
}
需要注意actionPerformed方法的ActionEvent参数。这个参数提供了事件的相关信息,例如,产生这个事件的源对象。在这个程序中,事件的详细信息并不重要,因此,可以放心地忽略这个参数。
接下来,构造这个类的一个对象,并将它传递给Timer构造器。
ActionListener listener = new TimePrinter();
Timer t = new Timer(10000,listener);
Timer构造器的第一个参数是发出通告的时间间隔,它的单位是毫秒。这里希望每隔10秒钟通告一次。第二个参数是监听器对象。
最后,启动定时器:
t.start();
每隔10秒钟,下列信息显示一次,然后响一声铃。
At the tone,the time is Wed Apr13 23:29:08 PDT 2016
下面的程序给出了定时器和监听器的操作行为。在定时器启动以后,程序将弹出一个消息对话框,并等待用户点击Ok按钮来终止程序的执行。在程序等待用户操作的同时,每隔10秒显示一次当前的时间。
运行这个程序时要有一些耐心。程序启动后,将会立即显示一个包含“Quit program?”字样的对话框,10秒钟之后,第1条定时器消息才会显示出来。
需要注意,这个程序除了导入javax.swing.*和java.util.*外,还通过类名导入了javax.swing.Timer。这就消除了javax.swing.Timer与java.util.Timer之间产生的二义性。这里的java.util.Timer是一个与本例无关的类,它主要用于调度后台任务。
/**
*@author zzehao
*/
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 TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone, the time is " +new Date());
Toolkit.getDefaultToolkit().beep();
}
}
运行的结果:
2.Comparator接口
之前,我们已经了解了如何对一个对象数组排序,前提是这些对象是实现了Comparable接口的类的实例,例如,可以对一个字符串数组排序,因为String类实现了Comparable<String>,而且String.compareTo方法可以按字典顺序比较字符串。
现在假设我们希望按长度递增的顺序对字符串进行排序,而不是按字典顺序进行排序。肯定不能让String类用两种不同的方式实现compareTo方法—更何况,String类也不应由我们来修改。
要处理这种情况,Arrays.sort方法还有第二个版本,有一个数组和一个比较器(comparator)作为参数,比较器是实现了Comparator接口的类的实例。
public interface Comparators<T>
{
int compare(T first,T second);
}
要按长度比较字符串,可以如下定义一个实现Comparator的类:
class LengthComparator implements Comparator<String>
{
public int compare(String first,String second)
{
return first.length()-second.length();
}
}
具体完成比较时,需要建立一个实例:
Comparator<String> comp = new LengthComparator();
if(comp.compare(words[i],words[j])>0)...
将这个调用与words[i].compareTo(words[j])做比较。这个compare方法要在比较器对象上调用,而不是在字符串本身上调用。
尽管LengthComparator对象没有状态,不过还是需要建立这个对象的一个实例。我们需要这个实例来调用compare方法它不是一个静态方法。
要对一个数组排序,需要为Arrays.sort方法传人一个LengthComparator对象:
String[] friends={"Peter","Paul","Mary"};
Arrays.sort(friends,new LengthComparator());
现在这个数组可能是["Paul","Mary","Peter"]或["Mary","Paul","Peter"]。
3.对象克隆
Cloneable接口,这个接口指示一个类提供了一个安全的clone方法。
要了解克隆的具体含义,先来回忆为一个包含对象引用的变量建立副本时会发生什么。原变量和副本都是同一个对象的引用,如下图。这说明,任何一个变量改变都会影响另一个变量。
Employee original = new Employee("John Public",50000);
Employee copy = original;
copy.raiseSalary(l0);
//oops-also changed original
如果希望copy是一个新对象,它的初始状态与original相同,但是之后它们各自会有自己不同的状态,这种情况下就可以使用clone方法。
Employee copy = original.clone();
copy.raiseSalary(l0);//OK original unchanged
不过并没有这么简单。clone方法是Object的一个protected方法,这说明你的代码不能直接调用这个方法。只有Employee类可以克隆Employee对象。这个限制是有原因的。想想看Object类如何实现clone。它对于这个对象一无所知,所以只能逐个域地进行拷贝。如果对象中的所有数据域都是数值或其他基本类型,拷贝这些域没有任何问题、但是如果对象包含子对象的引用,拷贝域就会得到相同子对象的另一个引用,这样一来,原对象和克隆的对象仍然会共享一些信息。
为了更直观地说明这个问题,考虑介绍过的Employee类。下图显示了使用Object类的clone方法克隆这样一个Employee对象会发生什么。可以看到,默认的克隆操作是“浅拷贝”,并没有克隆对象中引用的其他对象。(这个图显示了一个共享的Date对象。出于某种原因(稍后就会解释这个原因),这个例子使用了Employee类的老版本,其中的雇用日期仍用Date表示。)
浅拷贝会有什么影响吗?这要看具体情况。如果原对象和浅克隆对象共享的子对象是不可变的,那么这种共享就是安全的。如果子对象属于一个不可变的类,如String,就是这种情况。或者在对象的生命期中,子对象一直包含不变的常量,没有更改器方法会改变它,也没有方法会生成它的引用,这种情况下同样是安全的。
不过,通常子对象都是可变的,必须重新定义clone方法来建立一个深拷贝,同时克隆所有子对象。在这个例子中,hireDay域是一个Date,这是可变的,所以它也需要克隆。(出于这个原因,这个例子使用Date类型的域而不是LocalDate来展示克隆过程。如果hireDay是不可变的LocalDate类的一个实例,就无需我们做任何处理了。)
对于每一个类,需要确定:
1)默认的clone方法是否满足要求;
2)是否可以在可变的子对象上调用clone来修补默认的clone方法;
3)是否不该使用clone0
实际上第3个选项是默认选项。如果选择第1项或第2项,类必须:
1)实现Cloneable接口;
2)重新定义clone方法,并指定public访问修饰符。
Object类中clone方法声明为protected,所以你的代码不能直接调用anObject.clone()。但是,不是所有子类都能访问受保护方法吗?不是所有类都是Object的子类吗?幸运的是,受保护访问的规则比较微妙(前面有介绍)。子类只能调用受保护的clone方法来克隆它自己的对象。必须重新定义clone为public才能允许所有方法克隆对象。
在这里,Cloneable接口的出现与接口的正常使用并没有关系。具体来说,它没有指定done方法,这个方法是从Object类继承的。这个接口只是作为一个标记,指示类设计者了解克隆过程。对象对于克隆很“偏执”,如果一个对象请求克隆,但没有实现这个接口,就会生成一个受査异常。
即使clone的默认(浅拷贝)实现能够满足要求,还是需要实现Cloneable接口,将clone重新定义为public,再调用super.clone()。下面给出一个例子:
class Employee implements Cloneable
{
//raise visibility level to public,change return type
public Employee clone() throws CloneNotSupportedException
{
return (Employee)super.clone();
}
...
}
与Object.clone提供的浅拷贝相比,前面看到的clone方法并没有为它增加任何功能。这里只是让这个方法是公有的。要建立深拷贝,还需要做更多工作,克隆对象中可变的实例域。下面来看创建深拷贝的clone方法的一个例子:
class Employee implements Cloneable
{
...
public Employee clone() throws CloneNotSupportedException
{
//call Object.clone()
Employee cloned = (Employee)super.clone();
//clone mutable fields
cloned.hireDay=(Date)hireDay.clone();
return cloned;
}
}
如果在一个对象上调用clone,但这个对象的类并没有实现Cloneable接口,Object类的clone方法就会拋出一个CloneNotSupportedExceptionD当然,Employee和Date类实现了Cloneable接口,所以不会抛出这个异常。不过,编译器并不了解这一点,因此,我们声明了这个异常:
public Employee clone() throws CloneNotSupportedException
捕获这个异常是不是更好一些?
public Employee clone()
{
try
{
Employee cloned = (Employee) super.clone();
...
}
catch (CloneNotSupportedException e) { return null; }
//this won't happen, since we are Cloneable
}
这非常适用于final类。否则,最好还是保留throws说明符。这样就允许子类在不支持克隆时选择抛出一个CloneNotSupportedException。
必须当心子类的克隆。例如,一旦为Employee类定义了clone方法,任何人都可以用它来克隆Manager对象。Employee克隆方法能完成工作吗?这取决于Manager类的域。在这里是没有问题的,因为bonus域是基本类型。但是Manager可能会有需要深拷贝或不可克隆的域。不能保证子类的实现者一定会修正clone方法让它正常工作。出于这个原因,在Object类中clone方法声明为protected。不过,如果你希望类用户调用clone,就不能这样做。
要不要在自己的类中实现clone呢?如果你的客户需要建立深拷贝,可能就需要实现这个方法。有些人认为应该完全避免使用clone,而实现另一个方法来达到同样的目的。clone相当笨拙,这一点我们也同意,不过如果让另一个方法来完成这个工作,还是会遇到同样的问题。毕竟,克隆没有你想象中那么常用。标准库中只有不到5%的类实现了clone。
下面程序1中的程序克隆了Employee类(程序2)的一个实例,然后调用两个更改器方法。raiseSalary方法会改变salary域的值,而setHireDay方法改变hireDay域的状态。这两个更改器方法都不会影响原来的对象,因为clone定义为建立一个深拷贝。
程序1:
/**
*@author zzehao
*/
public class CloneTest
{
public static void main(String[] args)
{
try
{
Employee original = new Employee("]ohnQ. 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();
}
}
}
程序2:
/**
*@author zzehao
*/
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
{
//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 newHireDay = new GregorianCalendar(year, month - 1, day).getTime();
//Example of instance field mutation
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+"]";
}
}
运行的结果:
最后
以上就是明理荷花为你收集整理的Java学习day040 接口示例(接口与回调、Comparator接口、对象克隆)day040 接口示例(接口与回调、Comparator接口、对象克隆)1.接口与回调的全部内容,希望文章能够帮你解决Java学习day040 接口示例(接口与回调、Comparator接口、对象克隆)day040 接口示例(接口与回调、Comparator接口、对象克隆)1.接口与回调所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复