Java编程思想 学习笔记9
九、接口
接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。
1.抽象类和抽象方法
抽象类是普通的类与接口之间的一种中庸之道。创建抽象类是希望通过这个通用接口操纵一系列类。
Java提供一个叫做抽象方法的机制,这种方法是不完整的;仅有声明而没有方法体。例:
abstract void f();
包含抽象方法的类叫做抽象类。
如果从一个抽象类继承,并想创建该新类的对象,那么就必须为基类中的所有抽象方法提供方法定义。如果不这样做,那么导出类也是抽象类。
我们可能会创建一个没有任何抽象方法的抽象类。考虑这种情况:如果有一个类,让其包含任何abstract方法都显得没有实际意义,而且我们也想要阻止产生这个类的任何对象,那么这时这样做就很有用了。
2.接口
abstract关键字允许人们在类中创建一个或多个没有任何定义的方法——提供了接口部分,但是没有提供任何相应的具体实现,这些实现是由此类的继承者创建的。interface这个关键字产生一个完全抽象的类,它根本就没有提供任何具体实现。它允许创建者确定方法名、参数列表和返回类型,但是没有任何的方法体。接口只提供了形式,而未提供任何具体实现。
一个接口表示:“所以实现了该特定接口的类看起来都像这样”。接口用来建立类与类之间的协议。
但是,interface不仅仅是一个极度抽象的类,因为它允许人们通过创建一个能够被向上转型为多种基类的类型,来实现某种类似多重继变种的特性。
接口也可以包含域,但是这些域隐式地是static和final的。
要让类遵循某个特定接口,需要使用implements关键字,它表示“interface只是它的外貌,但是现在我要声明它是如何工作的。”
接口中的方法都隐式被定义为public,因此,当要实现一个接口时,在接口中被定义的方法必须被定义为是public的。
3.完全解耦
只要一个方法操作的是类而非接口,那么就只能使用这个类及其子类。如果你想要将这个方法应用于不在此继承结构中的某个类,那么你就会遇到障碍了。接口可以在很大程度上放宽这种限制,因此,它使得我们可以编写可复用性更好的代码。
创建一个能够根据所传递的参数对象的不同而具有不同行为的方法,被称为策略设计模式。这类方法包含所要执行的算法中固定不变的部分,而“策略”包含变化的部分。策略就是传递进去的参数对象,它包含要执行的代码。
适配器设计模式,适配器中的代码将接受你所拥有的接口,并产生你所需要的接口。
4.Java中的多重继承
可以继承任意多个接口,并可以向上转型为每个接口。
使用接口的核心原因:为了能够向上转型为多个基类型(以及由此带来的灵活性)。然而,使用接口的第二个原因却是与使用抽象类相同:防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口。
如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。事实上,如果知道某事物应该成为一个基类,那么第一选择应该是使它成为一个接口。
5.通过继承来扩展接口
通过继承,可以很容易地在接口中添加新的方法声明。还可以通过继承在新接口中组合数个接口。这两种情况都可以获得新的接口。
①组合接口时的名字冲突
当实现的多个接口中存在相同名字的方法,但它们的签名或返回类型不同,会产生混乱,所以尽量避免这种情况。
6.适配接口
接口最吸引人的原因之一就是允许同一个接口具有多个不同的具体实现。在简单的情况中,它的体现形式通常是一个接受接口类型的方法,而该接口的实现和向该方法传递的对象则取决于方法的使用者。
因此,接口的一种常见用法就是策略设计模式,此时你编写一个执行某些操作的方法,而该方法将接受一个同样是你指定的接口。你主要就是声明:“你可以用任何你想要的对象来调用我的方法,只要你的对象遵循我的接口。”
7.接口中的域
因为放入接口的任何域都自动是static和final的,所以接口就成为了一种很便捷的用来创建常量组的工具。这些域不是接口的一部分,它们的值被存储在该接口的静态存储区域内。
有了Java SE5,使用接口来群组常量已经没有什么意义了,因为我们有了enum关键字。
8.嵌套接口
接口可以嵌套在类中或其他接口中。
class A {
interface B {
void f();
}
public class BImp implements B {
public void f() {}
}
private class BImp2 implements B {
public void f() {}
}
public interface C {
void f();
}
class CImp implements C {
public void f() {}
}
private class CImp2 implements C {
public void f() {}
}
interface D {
void f();
}
private class DImp implements D {
public void f() {}
}
public class DImp2 implements D {
public void f() {}
}
public D getD() { return new DImp(); }
private D dRef;
public void receiveD(D d) {
dRef = d;
dRef.f();
}
}
interface E {
interface G {
void f();
}
public interface H {
void f();
}
void g();
// 不能创建一个private接口
// private interface I {}
}
public class NestingInterfaces {
public class BImp implements A.B {
public void f() {}
}
class CImp implements A.C {
public void f() {}
}
//不能实现私有的接口
//class DImp implements A.D {
// public void f() {}
// }
class EImp implements E {
public void g() {}
}
class EGImp implements E.G {
public void f() {}
}
class EImp2 implements E {
public void g() {}
class EG implements E.G {
public void f() {}
}
}
public static void main(String[] args) {
A a = new A();
//无法访问A.D
// A.D ad = a.getD();
//只返回A.D
//A.DImp di2 = a.getD();
//不能访问接口的成员
//a.getD().f();
A a2 = new A();
a2.receiveD(a.getD());
}
}
接口也可以被实现为private的,就像A.D。那么private的嵌套接口能带来什么好处呢?它即可实现为private类,也可实现为public类(A.DImp2)。但是,A.DImp2只能被其自身所使用。你无法说它实现了一个private接口D。因此,实现一个private接口只是一种方式。它可以强制该接口中的定义不要添加任何类型信息(也就是说,不允许向上转型)。
在main()中,数次尝试使用getD()都失败了。只有一种方式能成功,那就是将返回值交给有权使用它的对象。
接口E说明接口彼此之间也可以嵌套。嵌套在另一个接口中的接口自动就是public的。
特别要注意的是,当实现某个接口时,并不需要实现嵌套在其内部的任何接口。而且,private接口不能在定义它的类之外被实现。
9.接口与工厂
接口是实现多重继承的途径,而生成遵循某个接口的对象的典型方式就是工厂方法设计模式。这与直接调用不同,我们在工厂对象上调用的是创建方法,而该工厂对象将生成接口的某个实现的对象。理论上,通过这种方式,我们的代码将完全与接口的实现分离,这就使我们可以透明地将某个实现替换为另一个实现。下面展示了工厂方法的结构:
interface Service {
void method1();
void method2();
}
interface ServiceFactory {
Service getService();
}
class Implementation1 implements Service {
public void method1() {
System.out.println("Implementation1 method1");
}
public void method2() {
System.out.println("Implementation1 method2");
}
}
class Implementation1Factory implements ServiceFactory {
public Service getService() {
return new Implementation1();
}
}
class Implementation2 implements Service {
public void method1() {
System.out.println("Implementation2 method1");
}
public void method2() {
System.out.println("Implementation2 method2");
}
}
class Implementation2Factory implements ServiceFactory {
public Service getService() {
return new Implementation2();
}
}
public class Factories {
public static void serviceConsumer (ServiceFactory fact) {
Service s = new fact.getService();
s.method1();
s.method2();
}
public static void main(String[] args) {
serviceConsumer( new Implementation1Factory() );
serviceConsumer( new Implementation2Factory() );
}
/*Output
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
*/
如果不是用工厂方式,上面代码就必须在某处指定将要创建的Service的确切类型,以便调用合适的构造器。
10.总结
任何抽象性都应该是应真正的需求而产生的。恰当的原则是优先选择类而不是接口。从类开始,如果接口的必须性变得非常明确,那么就进行重构。接口是一种只要的工具,但容易被滥用。
Java编程思想 学习笔记9的更多相关文章
- [Java编程思想-学习笔记]第3章 操作符
3.1 更简单的打印语句 学习编程语言的通许遇到的第一个程序无非打印"Hello, world"了,然而在Java中要写成 System.out.println("He ...
- Java编程思想 学习笔记1
一.对象导论 1.抽象过程 Alan Kay曾经总结了第一个成功的面向对象语言.同时也是Java所基于的语言之一的Smalltalk的五个基本特性,这些特性表现了纯粹的面向对象程序设计方式 1)万物皆 ...
- [Java编程思想-学习笔记]第1章 对象导论
1.1 抽象过程 Java是一门面向对象的语言,它的一个优点在于只针对待解问题抽象,而不用为具体的计算机结构而烦心,这使得Java有完美的移植性,也即Java的口号"Write Once, ...
- Java编程思想 学习笔记11
十一.持有对象 通常,程序总是根据运行时才知道的某些条件去创建新对象.在此之前,不会知道所需对象的数量,甚至不知道确切的类型. Java实用库还提供了一套相当完整的容器类来解决这个问题,其中基本的类 ...
- Java编程思想学习笔记——类型信息
前言 运行时类型信息(RTTI:Runtime Type Information)使得我们可以在程序运行时发现和使用类型信息. Java在运行时识别对象和类的信息的方式: (1)一种是RTTI,它假定 ...
- Java编程思想 学习笔记12
十二.通过异常处理错误 Java的基本理念是“结构不佳的代码不能运行”. Java中的异常处理的目的在于通过使用少于目前数量的代码来简化大型.可靠的程序的生成,并且通过这种方式可以使你更加自信:你的 ...
- Java编程思想 学习笔记10
十.内部类 可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性.然而必须要了解,内部类和组合是 ...
- Java编程思想 学习笔记7
七.复用类 1.组合语法 在新的类中产生现有类的对象.由于新的类是由现有类的对象所组成,所以这种方法叫做组合. 类中域为基本类型时能够自动被初始化为零.对象引用被初始化为null. 编译器不是简单地为 ...
- Java编程思想 学习笔记5
五.初始化与清理 1.用构造器确保初始化 在Java中,通过提供构造器,类的设计者可确保每个对象都会得到初始化.创建对象时,如果其类具有构造器,Java就会在用户有能力操作对象之前自动调用相应的构造 ...
- Java编程思想 学习笔记4
四.控制执行流程 1.true和false 所有条件语句都利用条件表达式的真或假来决定执行路径.注意Java不允许我们将一个数字作为布尔值使用. 2.if-else 3.迭代 while.do-whi ...
随机推荐
- Mybatis复杂嵌套关联一例
Mybatis three entity relation:association in collection PatentMapper.xml <resultMap id="Bas ...
- ionic2/3注册安卓返回
如果使用了 this.app.getRootNav().push()以及this.navCtrl.push(); 则在注册安卓返回键的时候 registerBackButtonAction() ...
- 用Delphi制作动态菜单 该文章《用Delphi制作动态菜单》
---恢复内容开始--- 1.首先,确定动态菜单的数据来源,即要确定动态菜单标题是来自Windows的系统注册表,还是来自一个数据库,或者是来自一个子目录,主要由程序的功能而定.这里假设主窗口名为Ma ...
- BZOJ5101[POI2018]Powódź——并查集
题目描述 在地面上有一个水箱,它的俯视图被划分成了n行m列个方格,相邻两个方格之间有一堵厚度可以忽略不计的墙,水 箱与外界之间有一堵高度无穷大的墙,因此水不可能漏到外面.已知水箱内每个格子的高度都是[ ...
- poj 2236 Wireless Network (并查集)
链接:http://poj.org/problem?id=2236 题意: 有一个计算机网络,n台计算机全部坏了,给你两种操作: 1.O x 修复第x台计算机 2.S x,y 判断两台计算机是否联通 ...
- Codeforces Round #416 (Div. 2) B. Vladik and Complicated Book
B. Vladik and Complicated Book time limit per test 2 seconds memory limit per test 256 megabytes inp ...
- day28 反射 属性操作 getattr hasattr setattr delattr
反射 用字符串来对应其同名的属性或者方法,通过某种方法调用这个字符串来执行方法或者获取属性 网络编程的时候非常好用,是很重要的内容 先看个示例吧: class Teather: dic = { &qu ...
- 16 利用Zabbix完成windows监控
点击返回:自学Zabbix之路 16 利用Zabbix完成windows监控 1.安装zabbix_agentd 1.1.下载zabbix_agentd监控客户端软件安装包(windows操作系统客户 ...
- 【BZOJ2875】【NOI2012】随机数生成器(矩阵快速幂)
[BZOJ2875]随机数生成器(矩阵快速幂) 题面 Description 栋栋最近迷上了随机算法,而随机数是生成随机算法的基础.栋栋准备使用线性同余法(Linear Congruential Me ...
- [luogu3801]红色的幻想乡
题目描述 经过上次失败后,蕾米莉亚决定再次发动红雾异变,但为了防止被灵梦退治,她决定将红雾以奇怪的阵势释放. 我们将幻想乡看做是一个n*m的方格地区,一开始没有任何一个地区被红雾遮盖.蕾米莉亚每次站在 ...