接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法
抽象类和抽象方法
抽象方法 abstract void f();
包含抽象方法的类叫做抽象类 如果一个类包含一个或多个抽象方法 该类必须被限定为抽象的(否则 编译器就会报错)
如果从一个抽象类继承 并想创建该新类的对象 那么就必须为基类中的所有抽象方法提供方法定义 如果不这样做(可以选择不做) 那么导出类便也是抽象类 且编译器将会强制我们用abstract关键字来限定这个类
我们也可能会创建一个没有任何抽象方法的抽象类 考虑这种情况:如果有一个类 让其包含任何abstract方法都显得没有实际意义 而且我们也想要阻止产生这个类的任何对象 那么这时这样做就很有用了
接口
interface这个关键字产生一个完全抽象的类 它根本就没有提供任何具体实现 它允许创建者确定方法名 参数列表和返回类型 但是没有任何方法体 接口只提供了形式 而未提供任何具体实现
要想创建一个接口 需要用interface关键字来替代class关键字 就像类一样 可以在interface关键字前面添加public关键字(但仅限于该接口在与其同名的文件中被定义) 如果不添加public关键字 则它只具有包访问权限 这样它就只能在同一个包内可用 接口也可以包含域 但是这些域隐式地是static和final的
要让一个类遵循某个特定接口(或者是一组接口) 需要使用implements关键字 它表示:interface只是它的外貌 但是现在我要声明它是如果工作的 除此之外 它看起来还很像继承
可以选择在接口中显式地将方法声明为public的 但即使你不这么做 它们也是public的 因此 当要实现一个接口时 在接口中被定义的方法必须被定义为是public的
完全解耦
只要一个方法操作的是类而非接口 那么你就只能使用这个类及其子类 如果你想要将这个方法应用于不在此继承结构中的某个类 那么你就会触霉头了 接口可以在很大程度上放宽这种限制 因此 它使得我们可以编写可复用性更好的代码
例如 假设有一个Processor类 它有一个name()方法 另外还有一个process()方法 该方法接受输入参数 修改它的值 然后产生输出 这个类做为基类而被扩展 用来创建各种不同类型的Processor 在本例中 Processor的子类将修改String对象(注意 返回类型可以是协变类型 而非参数类型)
Apply.process()方法可以接受任何类型的Processor 并将其应用到一个Object对象上 然后打印结果 像本例这样 创建一个能够根据所传递的参数对象的不同而具有不同行为的方法 被称为策略设计模式
现在假设我们发现了一组电子滤波器 它们看起来好像适用于Apply.process()方法
Filter与Processor具有相同的接口元素 但是因为它并非继承自Processor——因为Filter类的创建者压根不清楚你想要将它用作Processor——因此你不能将Filter用于Apply.process()方法 即便这样做可以正常运行 这里主要是因为Apply.process()方法和Processor之间的耦合过紧 已经超出了所需要的程度 这就使得应该复用Apply.process()的代码时 复用却被禁止了 另外还需要注意的是它们的输入和输出都是Waveform
但是 如果Processor是一个接口 那么这些限制就会变得松动 使得你可以复用结构该接口的Apply.process() 下面是Processor和Apply的修改版本
复用代码的第一种方式是客户端程序员遵循该接口来编写他们自己的类 就像下面这样
但是 你经常碰到的情况是你无法修改你想要使用的类 例如 在电子滤波器的例子中 类库是被发现而非被创建的 在这些情况下 可以使用适配器设计模式 适配器中的代码将接受你所拥有的接口 并产生你所需要的接口 就像下面这样
在这种使用适配器的方式中 FilterAdapter的构造器接受你所拥有的接口Filter 然后生成具有你所需要的Processor接口的对象 在FilterAdapter类中用到了代理
将接口从具体实现中解耦使得接口可以应用于多种不同的具体实现 因此代码也就更具可复用性
Java中的多重继承
组合多个类的接口的行为被称作多重继承
在导出类中 不强制要求必须有一个是抽象的或 具体的(没有任何抽象方法的)基类 如果要从一个非接口的类继承 那么只能从一个类去继承 其余的基元素都必须是接口 需要将所有的接口名都置于implements关键字之后 用逗号将它们一一隔开 可以继承任意多个接口 并可以向上转型为每个接口 因为每一个接口都是一个独立类型
当通过这种方式将一个具体类和多个接口组合到一起时 这个具体类必须放在前面 后面跟着的才是接口(否则编译器会报错)
使用接口的核心原因:为了能够向上转型为多个基类型(以及由此而带来的灵活性)和防止客户端程序员创建该类的对象 并确保这仅仅是建立一个接口
通过继承来扩展接口
通过继承 可以很容易地在接口中添加新的方法声明 还可以通过继承在新接口中组合数个接口 这两种情况都可以获得新的接口
在Vampire中使用的语法仅适用于接口继承 一般情况下 只可以将extends用于单一类 但是可以引用多个基类接口 就像所看到的 只需用逗号将接口名一一分隔开即可
组合接口时的名字冲突
在实现多重继承时 可能会碰到一个小陷阱
在打算组合的不同接口中使用相同的方法名通常会造成代码可读性的混乱 请尽量避免这种情况
适配接口
接口最吸引人的原因之一就是允许同一个接口具有多个不同的具体实现 在简单的情况中 它的体现形式通常是一个接受接口类型的方法 而该接口的实现和向该方法传递的对象则取决于方法的使用者
因此 接口的一种常见用法就是前面提到的策略设计模式 此时你编写一个执行某些操作的方法 而该方法将接受一个同样是你指定的接口 你主要就是要声明 你可以用任何你想要的对象来调用我的方法 只要你的对象遵循我的接口 这使得你的方法更加灵活 通用 并更具可复用性
假设你有一个还未实现Readable的类 怎样才能让Scanner作用于它呢 下面这个类就是一个例子 它可以产生随机浮点数
我们再次使用了适配器模式 但是在本例中 被适配的类可以通过继承和实现Readable接口来创建 因此 通过使用interface关键字提供的伪多重继承机制 我们可以生成既是RandomDoubles又是Readable的新类
因为在这种方式中 我们可以在任何现有类之上添加新的接口 所以这意味着让方法接受接口类型 是一种让任何类都可以对该方法进行适配的方式 这就是使用接口而不是类的强大之处
接口中的域
因为你放入接口中的任何域都自动是static和final的 所以接口就成为了一种很便捷的用来创建常量组的工具
有了Java SE5 你就可以使用更加强大而灵活的enum关键字 因此 使用接口来群组常量已经显得没什么意义了
初始化接口中的域
在接口中定义的域不能是 空final 但是可以被非常量表达式初始化
既然域是static的 它们就可以在类第一次被加载时初始化 这发生在任何域首次被访问时
这些域不是接口的一部分 它们的值被存储在该接口的静态存储区域内
接口与工厂
接口是实现多重继承的途径 而生成遵循某个接口的对象的典型方式就是工厂方法设计模式 这与直接调用构造器不同 我们在工厂对象上调用的是创建方法 而该工厂对象将生成接口的某个实现的对象 理论上 通过这种方式 我们的代码将完全与接口的实现分离 这就使得我们可以透明地将某个实现替换为另一个实现
如果不是用工厂方法 你的代码就必须在某处指定将要创建的Service的确切类型 以便调用合适的构造器
为什么我们想要添加这种额外级别的间接性呢 一个常见的原因是想要创建框架 假设你正在创建一个对弈游戏系统
如果Games类表示一段复杂的代码 那么这种方式就允许你在不同类型的游戏中复用这段代码 你可以再想象一些能够从这个模式中受益的更加精巧的游戏