前几天和朋友谈到Java的问题,她突然提到implements 和extend的比较问题,我觉得这个问题是一个老生常谈的问题了,但是它又特别的重要,重要到能够影响一个人编程的思维方式,所以还是值得我们讨论讨论的(本文讨论的extend是类的扩展,而不是interface的扩展;因为接口可以扩展多个接口)。
很多人用Java都因为它提供了丰富的开源库,却还用面向过程的思维方式编写Java程序。这样的程序不但重用性很差,而且会有很大的安全隐患。
谈谈接口和虚拟类
用一个形象比喻interface 和 abstract class的关系(虽然不是特别准确,但还是有助于理解的,当然也是原创的哦):我们把一个具体的类比喻成一个人,interface就是这个人的各种身份,abstract class就是这个人的父亲(当然母亲也可以啊)。
1. 一个人可以同时具有很多种身份:,爷爷,儿子,厂长,师傅,朋友;但是他只能有一个父亲。——一个类只能有一个父亲,却能有多个身份
2. 我们可以赋予一个人某种身份(当然也可以是几种),当他获得这个身份的时候,他就能获得某些特定的操作。比如我们给你赋予老板的身份,你就可以给你的员工加工资,炒别人鱿鱼(这是其他身份所不能办到的)等等。当然这些特定的操作不仅是你的权利,也是你的义务。——这就好比类如果实现一个接口,能够获得某些操作一样,而且必须实现接口中定义的所有方法。当然你也可以不通过接口,而在类的内部定义方法来实现特定操作。这样带来的坏处是:要重复的写类的APIs说明,而且程序的可读性和可理解性也就降低了。
3. 在不同的地方描述一个人的时候,应该尽量用他不同的身份,而不是他的父亲。比如在商业会谈上他是A厂的厂长、在酒会上他是某某的朋友而不要随时告诉别人他自己是谁。——这样,程序的通用性就大大提高了。
例如:在定义一个人时我们可以这样定义:
public class Man extend Father implements Manager, Friend, Teacher{
…
}
在不同声明的地方,我们可以声明使用不同声明方法:
Manager a= new Man();// 不推荐使用Man a=new Man();
Friend b=new Man();
以这样的声明方式,我们就可以使用上述的接口作为参数的函数
(如addObject(Set a) ),大大提高了代码的重用性。
4. 父亲愿意教你他所有会的东西——可以从虚拟类继承方法的实现; 但是你的身份却不会教你怎么做:比如有一天你当了老板,你就具有一些特有权利,但却没有人会教你具体怎么做,你只能依靠自己——类只能从接口继承方法的声明,而具体方法的实现却要放到类中定义。接口定义的只是一些纯粹的声明,不包括任何的实现。
谈谈implements的优点和extend的缺点吧。
讨论implements和extend,其实也就是讨论封装的问题。一个安全API封装应该满足的要求是:“它应该描述它做了什么,而不是怎么做的”。很显然,一个subclass要继承superclass这个具体类,它很显然要违反上述的规定。因为在类方法的改写时我们必须要知道改写的方法在超类中是怎样被定义的,被其他哪些方法所使用,又有哪些限制条件。造成这个问题的根源正是因为extend的功能太强大了,它不但能够继承公有的实例字段(强烈推荐不要使用公有的实例字段),而且能够继承方法的具体实现。因为extend把super类的细节统统都继承过来了,在子类重写方法时就容易出现问题(如果超类定义的方法之间依赖很强的话更容易出问题)。就好比你在模仿一个你所崇拜的大牛的时候,extend就好比不但知道人家做了什么,还模仿人家是怎么做的。但是人与人之间当然会有差异性,别人怎么做的不一定适合自己啊,所有问题就产生了(这也就是道家所说的物极必反吧)。难怪Java之父曾半开玩笑的说:“如果老天能给我重来一次的机会,我一定要‘leave out extend’”。确实,相对于implements来说,extend确实有太多的缺点了。
我们可以用接口来定义数据类型,这样可以大大增加代码的通用性(代码量也就大大减少了啊)。比如下列的定义:Set x=new HashSet(); 在以后的使用该对象的时候,我们可以忽略x具体的实现类型而使用接口所定义的方法。另外在方法定义的时候,例如:
public Boolean addObject(Set a){ // Set 是一个interface哦,不要弄混了
……
a.add(Object x);
……
}
此时,我们可以绕过a的具体实现类定义方法(实现了Set接口的类所定义的对象都可以使用该方法,比如HashSet, TreeSet, EnumSet等类实例化的对象),这样方法的可用性就大大提高了。
在类的扩展方面,我们可以使用包装类(wrapper class)来实现接口。比如:
谈谈extend的优点和implements的缺点
在这里谈extend的优点并不是我鼓励大家去使用extend(恰恰相反,我认为编写Java程序大部分的精力应该放在implements上)。但是虚拟类也有它存在的理由(注意:我们强调的虚拟类,具体类最好避免使用extend)。正如我们上面所说的,由于接口只包含方法的声明,所以在实现interface的时候必须要重新定义接口中的每一个方法,当接口中方法特别多而且这些方法的实现大都相同的情况下,interface的效率是很低的。这是虚拟类就要大派用场了。当然也有一种折中的方法:使用骨架实现(skeletal implementation)类。用接口定义数据类型,用虚拟类实现该接口的方法。例如javadoc里面的JCF定义都是如此:
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, Serializable
此AbstractSet就是Set的骨架实现。它实现了Set的一下基本操作,当类HashSet实现Set的时候,直接extendAbstractSet就可以了,这样会大大减少方法的重复定义。总之,它可以综合implements的灵活和extend的扩展方便。
使用虚拟类应该注意一个问题:由于一个类只能extend一次,所以extend也就应该特别的慎重。我们应该考虑A真的是B的子类型吗(即存在“is a”的关系)?如果真的满足“a B is A”,才能使用extend(必要非充分条件)。
另外设计Interface也应该比较慎重,因为interface的扩展(演化)很不方便(这里所说的扩展是interface内部方法的扩展)。这是因为每一个实现该interface的类都必须实现该接口声明的所有方法,如果该interface一旦改变,所有与interface有关的类则都要伤筋动骨了(实际的编程中怎么可能去改这么多的类呢?所以只能硬着头皮不改,另寻它法了)。
最后,给大家提几点关于extend和implements使用的建议(这里参照了一些大牛的书,不是我的成果拉):
1. 尽量多的使用接口,少使用虚拟类。尽管接口在方法的实现效率上相对较低,但是可以使用骨架实现的方式来弥补此不足。
2. 如果要使用extend,就需要编写专门用于继承的虚拟类,并给出详细的API说明。强烈建议不要extend具体类。
3. 用复合/转发的方式来扩展类比extend方式更加安全(这里就不强调了,自己去google一下吧,如果以后有时间也可以写写)。
相关推荐
Java 语言中,extend 和 implement 是两个基本概念,它们之间的区别是非常重要的。extend 用于继承类,而 implement 用于实现接口。在 Java 中,不支持多重继承,但是可以使用接口来实现多重继承。 继承(extend)...
2. **性能优化**:使用 `implements` 不仅仅是为了代码的清晰和可读性,它还可以带来性能上的优势。例如,在某些情况下,如果一个类需要实现的接口中有大量的方法,而这些方法的实际实现可能存在于其他类中,那么...
Java 中的继承和实现接口是两个基本概念,extends 和 implements 是两个关键字,它们之间的区别是 Java 编程语言中最重要的基础知识。 extends 的作用 在 Java 中,extends 关键字用于继承父类,创建一个子类。在...
"implements Runnable"是Java编程语言中的一个重要概念,它与多线程编程紧密相关。在Java中,线程是程序执行的最小单位,而创建线程主要有两种方式:继承Thread类和实现Runnable接口。本项目是一个Java小游戏,核心...
在JavaScript中,当我们讨论Class属性Extends和Implements的区别时,我们通常是在讨论在某个特定的JavaScript框架或者库中的类的继承方式,比如在Prototype框架或Mootools框架中。 首先,我们来详细探讨一下Extends...
在 Java 中,extends 和 implements 是两个关键字,都是用来建立类与类或类与接口之间的关系的,但它们的使用场景和实现机制却有着很大的不同。 extends extends 关键字用于继承某个类,使得子类可以使用父类的...
public class findmin extends Applet implements ActionListener JavaAppliet程序,在文本框里输入三个数,显示最小数
java学习-java中的interface和implements关键字
贯彻 implements是用于检查Object符合给定接口的实用程序模块。 例子 var impl = require ( 'implements' ) ; var instance = [ ] ; impl ( instance , [ '... 像这样将Object和接口传递给implements 。 var EventE
这种设计模式在多态和组件开发中尤其有用,因为它允许不同类之间共享相同的接口,从而实现通信和互操作性。 首先,我们来了解如何创建接口。在PHP中,接口定义使用 `interface` 关键字,然后是接口名称,接着是大...
This module implements the Requests API.
标题“*Renderer implements TableCellRenderer”表明我们正在讨论一个自定义的渲染器,该渲染器实现了`TableCellRenderer` 接口以控制表格中每个单元格的外观和行为。 `TableCellRenderer` 是 Swing 的核心组件之...
a Go library that implements E
描述中提到"资源来自pypi官网,资源全名:protocol_implements_decorator-0.3.1.tar.gz",确认了这个压缩包的来源和完整名称,说明用户可以从PyPI官方网站获取这个特定版本的软件包。 标签包括"zookeeper"、"分布式...
在Java编程语言中,`extends` 和 `implements` 关键字分别用于类的继承和接口的实现,它们是面向对象编程的重要特性。接下来我们将详细探讨这两个关键字的区别和使用场景。 1. **`extends` 关键字**: - `extends`...
ate final String[] COMMAND={"Backspace","CE","C"}; private final String[] M={" ","MC","MR","MS","M+"}; private JButton keys[]=new JButton[KEYS.length]; private JButton commands[]=new JButton[COMMAND....
`day12`这个文件名可能表示这是一个学习或教程的一部分,可能包含了第12天的学习内容,涵盖了`Comparator`和自定义排序相关的知识。在这个阶段,学习者可能已经接触了基础的Java集合框架,现在正在深入理解如何根据...