Java继承与组合

Posted 爱学JAVA的小妍妍

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java继承与组合相关的知识,希望对你有一定的参考价值。

系列文章目录

你真的知道怎样用java敲出Hello World吗?—初识JAVA
你知道为什么会划分数据类型吗?—JAVA数据类型与变量
10 > 20 && 10 / 0 == 0等于串联小灯泡?—JAVA运算符

目录

1.继承

1.1Java中为什么有继承的概念

Java用继承将现实世界中多种多样的实体进行共性抽取,然后让实体继承共性,从而简化代码.比如,我们将猫和狗抽取出他们的共性—动物.

java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是现实世界错综复杂,事物之间可能会存在一些关联,那在设计程序是就需要考虑。

我们用下面的代码描述狗和猫.

从上述代码可以看出,狗类和猫类存在一段重复的代码,这就是他们的共性,我们对这些重复的代码抽取共性,然后定义一个新的动物类.如下图:

对描述狗和猫的代码使用继承方式重新设计:
在Java中如果要表示类之间的继承关系,需要借助extends关键字


注意:子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
以上过程便是面向对象思想中提出的继承的概念,专门用来进行共性抽取,实现代码复用.

1.2 继承的概念

继承(inheritance)机制:面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类的特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。

例如:狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到共用。

上述图示中,Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类/超类,Dog和Cat可以称为Animal的子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态。

1.3 继承的语法

在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:

修饰符 class 子类 extends 父类

注意:

  • 子类会将父类中的成员变量或者成员方法继承到子类中了
  • 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了

1.4 父类成员访问

子类将父类中的方法和变量继承下来了,子类中能直接访问父类中继承下来的成员.

1.4.1 子类中访问父类的成员变量

自我优先原则:

  • 访问的成员变量子类中有,优先访问自己的成员变量。
  • 访问的成员变量子类中无,访问父类继承下来的,如果父类也没有定义,编译报错。
  • 访问的成员变量与父类中成员变量同(不考虑数据类型),优先访问自己的

子类和父类不存在同名成员变量


子类和父类存在同名成员变量.

同名时,非要访问父类成员变量,用super,可强制访问父类成员变量.

1.4.2 子类中访问父类的成员方法

成员方法名字不同:成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时再到父类中找,如果父类中也没有则报错。

public class Base 
public void methodA()
System.out.println("Base中的methodA()");


public class Derived extends Base
public void methodB()
System.out.println("Derived中的methodB()方法");

public void methodC()
methodB(); // 访问子类自己的methodB()
methodA(); // 访问父类继承的methodA()
// methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()


成员方法名字相同:子类对象访问父类与子类同名方法时,如果父类和子类同名方法但参数列表不同(重载),根据调用方法视传递的参数选择合适的方法访问,如果没有则报错

public class Base 
public void methodA()
System.out.println("Base中的methodA()");

public void methodB()
System.out.println("Base中的methodB()");


public class Derived extends Base
public void methodA(int a) 
System.out.println("Derived中的method(int)方法");

public void methodB()
System.out.println("Derived中的methodB()方法");

public void methodC()
methodA(); // 没有传参,访问父类中的methodA()
methodA(20); // 传递int参数,访问子类中的methodA(int)
methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),父类的无法访问

由此可见,方法重载可以不在同一个类中.

//此处应有代码示范的图片(super构造方法)

1.5 super关键字

由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,直接访问是无法做到的,Java提供了super关键字,该关键字主要作用是:在子类方法中访问父类的成员.
有些书上说: super代表父类对象的引用这句话是错误的! !super其实就是一个非常简单的关键字,增加了代码的可读性!
super使用举例如下:

public class Father 
int a;
int b;
public void methodA()
System.out.println("Father中的methodA()");

public void methodB()
System.out.println("Father中的methodB()");


public class Father extends Son
int a; // 与父类中成员变量同名且类型相同
char b; // 与父类中成员变量同名但类型不同
// 与父类中methodA()构成重载
public void methodA(int a) 
System.out.println("Son中的method()方法");
 // 与父类中methodB()构成重写(即原型一致,重写后序详细介绍)
public void methodB()
System.out.println("Son中的methodB()方法");

public void methodC()
// 对于同名的成员变量,直接访问时,访问的都是子类的
a = 100; // 等价于: this.a = 100;
b = 101; // 等价于: this.b = 101;
// 注意:this是当前对象的引用
// 访问父类的成员变量时,需要借助super关键字
// super是获取到子类对象中从父类继承下来的部分
super.a = 200;
super.b = 201;
// 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
methodA(); // 没有传参,访问父类中的methodA()
methodA(20); // 传递int参数,访问子类中的methodA(int)
// 如果在子类中要访问重写的父类方法,则需要借助super关键字
methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),父类的无法访问到
super.methodB(); // 访问父类的methodB()


public class Father 
public Father()
System.out.println("Father()");

super有三种使用场景:

  • super.成员变量
  • super.方法名()
  • super()调用父类构造方方法

使用super时注意:

  • super只能在非静态方法中使用
  • super只能在子类方法中访问父类的成员变量和方法
  • super() 调用父类构造方法必须在子类中使用才能调用父类的方法和属性
  • super() 帮助子类调用父类构造方法初始化成员变量
  • super() 当父类有构造方法时,必须先帮父类完成构造方法, 子类才能完成构造方法,否则编译报错。
  • this () 和 super () 不能在同一个方法中,因为这两条语句都必须放在代码的第一行

面试问题:this和super的区别是什么?

1.6 super和this的区别

相同点:

  • super和this都可以在成员方法中用来访问:成员变量和调用其他的成员函数
  • 都是Java中的关键字
  • 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
  • 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

不同点

  • this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
  • 在非静态成员方法中:this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
  • 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现
  • 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有

1.7 静态代码块和实例代码块在有继承关系时的执行顺序

我们先用一段代码演示一下在没有继承关系时的执行顺序:

class Person 
public String name;
public int age;
public Person(String name, int age) 
this.name = name;
this.age = age;
System.out.println("构造方法执行");
 
System.out.println("实例代码块执行");
 s
tatic 
System.out.println("静态代码块执行");


public class TestDemo 
public static void main(String[] args) 
Person person1 = new Person("bit",10);
System.out.println("============================");
Person person2 = new Person("gaobo",20);



//执行结果:
//静态代码块执行
//实例代码块执行
//构造方法执行
//============================
//实例代码块执行
//构造方法执行

我们可以得出以下结论:

  • 静态代码块先执行,并且只执行一次,在类加载阶段执行
  • 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行

我们在来看在继承关系上的执行

class Person 
public String name;
public int age;
public Person(String name, int age) 
this.name = name;
this.age = age;
System.out.println("Person:构造方法执行");
 
System.out.println("Person:实例代码块执行");
 s
tatic 
System.out.println("Person:静态代码块执行");


class Student extends Person
public Student(String name,int age) 
super(name,age);
System.out.println("Student:构造方法执行");
 
System.out.println("Student:实例代码块执行");
 s
tatic 
System.out.println("Student:静态代码块执行");


public class TestDemo4 
public static void main(String[] args) 
Student student1 = new Student("张三",19);
System.out.println("===========================");
Student student2 = new Student("gaobo",20);

public static void main1(String[] args) 
Person person1 = new Person("bit",10);
System.out.println("============================");
Person person2 = new Person("gaobo",20);



//
执行结果:
Person:静态代码块执行
Student:静态代码块执行
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
===========================
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行

得出以下结论:

  • 父类静态代码块优先于子类静态代码块执行,且是最早执行
  • 父类实例代码块和父类构造方法紧接着执行
  • 子类的实例代码块和子类构造方法紧接着再执行
  • 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行

1.8 protect

为了实现封装特性,Java中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其他包中被访问。在程序设计时,类要尽量做到 “封装”,即隐藏内部实现细节, 只暴露出必要的信息给类的调用者.因此我们在使用的时候应该尽可能的使用比较严格的访问权限. 例如如果一个方法能用private, 就尽量不要用 public. 还有一种 简单粗暴 的做法: 将所有的字段设为private, 将所有的方法设为public. 不过这种方式属于是对访问权限的滥用,不建议这样使用.

默认就是不加任何访问修饰限定符,也可以叫做default.

这里我们重点讲protected在不同位置中的使用.
同一个类:


同一个包中的类:

  • 同一个包中的子类
  • 同一个包中的非子类

不同包的子类:

注意:不同包中的子类要通过super访问

不同包且不是子类:

那我们什么时候用private什么时候用public呢?
在考虑用哪一个限定符时,我们应该考虑该类提供的字段方法到底给 “谁” 使用(是类内部自己用, 还是类的调用者使用, 还是子类使用).

1.9 继承方式

在现实生活中事物的继承关系可能非常复杂且灵活,那么我们就需要对不同的继承关系分类.
我们举例写的类是现实事物的抽象. 而我们真正开发所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以真实项目中所写的类也会有很多. 类之间的关系也会更加复杂.即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.
如果想从语法上进行限制继承, 就可以使用 final 关键字

JAVA中的继承方式有以下几种:

注意:Java中不支持下面这种多继承方式,C++中支持.

1.10 final 关键字

上面我们提到可以用final关键字限制继承,下面我们来详细了解final.

  • 修饰变量或字段,表示常量(即不能修改)
final int a = 10;
a = 20; // 编译出错

定义的同时可以不初始化,但是只能初始化一次.

final int a;
a = 100;
System.out.println(a);
  • 修饰类:表示此类不能被继承
final public class Animal 
...

public class Bird extends Animal 
...
 /
/ 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继

我们查看String字符串类可以看到,String字符串类是用final修饰的,不能被继承.

  • 修饰方法,表示该方法不能被重写

2. 组合

2.1 组合的概念

Java是一个面向对象的语言,每一个学习过Java的人都知道,封装、继承、多态是面向对象的三个特征。但组合并不是一种特性,它只是一种实现手段.组合也是一种表达类之间关系的方式, 也是能够达到代码复用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。
组合(Composition)体现的是整体与部分、拥有的关系,即has-a的关系.我们可以用xx是xx的一部分这样的语句描述组合中体现的关系.

继承(Inheritance)是一种联结类与类的层次模型,指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系,是一种is-a关系。

例如,我们用组合写一个汽车的代码表现这种关系,如下:

// 轮胎类
class Tire
// ...
 /
/ 发动机类
class Engine
// ...
 /
/ 车载系统类
class VehicleSystem
// ...

class Car
private Tire tire; // 可以复用轮胎中的属性和方法
private Engine engine; // 可以复用发动机中的属性和方法
private VehicleSystem vs; // 可以复用车载系统中的属性和方法
// ...
 /
/ 奔驰是汽车
class Benz extend Car
// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来

2.2 组合和继承的区别

下面我们从以下几个方面区别组合和继承:

  • 表示关系上:
    继承表示对象之间是is-a的关系(可以用xx是xx描述),比如:狗是动物,猫是动物

组合表示对象之间是has-a的关系(可以用xx是xx的一部分描述),比如:零件是汽车的一部分

  • 代码复用上:
    继承:父类的内部细节对于子类是可见的,所以我们通常也可以说通过继承的代码复用是一种白盒式代码复用。(如果基类的实现发生改变,那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性;)

组合:通过对现有的对象进行拼装(组合)产生新的、更复杂的功能。因为在对象之间,各自的内部细节是不可见的,所以我们也说这种方式的代码复用是黑盒式代码复用。(因为组合中一般都定义一个类型,所以在编译期根本不知道具体会调用哪个实现类的方法)

  • 类的关系上:
    继承:在写代码的时候就要指名具体继承哪个类,所以,在编译期就确定了关系。(从基类继承来的实现是无法在运行期动态改变的,因此降低了应用的灵活性。)

组合:在写代码的时候可以采用面向接口编程。所以,类的组合关系一般在运行期确定。

建议在同样可行的情况下,优先使用组合而不是继承。因为组合更安全,更简单,更灵活,更高效,长期大量的使用继承会给代码带来很高的维护成本。


重新认识java — 组合聚合与继承的爱恨情仇

有人学了继承,认为他是面向对象特点之一,就在所有能用到继承的地方使用继承,而不考虑究竟该不该使用,无疑,这是错误的。那么,究竟该如何使用继承呢?

java中类与类之间的关系

大部分的初学者只知道java中两个类之间可以是继承与被继承的关系,可是事实上,类之间的关系大体上存在五种—继承(实现)、依赖、关联、聚合、组合。

接下来,简单的分析一下这些关系。

继承(实现)

对于类来说,这种关系叫做继承,对于接口来说,这种关系叫做实现。继承上一篇文章已经详细的讲解过了,至于实现,我想大家也都知道是怎么回事,由于后面要专门讲接口,所以这里就先不说了。继承是一种“is-a”关系。

依赖

依赖简单的理解,就是一个类A中的方法使用到了另一个类B。

这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A。

比如说,我用笔写字,首先需要一个类来代表我自己,然后需要一个类来代表一支笔,最后,‘我’要调用‘笔’里的方法来写字,用代码实现一下:

public class Pen 
    public void write()
        System.out.println("use pen to write");
    


public class Me 
    public void write(Pen pen)//这里,pen作为Me类方法的参数
        pen.write();
    

看到这大家都懂了,因为这种代码你每天都会写。现在你知道了,这就是一种类与类之间的关系,叫做依赖。

这种关系是一种很弱的关系,但是pen类的改变,有可能会影响到Me类的结果,比如我把pen类write方法的方法体改了,me中再调用就会得到不同的结果。

一般而言,依赖关系在Java中体现为局域变量、方法的形参,或者对静态方法的调用。

关联

关联体现的是两个类、或者类与接口之间语义级别的一种强依赖关系。

这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的、关联可以是单向、双向的。

看下面这段代码:

// pen 还是上面的pen
public class You 
    private Pen pen; // 让pen成为you的类属性 

    public You(Pen p)
        this.pen = p;
    

    public void write()
        pen.write();
    

被关联类B以类属性的形式出现在关联类A中,或者关联类A引用了一个类型为被关联类B的全局变量的这种关系,就叫关联关系。

在Java中,关联关系一般使用成员变量来实现。

聚合

聚合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即has-a的关系

看下面一段代码:

public class Family 
    private List<Child> children; //一个家庭里有许多孩子

    // ...

在代码层面,聚合和关联关系是一致的,只能从语义级别来区分。普通的关联关系中,a类和b类没有必然的联系,而聚合中,需要b类是a类的一部分,是一种”has-a“的关系,即 a has-a b; 比如家庭有孩子,屋子里有空调。

但是,has 不是 must has,a可以有b,也可以没有。a是整体,b是部分,整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。

不同于关联关系的平等地位,聚合关系中两个类的地位是不平等。

组合

组合也是关联关系的一种特例,他体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合。

先看一段代码:

public class Nose 
    private Eye eye = new Eye();  //一个人有鼻子有眼睛
    private Nose nose = new Nose();

    // .... 

组合同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束。

就像你有鼻子有眼睛,如果你一不小心结束了生命周期,鼻子和眼睛的生命周期也会结束,而且,鼻子和眼睛不能脱离你单独存在。

只看代码,你是无法区分关联,聚合和组合的,具体是哪一种关系,只能从语义级别来区分。

同样,组合关系中,两个类额关系也是不平等的。

组合,聚合和继承

依赖关系是每一个java程序都离不开的,所以就不单独讨论了,普通的关联关系也没有什么特殊的地方,下面我们重点研究一下组合,聚合和继承。

聚合与组合

  1. 聚合与组合都是一种关联关系,只是额外具有整体-部分的意义。

  2. 部件的生命周期不同

    • 聚合关系中,整件不会拥有部件的生命周期,所以整件删除时,部件不会被删除。再者,多个整件可以共享同一个部件。

    • 组合关系中,整件拥有部件的生命周期,所以整件删除时,部件一定会跟着删除。而且,多个整件不可以同时间共享同一个部件。

    这个区别可以用来区分某个关联关系到底是组合还是聚合。两个类生命周期不同步,则是聚合关系,生命周期同步就是组合关系。

  3. 聚合关系是【has-a】关系,组合关系是【contains-a】关系。

    平时我们只讨论组合和继承的时候,认为组合是【has-a 】关系,而事实上,聚合才是真正的【has-a】关系,组合是更深层次的【contains-a】关系。

    由于【contains-a】关系是一种更深的【has-a】关系,所以说组合是【has-a】关系也是正确的。

组合和继承

这个才是本文的重点。

学过设计模式的都知道,要“少用继承,多用组合”,这究竟是为什么呢?

我们先来看一下组合和继承各自的优缺点:

组合和继承的优缺点

组合

优点:

- 不破坏封装,整体类与局部类之间松耦合,彼此相对独立
- 具有较好的可扩展性
- 支持动态组合。在运行时,整体对象可以选择不同类型的局部对象
- 整体类可以对局部类进行包装,封装局部类的接口,提供新的接口

缺点:

- 整体类不能自动获得和局部类同样的接口
- 创建整体类的对象时,需要创建所有局部类的对象

缺点分析:

1、整体类不能自动获得和局部类同样的接口

如果父类的方法子类中几乎都要暴露出去,这时可能会觉得使用组合很不方便,使用继承似乎更简单方便。但从另一个角度讲,实际上也许子类中并不需要暴露这些方法,客户端组合应用就可以了。所以上边推荐不要继承那些不是为了继承而设计的类,一般为了继承而设计的类都是抽象类。

2、创建整体类的对象时,需要创建所有局部类的对象

这个可能没什么更好的办法,但在实际应用中并没有多出多少代码。

继承

优点:

- 子类能自动继承父类的接口
- 创建子类的对象时,无须创建父类的对象

缺点:

- 破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
- 支持扩展,但是往往以增加系统结构的复杂度为代价
- 不支持动态继承。在运行时,子类无法选择不同的父类
- 子类不能改变父类的接口

缺点分析:

1、为什么继承破坏封装性?

鸭子中不想要“飞”的方法,但因为继承无法封装这个无用的“飞”方法 。

2、为什么继承紧耦合:

当作为父类的BaseTable中感觉Insert这个名字不合适时,如果希望将其修改成Create方法,那使用了子类对象Insert方法将会编译出错,可能你会觉得这改起来还算容易,因为有重构工具一下子就好了并且编译错误改起来很容易。但如果BaseTable和子类在不同的程序集中,维护的人员不同,BaseTable程序集升级,那本来能用的代码忽然不能用了,这还是很难让人接受的

3、为什么继承扩展起来比较复杂

当图书和数码的算税方式和数码产品一样时,而消费类产品的算税方式是另一样时,如果采用继承方案可能会演变成如下方式:

这样如果产品继续增加,算税方式继续增加,那继承的层次会非常复杂,而且很难控制,而使用组合就能很好的解决这个问题

4、继承不能支持动态继承

这个其实很好理解,因为继承是编译期就决定下来的,无法在运行时改变,如3例中,如果用户需要根据当地的情况选择计税方式,使用继承就解决不了,而使用组合结合反射就能很好的解决。

5、为什么继承,子类不能改变父类接口

如2中的图,子类中觉得Insert方法不合适,希望使用Create方法,因为继承的原因无法改变

组合与继承的区别和联系

  • 在继承结构中,父类的内部细节对于子类是可见的。所以我们通常也可以说通过继承的代码复用是一种 白盒式代码复用。(如果基类的实现发生改变,那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性)

  • 组合是通过对现有的对象进行拼装(组合)产生新的、更复杂的功能。因为在对象之间,各自的内部细节是不可见的,所以我们也说这种方式的代码复用是黑盒式代码复用 。(因为组合中一般都定义一个类型,所以在编译期根本不知道具体会调用哪个实现类的方法)

  • 继承在写代码的时候就要指名具体继承哪个类,所以,在编译期就确定了关系。(从基类继承来的实现是无法在运行期动态改变的,因此降低了应用的灵活性。)

  • 组合,在写代码的时候可以采用面向接口编程。所以,类的组合关系一般在运行期确定。

  • 组合(has-a)关系可以显式地获得被包含类(继承中称为父类)的对象,而继承(is-a)则是隐式地获得父类的对象,被包含类和父类对应,而组合外部类和子类对应。

  • 组合是在组合类和被包含类之间的一种松耦合关系,而继承则是父类和子类之间的一种紧耦合关系。

  • 当选择使用组合关系时,在组合类中包含了外部类的对象,组合类可以调用外部类必须的方法,而使用继承关系时,父类的所有方法和变量都被子类无条件继承,子类不能选择。

  • 最重要的一点,使用继承关系时,可以实现类型的回溯,即用父类变量引用子类对象,这样便可以实现多态,而组合没有这个特性。

  • 还有一点需要注意,如果你确定复用另外一个类的方法永远不需要改变时,应该使用组合,因为组合只是简单地复用被包含类的接口,而继承除了复用父类的接口外,它甚至还可以覆盖这些接口,修改父类接口的默认实现,这个特性是组合所不具有的。

  • 从逻辑上看,组合最主要地体现的是一种整体和部分的思想,例如在电脑类是由内存类,CPU类,硬盘类等等组成的,而继承则体现的是一种可以回溯的父子关系,子类也是父类的一个对象。

  • 这两者的区别主要体现在类的抽象阶段,在分析类之间的关系时就应该确定是采用组合还是采用继承。

  • 引用网友的一句很经典的话应该更能让大家分清继承和组合的区别:组合可以被说成“我请了个老头在我家里干活” ,继承则是“我父亲在家里帮我干活”。

继承还是组合?

首先它们都是实现系统功能重用,代码复用的最常用的有效的设计技巧,都是在设计模式中的基础结构。

很多人都知道面向对象中有一个比较重要的原则『多用组合、少用继承』或者说『组合优于继承』。从前面的介绍已经优缺点对比中也可以看出,组合确实比继承更加灵活,也更有助于代码维护。

所以,建议在同样可行的情况下,优先使用组合而不是继承。因为组合更安全,更简单,更灵活,更高效。

注意,并不是说继承就一点用都没有了,前面说的是【在同样可行的情况下】。有一些场景还是需要使用继承的,或者是更适合使用继承。

继承要慎用,其使用场合仅限于你确信使用该技术有效的情况。一个判断方法是,问一问自己是否需要从新类向基类进行向上转型。如果是必须的,则继承是必要的。反之则应该好好考虑是否需要继承。

只有当子类真正是超类的子类型时,才适合用继承。换句话说,对于两个类A和B,只有当两者之间确实存在 is-a 关系的时候,类B才应该继承类A。

向上转型将会在下一篇《重新认识Java(五) — 面向对象之多态》中详细讲解。

总结

根据我们前面讲的内容我们可以发现继承的缺点远远多于优点,尽管继承在学习OOP的过程中得到了大量的强调,但并不意味着应该尽可能地到处使用它。相反,使用它时要特别慎重。

只有在清楚知道继承在所有方法中最有效的前提下,才可考虑它。 继承最大的优点就是扩展简单,但大多数缺点都很致命,但是因为这个扩展简单的优点太明显了,很多人并不深入思考,所以造成了太多问题。

最后,总结一下:

1、精心设计专门用于被继承的类,继承树的抽象层应该比较稳定,一般不要多于三层。
2、对于不是专门用于被继承的类,禁止其被继承。
3、优先考虑用组合关系来提高代码的可重用性。
4、子类是一种特殊的类型,而不只是父类的一个角色
5、子类扩展,而不是覆盖或者使父类的功能失效

没错,写了这么多,就是想说: 请慎重使用继承,除非你确定非用继承不可!


这篇文章写得比较粗糙,因为写文章的时候一直在拉肚子。。。以后还会做一些修改,暂时先这样。如果文中有错误或者有更好的解释,欢迎给我留言。我也只是一个学习的人,而不是一个Java大神,所以不保证文章内容的正确性~

参考文章:
http://www.cnblogs.com/nuaalfm/archive/2010/04/23/1718453.html
http://xifangyuhui.iteye.com/blog/819498
http://www.tuicool.com/articles/u2uUZjb
http://www.cnblogs.com/jiqing9006/p/5915023.html

本文地址:http://blog.csdn.net/qq_31655965/article/details/54645220,转子请注明出处。
看完点个赞呗。

以上是关于Java继承与组合的主要内容,如果未能解决你的问题,请参考以下文章

Java中的继承与组合(转载)

Java继承与组合

Java的继承与组合

JAVA学习笔记--组合与继承

重新精读《Java 编程思想》系列之组合与继承

java中的继承(IS-A)与组合(HAS-A)关系[重复]