接口与内部类


本章代码

接口

接口不是类,而是对类的一组需求描述。以Comparable接口为例,对象所属的类如果实现了Comparable接口,Arrays类中的sort方法就可以对该类的对象数组进行排序。接口中的方法自动属于public,无需提供修饰符。为了让类实现一个接口,通常需要下面两个步骤

1)将类声明为实现给定的接口
2)对接口中的所有方法进行定义

    class Employee implements Comparable{public int compareTo(Object otherObject){Employee other = (Employee)otherObject;return Double.compare(salary,other.salary);}...}

Java SE 5.0 中,可以做得更好。

    class Employee implements Comparable<Employee>{public int compareTo(Employee other){return Double.compare(salary,other.salary);}...}

这样就不用对参数Object进行类型转换了。但是这种做法又会在继承中出现问题,Manager类继承了Employee类,但是Employee实现的是Comparable<Employee>接口。解决方法和equals类似。如果子类之间比较的含义不一样,那么应该在compareTo方法开始时进行下列检测。

    if(getClass() != other.getClass()) throw new ClassCastException();

如果存在一个通用算法,它能够对不同的子类对象进行比较,则应该在超类中提供这样的compareTo方法,并将方法声明为final。

6.1.1 接口的特性

接口不是类,尤其是不能使用new实例化一个接口。尽管不能构造接口的对象,却能声明接口的变量。例如

    Comparable x;  //OKx = new Employee(...)  //OK, provided Employee implements Comparable

也可以使用instanceof检查一个对象是否实现了某个特定的接口:

    if(anObject instanceof Comparable){...}

接口也可以被扩展,如同继承链一样。

    public interface Moveable{void move(double x, double y);}   public interface Powered extends Moveable{double milesPerGallon();}//接口中虽然不能包含实例域或静态方法,却可以包含常量public interface Powered extends Moveable{double milesPerGallon();double SPEED_LIMIT = 95; //a public static final constant}

接口中的域被自动设为public static final。

6.1.2 接口与抽象类

每个类只能继承一个类,不能多继承,但是一个类可以扩展多个接口。

    class Employee extends Person, OtherClass //ERRORclass Employee implements Cloneable, Comparable //OKclass Employee extends Person implements Comparable // OK

6.2 对象克隆

当拷贝一个变量时,原始变量与拷贝变量引用同一个对象,改变任意一个都会对另一个产生影响。如果想不影响,就需要利用到clone方法。

clone方法时Object类的一个proteced方法,在用户编写的代码中不能直接调用它。默认的clone操作是浅拷贝。它不能拷贝包含在对象中的内部对象。对每一个类,都要做出如下判断

1) 默认的clone方法是否满足要求
2) 默认的clone方法是否能够通过调用可变子对象的clone得到修补
3) 是否不应该使用clone

选项3 是默认的,如果要选择1或2,类必须

实现Cloneable接口
使用pbulic重新定义clone方法。

如果一个对象需要克隆,而没有实现Cloneable接口,就会产生一个已检验异常。

    class Employee implements Cloneable{public Employee clone() throws CloneNotSupportedException{return (Employee) super.clone();}}

注意,需要声明异常。


6.3 接口与回调

回调是一种常见的程序设计模式,可以指出某个特定事件发生时应该采取的动作。从一个简单的程序看看接口与回调。javax.swing中的Timer类,可以使用它在到达给定的时间间隔时发出通告。定时器需要知道调用哪一个方法,并要求传递的对象所属的类实现了java.event包的ActionListener接口,当到达指定时间间隔,定时器就调用actionPerformed方法。

    public interface ActionListener{void actionPerformed(ActionEvent event);}

例如

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

接下来,构造一个这个类的一个对象,传递个Timer的构造器

    ActionListener listener = new TimerPrinter();Timer t = new Timer(10000,listener);t.start();          //启动定时器

来看看整个程序吧,很简单。

import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;import javax.swing.JOptionPane;
import javax.swing.Timer;public class TimerTest {public static void main(String[] args){ActionListener listener = new TimePrinter();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){Date now = new Date();System.out.println("At the tone, the time is "+ now);Toolkit.getDefaultToolkit().beep();}
}

6.4 内部类

内部类是定义在另一个类中的类。

内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
内部类可以对同一个包中的其他类隐藏起来。
当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。

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

用TalkingClock类来介绍一下内部类的使用方式

    public class TalkingClock{private int interval;private boolean beep;public TalkingClock(int interval, boolean beep){...}public void start(){...}//an inner classpublic class TimerPrinter implements ActionListener{...}}

TimePrinter 位于 TalkinClock类的内部,它的对象,由TalkingClock类的方法构造。

    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();}}

TimPrinter 类并没有任何数据域,但是确能够访问外围类的beep域。内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。为了说明这个概念,将外围类对象的引用称为outer。

    public void actionPerformed(ActionEvent event){Date now = new Date();System.out.println("At the tone, the time is "+now);if(outer.beep) Toolkit.getDefaultToolkit().beep();}}

那么outer这个引用何时被设置呢?编译器修改了所有内部类的构造器,添加一个外围类引用的参数。由于TimePrinter类没有定义构造器,所以编译器为这个类生成了一个默认的构造器。

    public TimePrinter(TalkingClock clock)  //自动生成的代码{outer = clock}

当在外围类中的方法创建TimePrinter对象后,编译器就会将this引用传递给outer

    ActionListener listener = new TimePrinter(this); //参数是被自动添加的,实际并不需要这样写

可以看到,outer此时和this指向同一个对象,也就是这个外围类。看看这个简单的程序吧。

import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;import javax.swing.JOptionPane;
import javax.swing.Timer;public class InnerClassTest {public static void main(String[] args){TalkingClock clock = new TalkingClock(10000, true);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();}}
}

6.4.2 内部类的特殊语法规则

前面介绍过,内部类有一个外围类的引用outer,正式语法如下
OuterClass.this
可以像下面这样编写actionPerformed方法:

    public void actionPerformed(ActionEvent event){...if(TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep();}

可以用下列语法格式更加明确地编写内部对象的构造器:
outerObject.new InnerClass(construction parameters)
例如

    //外围类内部的方法可以这么写ActionListener listener = this.new TimePrinter();

如果TimePrinter是一个公有内部类,对于任意的语音时钟都可以构造一个TimePrinter:

    TalkingClock jabberer = new TalkingClock(10000,true);TalkingClock.TimePrinter listener = jabber.new TimePrinter();

6.4.3 内部类是否有用、必要和安全

内部类拥有访问特权,比起常规类功能更加强大。(访问数据方面),分析一下内部类

    public class chapter6.section4.innerclass.TalkingClock$TimePrinter{public chapter6.section4.innerclass.TalkingClock$TimePrinter(chapter6.section4.innerclass.TalkingClock);public void actionPerformed(java.awt.event.ActionEvent);final chapter6.section4.innerclass.TalkingClock this$0;
}

可以看到,编译器为了引用外围类,生成了一个附加的实例域this$0。另外还可以看到编译器自动合成的构造器,有一个TalkingClock参数。内部类被翻译成了名字很古怪的常规类,作为常规类,如何访问外围类的私有数据呢?再来分析一下TalkingClock类:

    class chapter6.section4.innerclass.TalkingClock{public chapter6.section4.innerclass.TalkingClock(int, boolean);public void start();static boolean access$0(chapter6.section4.innerclass.TalkingClock);private int interval;private boolean beep;
}

编译器在外围类添加了静态方法access$0。它将返回作为参数传递给它的对象域beep。也就是在TimePrinter类中的方法actionPerformed中访问beep时

    if(beep)

其实相当于

    if(access$0(outer))

6.4.4 局部内部类

上述例子中,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访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。局部类的优势在于它对外部世界完全隐藏起来了。

6.4.5 由外部方法访问final变量

与其他内部类相比,局部类还有一个优点,它们不仅能够访问包含它们的外部类,还可以访问局部变量,不过,那些局部变量必须被声明为final。

    public void start(int interval, final boolean beep){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();}

TimePrinter类在beep释放之前将beep用start方法的局部变量进行备份。但是必须要声明为final。

如果想要在一个封闭的作用域内更改某个局部变量,如下

    int counter = 0;Date[] dates = new Date[100];for(int i = 0; i < dates.length; i++)dates[i] = new Date();{public int compareTo(Date other){counter++;  //ERROR 无法访问return super.compareTo(other);}}

上述对counter的访问是无法成功的,但是如果改成了final,就没办法进行更改了,解决方法时可以使用一个长度为1的数组,并声明为final,这样的话,counter变量不能访问其他的数组引用了,数组内容可以更改。

    final   int[] counter = new int[1];Date[] dates = new Date[100];for(int i = 0; i < dates.length; i++)dates[i] = new Date();{public int compareTo(Date other){counter[0]++;  //OKreturn super.compareTo(other);}}

6.4.6 匿名内部类

将局部类的使用再更进一步。加入只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类。

    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();}

上面程序语法的含义是:创建一个实现ActionListener接口的类的新对象,需要实现的方法actionPerformed定义在括号{ }内。
new SuperTyper(construction parameters)
{
inner class methods and data
}
SuperType可以是一个接口,于是内部类就要实现这个接口。SuperType也可以是一个类,内部类需要扩展它。

匿名类没有类名,所以不能有任何构造器。
取而代之的是,将构造器参数传递给超类。

构造一个类的新对象与构造一个扩展了那个类的匿名内部类的对象的区别如下

    Person queen = new Person("Mary");  //一个Person对象Person count = new Person("Alice") {...};//一个继承了Person类的匿名内部类的对象。

提示:生成日志调试消息是,通常希望包含当前类的类名:

    System.err.println("Something awful happened in " + getClass());

不过,这对静态方法不奏效,因为静态方法不包含this,所以应该使用以下的表达式:

    new Object(){}.getClass().getEnclosingClass(); //get class of static method

6.4.7 静态内部类

有时候,使用内部类知识为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此可以将内部类声明为static。从一个程序中简单看看静态内部类的使用方法。

    class ArrayAlg{public static class Pair{...}...}

创建Pair类的目的是为了能返回两个值,利用Pair中的方法获取返回的两个值。


public class StaticInnerClassTest {public static void main(String[] args){double[] d = new double[20];for(int i = 0; i < d.length; i++)d[i] = Math.random();ArrayAlg.Pair p = ArrayAlg.minmax(d);for(double v : d)System.out.println(v);System.out.println("min value: " + p.getMin());System.out.println("max value: " + p.getMax());}}class ArrayAlg
{public static class Pair{private double min;private double max;public Pair(double min, double max){this.min = min;this.max = max;}public double getMin() {return min;}public double getMax() {return max;}}public static Pair minmax(double[] d){double min = Double.MAX_VALUE;double max = Double.MIN_VALUE;for(double v : d){if(v < min) min = v;if(v > max) max = v;}return new Pair(min,max);}
}

《Core Java》读书笔记——第6章相关推荐

  1. 《ASP.NET Core 微服务实战》译者序

    最近,我将<ASP.NET Core 微服务实战>一书由英文翻译为中文.这本书是由清华大学出版社引进的,目前还处于最后的排版校对过程中,现将该书的译者序发表于此. 以下为译者译全文: &q ...

  2. 《ASP.NET Core 微服务实战》送书结果公告

    如何构建基于.NET Core和云环境下的微服务技术体系?的送书抽奖结果已经出来了: 当前只有一位同学填写了地址.其他几位同学抓紧填写,3/9 日还没有完成填写将作废,奖品可是热门的<ASP.N ...

  3. 《ASP.NET Core 微服务实战》-- 读书笔记(第10章)

    第 10 章 应用和微服务安全 云应用意味着应用运行所在的基础设施无法掌控,因此安全不能再等到事后再考虑,也不能只是检查清单上毫无意义的复选框 由于安全与云原生应用密切相关,本章将讨论安全话题,并用示 ...

  4. 《ASP.NET Core 微服务实战》-- 读书笔记(第7章)

    第 7 章 开发 ASP.NET Core Web 应用 ASP.NET Core 基础 在本章,我们将从一个命令行应用开始,并且在不借助任何模板,脚手架和向导的情况下,最终得到一个功能完整的 Web ...

  5. 《ASP.NET Core 微服务实战》-- 读书笔记(第3章)

    第 3 章 使用 ASP.NET Core 开发微服务 微服务定义 微服务是一个支持特定业务场景的独立部署单元.它借助语义化版本管理.定义良好的 API 与其他后端服务交互.它的天然特点就是严格遵守单 ...

  6. 《ASP.NET Core 微服务实战》-- 读书笔记(第1章 、第2章)

    译者序 微服务设计方法清晰定义了各个开发团队的业务边界,微服务框架以不同方式实现了服务之间的协作与集成. .NET Core 作为全新的 .NET 技术,它不仅完全开源.跨平台,更面向云原生开发进行了 ...

  7. 《ASP.NET Core 微服务实战》-- 读书笔记(第9章)

    第 9 章 微服务系统的配置 微服务系统中的配置需要关注更多其他方面的因素,包括: 配置值的安全读写 值变更的审计能力 配置信息源本身的韧性和可靠性 少量的环境变量难以承载大型.复杂的配置信息 应用要 ...

  8. 《ASP.NET Core 微服务实战》-- 读书笔记(第12章)

    第 12 章 设计汇总 微服务开发并不是要学习 C#.Java 或者 Go 编程--而是要学习如何开发应用以适应并充分利用弹性伸缩环境的优势,它们对托管环境没有偏好,并能瞬间启停 换句话说,我们要学习 ...

  9. 《ASP.NET Core 微服务实战》-- 读书笔记(第11章)

    第 11 章 开发实时应用和服务 在本章,我们将讨论"实时"的准确含义,以及在大部分消费者看来应该属于这一范畴的应用类型 接着,我们将探讨 WebSocket,并分析为什么传统的 ...

  10. 《ASP.NET Core 微服务实战》-- 读书笔记(第6章)

    第 6 章 事件溯源与 CQRS 在本章,我们来了解一下随着云平台一同出现的设计模式 我们先探讨事件溯源和命令查询职责分离(CQRS)背后的动机与哲学 事件溯源简介 事实由事件溯源而来 我们大脑就是一 ...

最新文章

  1. Request.ServerVariables获取环境变量
  2. 前端运行python代码几种方式_前的解释|前的意思|汉典“前”字的基本解释
  3. linux终端信息读取,linux系统 如何通过终端查看信息
  4. java并发编程实践 part 01 --gt; 线程创建方式
  5. oracle恢复某个表的数据库,如何从rman的全备份中恢复一张表?
  6. 浅谈自学方法论- 不断更新-记录思路
  7. 本地下载mysql数据库_本地Windows上安装 MySQL数据库
  8. hbase小版本升级
  9. Yaksa让你抛弃Adapter和ViewHolder写RecyclerView
  10. 圈子圈套 pdf_名字叫什么? 如何圈套好域名
  11. 第10章 主成分分析(PCA)
  12. chrome浏览器 Adobe Flash 版本太旧 无法播放视频解决办法
  13. Gym - 100886D 2015-2016 Petrozavodsk Winter Training Camp, Saratov SU Contest D - Catenary
  14. 如何下载jQuery
  15. 自编超爽的对战游戏 非常有趣
  16. 【PWM】从stm32到pwm到OLED屏幕调光到晚上不要玩手机
  17. telegram怎么搜索机器人_telegram机器人
  18. 低功耗蓝牙开发技术概述(三)——主机
  19. Redis Module 模块组件
  20. 如何应对“改变现状”的失败

热门文章

  1. Ajax回调函数无反应及进erro问题记录。
  2. android recyclerView Binary XML file line #7: Error inflating class android.support.v7.widget.Recycl
  3. UA MATH564 概率论 计算至少有一个发生的概率:Waring公式
  4. canvas中如何设置渐变色
  5. java打印日期序列_Java8新特性之新日期API
  6. codevs 2977 二叉堆练习1x
  7. Windows系统下CMD命令行切换目录文件
  8. 双栈路由Linux,IPV6 IPV4双栈互通与静态路由
  9. ryzen linux 搭配显卡,R3 2200G适合搭配什么显卡?AMD锐龙3 2200G适合搭配的显卡攻略...
  10. AE导出JSON数据用CSS做前端交互---kalrry