第六章、继承


继承是复用程序代码的有力手段,当多个类之间存在相同的属性和方法时,可从这些类中抽象出父类 Base, 在父类 Base 中定义这些相同的属性和方法,所有的 Sub 类无须重新定义这些属性和方法,只需通过 extends 语句来声明继承 Base 类,Sub 类就会自动拥有在 Base 类中定义的属性和方法。

本章首先介绍继承的基本语法,然后介绍了两个重要的概念:方法重载和方法覆盖,随后介绍多态的各种特征,最后介绍正确使用继承关系的原则,以及和组合关系的区别。

6.1 继承的基本语法

在 Java 语言中,用 extends 关键字来表示一个类继承了另一个类。

public class Sub extends Base{
    ...
}

以上代码表明 Sub 类继承了 Base 类 。 那么 Sub 类到底继承了 Base 类的哪些东西呢?这需耍分为两种情况:

  • 当 Sub 类和 Base 类位于同一个包中时:Sub 类继承 Base 类中 publicprotected默认 访问级别的成员变量和成员方法。
  • 当 Sub 类和 Base 类位于不同的包中时:Sub 类继承 Base 类中 publicprotected 访问级别的成员变量和成员方法。

假设 Sub 和 Base 类位于同一个包中。以下程序演示在 Sub 类中可继承 Base 类的那些成员变量和方法:

public class Base {
    public int var1 = 1;                    //public 访问级别
    private int var2 = 1;                   //private 访问级别
    int var3 = 1;                           //默认 访问级别
    protected void methodOfBase() {}        //protected 访问级别
}

public class Sub extends Base {
    public void methodOfSub() {
        var1 = 2;                           //合法
        var2 = 2;                           //非法,不能访问Base类的private类型变量
        var3 = 2;                           //合法
        methodOfBase();                     //合法
    }

    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.var1 = 3;                       //合法
        sub.var2 = 3;                       //非法,Sub类不能继承Base类private类型变量
        sub.var3 = 3;                       //合法

        sub.methodOfBase();                 //合法
        sub.methodOfSub();                  //合法
    }
}

Java 语言不支持多继承 , 即一个类只能直接继承一个类。尽管一个类只能有一个直接的父类 ,但是它可以有多个间接的父类。所有的 Java 类都直接或间接地继承了 java.lang.Object 类, Object类是所有 Java 类的祖先,在这个类中定义了所有的 Java 对象都具有的相同行为。假如在定义一个类时,没有使用 extends 关键字, 那么这个类直接继承 Object 类。

class Sub extends Base1, Base2, Base3{...}      //编译出错
class Father extends GrandFather{...}
class Son extends Father{...}                   //合法

6.2 方法重载(Overload)

有时候,类的同一种功能有多种实现方式,到底采用哪种实现方式,取决于调用者给定的参数。

public void train(Dog dog) {/* Stand */}
public void train(Monkey monkey) {/* Ride */}

对于类的方法(包括从父类中继承的方法),如果有两个方法的方法名相同,但参数不一致,那么可以说,一个方法是另一个方法的 重载 方法。

重载方法必须满足以下条件:

  • 方法名相同 。
  • 方法的参数类型、个数、顺序至少有一项不相同。
  • 方法的返回类型可以不相同。
  • 方法的修饰符可以不相同。

例如以下 Sample 类中已经定义了一个 amethod() 方法:

public class Sample {
    public void amethod(int i, String s) {}

    public void amethod(String s, int i) {}          //可以加入Sample类中,参数顺序和已有的不一样
    private int amethod(int i, String s){return 0;}  //不可以加入Sample类中
    abstract void amethod(int i);                    //不可以加入Sample类中,虽然是一种重载方法。 但此处 Sample 类不是抽象类,要加的话需要修改Sample类为抽象类
}

作为程序入口的 main()方法也可以被重载。

abstract public class Sample{
    public static void main(String[] args) {}

    abstract public void main(String s, int i);     //可以加入Sample类中
    private void main(int i, String s){}            //可以加入Sample类中
    public void main(String s) throws Exception{}   //可以加入Sample类中
    private final static int main(String[] s) {}    //不可以,与已有的 main()方法有相同的方法签名
}

6.3 方法覆盖

假如有 100 个类,它们的一个共同行为是写字,除了 Sub1 类用左手写字外,其余的类都用右手写字。可以抽象出 一个父类 Base 类,它有一个表示写字的方法 write()。从尽可能提高代码可重用性的角度, 该方法应该采用适用于大多数子类的实现方式,这样就可以避免在大多数子类中重复定义该方法。

如果在子类中定义的一个方法,其名称、返回类型及参数签名正好与父类中某个方法的名称、返回类型及参数签名相匹配,那么可以说,子类的方法 覆盖 了父类的方法。

覆盖方法必须满足多种约束,下面分别介绍。

  • 子类方法的名称、参数签名和返回类型必须与父类方法的名称、参数签名和返回类型一致。

    public class Base {
        public void method() {...}
    }
    public class Sub extends Base {
        public int method() {...}               //编译错误,返回值类型不同
    }
    
    public class Base {
        public void method() {...}
    }
    public class Sub extends Base {         
        public void method(){...}
        public int method(int a) {...}          //合法,先重写父类方法,再重载该方法        
    }
    
  • 子类方法不能缩小父类方法的访问权限。假如没有这个限制 ,将会与 Java 语言的多态机制发生冲突。

    public class Base {
        public void method() {...}
    }
    public class Sub extends Base {
        private int method() {...}               //编译错误,子方法缩小了父类方法访问权限
    }
    
    Base base = new Sub();
    base.method();
    //Java 编译器认为以上是合法的代码。
    //但在运行时,根据动态绑定规则,Java虚拟机会调用 base 变量所引用的 Sub 实例的 method() 方法,
    //如果这个方法为 private 类型,Java 虚拟机就无法访问它。
    
  • 子类方法不能抛出比父类方法更多的异常。子类方法抛出的异常必须和父类方法抛出的异常相同, 或者子类方法抛出的异常类是父类方法抛出的异常类的子类。没有这个限制,将会与 Java 语言的多态机制发生冲突。

    public class Father {
        void method() throws ExceptionSub{}
    }
    public class Son extends Father {
        void method() throws ExceptionBase{}        //编译出错
    }
    
  • 方法覆盖只存在于子类和父类(包括直接父类和间接父类)之间 。在同一个类中方法只能被重载,不能被覆盖。

  • 父类的静态方法不能被子类覆盖为非静态方法。

    public class Base {
        public static void method() {}
    }
    public class Sub extends Base {
        public void method(){}                      //编译出错
    }
    
  • 父类的非静态方法不能被子类覆盖为静态方法。

    public class Base {
        void method() {}
    }
    public class Sub extends Base {
        static void method(){}                      //编译出错
    }
    
  • 子类可以定义与父类的静态方法同名的静态方法,以便在子类中隐藏父类的静态方法。在编译时,子类定义的静态方法也必须满足和方法覆盖类似的约束。

    public class Base {
        static int method(int a) throws BaseException {}
    }
    public class Sub extends Base {
        public static int method(int a) throws SubException {}      //合法                    
    }   
    

    子类隐藏父类的静态方法和子类覆盖父类的实例方法,这两者的区别在于:运行时,Java 虚拟机把静态方法和所属的类绑定,而把实例方法和所属的实例绑定。

    package hidestatic;
    class Base {
        void method() {}
        static void staticMethod() {}
    }
    public class  Sub extends Base {
        void method() {}
        static void staticMethod() {}
    }
    public static void main(String[] args) {
        Base sub1 = new Sub();
        sub1.method();                  //实际运行 Sub类中的 method()
        sub1.staticMethod();            //实际运行 Base类中的 staticMethod()
    
        Sub sub2 = new Sub();
        sub2.method();                  //实际运行 Sub类中的 method()
        sub2.staticMethod();            //实际运行 Sub类中的 staticMethod()
    }
    

    引用变量 sub1 和 sub2 都引用 Sub 类的实例,Java 虚拟机在执行 sub1.method()sub2.method() 时,都调用 Sub 实例的 method() 方法 ,此时父类 Base 的实例方法 method() 被子类覆盖。引用变量 sub1 被声明为 Base 类型,Java 虚拟机在执行 sub1.staticMethod() 时,调用 Base 类的 staticMethod() 方法,可见父类 Base 的静态方法 staticMehtod() 不能 被子类覆盖。引用变量 sub2 被声明为 Sub 类型,Java 虚拟机在执行 sub2.staticMethod() 时,调用 Sub 类的 staticMetbod()方法, Base 类的 staticMehtod() 方法被 Sub 类的 staticMehtod() 方法 隐藏

  • 父类的私有方法不能被子类覆盖。

    package privatetest;
    class Base {
        private String show() {
            return "Base";
        }
        public void print() {
            System.out.println(show());
        }
    }
    public class Sub extends Base {
        public String show() {         //和父类方法、参数签名、返回类型一致,但访问权限不一致
            return "Sub";              //Java虚拟机对此有不同的处理机制
        }                              //此处 Base类的show方法 与 Sub类的show方法没有覆盖关系
    
        public static void main(String[] args) {
            Sub sub = new Sub();
            sub.print(); 
        }
    }
    

    执行以上 Sub 类的 main() 方法,会打印出结果 “Base”, 这是因为 print() 方法在 Base 类中定义,因此 print()方 法会调用 Base 类中定义的 private 类型的 show() 方法。
    如果把 Base 类的 show() 方法改为 public 类型,其他代码不变:

    class Base {
        private String show() {
            return "Base";
        }
        ...
    }
    

    再执行以上 Sub 类的 main() 方法的代码,会打印出结果 “Sub”, 这是因为此时 Sub 类的 show() 方法覆盖了 Base 类的 show() 方法,因此尽管 print() 方法在 Base 类中定义,但是 Java 虚拟机会调用当前 Sub 实例的 show() 方法。

  • 父类的抽象方法可以被子类通过两种途径覆盖: 一是子类实现父类的抽象方法;二是子类重新声明父类的抽象方法

    public abstract class Base {
        abstract void method1();
        abstract void method2();
    }
    public abstract class Sub extends Base {
        public void method() {...}      //实现 method() 方法, 并且扩大访问权限
        public abstract void method2(); //重新声明 method2()方法,仅仅扩大访问权限,但不实现
    
        //不合法的代码
        private void method1() {...}    //编译出错,不能缩小访问权限
        private abstract void method2();
    }
    
  • 父类的非抽象方法可以被覆盖为抽象方法。

    public class Base {
        void method() {}
    } 
    public abstract class Sub extends Base {
        public abstract void method();              //合法
    } 
    

6.4 重写与重载的异同

方法覆盖(Override)和方法重载(Overload)具有以下 相同 点:

  • 都要求方法同名。
  • 都可以用于抽象方法和非抽象方法之间。

方法覆盖(Override)和方法重载(Overload)具有以下 不同 点:

  • 参数签名:方法覆盖要求参数签名必须一致,而方法重载要求参数签名必须不一致。
  • 返回类型:方法覆盖要求返回类型必须一致,而方法重载对此不作限制。
  • 作用范围:方法覆盖只能用于子类覆盖父类的方法,方法重载用于同一个类的所有方法(包括从父类中继承而来的方法)。
  • 异常:方法覆盖对方法的访问权限和抛出的异常有特殊的要求,而方法重载在这方面没有任何限制。
  • 操作次数:父类的一个方法只能被子类覆盖一次 ,而一个方法在所在的类中可以被重载多次。
public class Base {
    protected void method(int v) {}
    private void method(String s) {}                        //Overload
}
public abstract class Sub extends Base {
    public void method(int v) {}                            //Override
    public int method(int v1, int v2) {..}                  //Overload
    protected void method(String s) throws Exception{}      //Overload
    abstract void method();                                 //Overload
}

6.5 super关键字

superthis 关键字都可以用来覆盖 Java 语言的默认作用域,使被屏蔽的方法或变量变为可见。在以下场合会出现方法或变量被屏蔽的现象

  • 场合一:在一个方法内,当局部变量和类的成员变量同名,或者局部变量和父类的成员变量同名时,按照变量的作用域规则,只有局部变量在方法内可见 。
  • 场合二 :当子类的某个方法覆盖了父类的一个方法时,在子类的范围内,父类的方法不可见。
  • 场合三 :当子类中定义了和父类同名的成员变量时,在子类的范围内,父类的成员变量不可见。
package usesuper;
class Base {
    String var = "base";
    void method() {
        System.out.println("base");
    }
}
public class Sub extends Base{
    String var = "sub";                     //隐藏父类 var 变量
    void method() { 
        System.out.println("sub");          //覆盖父类 method() 方法
    }
    void test() {
        String var = "local variable";
        System.out.println(var);            //打印method()方法局部变量 -> local
        System.out.println(this.var);       //打印Sub实例的实例变量 -> sub
        System.out.println(super.var);      //打印Base类中定义的实例变量 -> base
        
        method();                           //调用Sub实例的 method() 方法
        this.method();                      //调用Sub实例的 method() 方法
        super.method();                     //调用在Base类中定义的 method() 方法
    }
    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.test();
    }
}

值得注意的是,如果父类中的成员变量和方法被定义为 private 类型,那么子类永远无法访问它们,如果试图采用 super.var 的形式去访问父类的 private 类型的 var 变量,会导致编译错误。

在程序中,在以下情况会使用 super 关键字:

  • 在类的构造方法中,通过 super 语句调用这个类的父类的构造方法
  • 子类中访问父类的被屏蔽的方法和属性。还有需注意的是,只能在构造方法或实例方法内使用 super 关键字 ,而在静态方法和静态代码块内不能 使用 super 关键字。

6.6 多态

多态是指当系统 A 访问系统 B 的服务时,系统 B 可以通过多种实现方式来提供服务,而这一切对系统 A 是透明的。

public class Feeder {
    public void feed(Animal animal, Food food) {
        animal.eat(food);
    }
}
Feeder feeder = new Feeder();
Animal animal = new Dog();
Food food = new Bone();
feeder.feed(annimal, food);

animal = new Cat();
food = new Fish();
feeder.feed(animal, food);

以上 animal 变量被定义为 Animal 类型,但实际上有可能引用 Dog 或 Cat 的实例。在 Feeder 类的 feed() 方法中调用 animal.eat() 方法, Java 虚拟机会执行 animal 变量所引用的实例的 eat()方法 。可见 animal 变量有多种状态,一会儿变成猫, 一会儿变成狗,这是多态的字面含义。

Java 语言允许某个类型的引用变量引用子类的实例,而且可以对这个引用变量进行类型转换:

Animal animal = new Dog();
Dog dog = (Dog) animal;                 //向下转型,把Animal类型转换为Dog类型
Creature creature = animal;             //向上转型,把Animal类型转换为Creature类型

如果把引用变量转换为子类类型,称为向下转型,如果把引用变量转换为父类类型,称为向上转型。在进行引用变量的类型转换时,会受到各种限制。而且在通过引用变量访问它所引用的实例的静态属性、静态方法、实例属性、实例方法,以及从父类中继承的方法和属性时, Java 虚拟机会采用不同的绑定机制。

下面通过具体的例子来演示多态的各种特性 。下述例程中, Base 父类和 Sub 子类中都定义了实例变量 var 、 实例方法 method()、 静态变量 staticVar 和静态方法 staticMethod()。此外, 在 Sub 类中还定义了实例变量 subVar 和实例方法 subMethod()。

package poly;
class Base {
    String var = "base var";                            //实例变量
    static String staticVar = "static base var";        //静态变量

    void method() {
        System.out.println("base method");              //实例方法
    }

    static void staticMethod() {                        //静态方法
        System.out.println("static base method");
    }
}

public class Sub extends Base {
    String var = "sub var";                             //实例变量
    static String staticVar = "static sub var";         //静态变量

    void method() {                                     //覆盖父类的 method() 方法
        System.out.println("sub method");
    }

    static void staticMethod() {                        //隐藏父类的 staticMethod() 方法
        System.out.println("static sub method");
    }

    String subVar = "var only belong to sub";

    void subMethod() {
        System.out.println("method only belong to sub");
    }

    public static void main(String[] args) {
        Base obj = new Sub();                   //obj被声明为Base类型,引用Sub类型
        System.out.println(obj.var);            //打印 Base 类的 var 变量
        System.out.println(obj.staticVar);      //打印 Base 类的 staticVar 变量
        obj.method();                           //打印 Sub 实例的 method() 方法
        obj.staticMethod();                     //打印 Base 类的staticMethod() 方法
    }
}
  1. 对于一个引用类型的变量,Java 编译器 按照它声明的类型 来处理。以下代码中编译器认为 objBase 类型的引用,不存在 subVar 成员变量和 subMethod() 方法。

    Base obj = new Sub();           //obj声明为Base类型
    obj.subVar = "123";             //编译出错,Base类中没有 subVar 属性
    obj.subMethod();                //编译出错,Base类中没有 subMethod() 方法
    

    如果要访问 Sub 类的成员,必须通过强制类型转换。

    Base obj = new Sub();
    ((Sub)obj).subVar = "123";      //合法,把 Base 引用类型强制转换为 Sub 引用类型
    ((Sub)obj).subMethod();
    

    Java 编译器允许具有直接或间接继承关系的类之间进行类型转换。对于向上转型,不必使用强制类型转换,因为子类的对象肯定也可看作父类的对象。对于向下转型,必须进行强制类型转换。

    Dog dog = new Dog();
    Creature creature = dog;
    Object object = dog;                //合法,把 Dog 引用类型直接转换为 Object 引用类型
    
    Creature creature = new Cat();
    Animal animal = (Animal) creature;  //合法,把 Creature 引用类型强制转换 Animal 引用类型
    Cat cat = (Cat)creature;
    
  2. 对于一个引用类型的变量,运行时 Java 虚拟机 按照它实际引用的对象 来处理。以下代码虽然编译可以通过,但运行时会抛出 ClassCastException 运行时异常:

    Base obj = new Base();              //obj引用Base类的实例
    Sub s = (Sub)obj;                   //运行时抛出 ClassCastException 异常
    

    在运行时,子类的对象可以转换为父类类型,而父类的对象实际上无法转换为子类类型。因为通俗地讲,父类拥有的成员子类肯定也有,而子类拥有的成员父类不一定有。

  3. 在运行时环境中,通过引用类型变量来访问所引用对象的方法和属性时,Java 虚拟机采用以下绑定规则:

    • 实例方法 与引用变量 实际引用 的对象的方法绑定,这种绑定属于动态绑定,因为是在运行时由 Java 虚拟机动态决定的。
    • 静态方法 与引用变量 所声明 的类型的方法绑定,这种绑定属于静态绑定,因为实际上在编译阶段就已经做了绑定。
    • 成员变量(包括静态变量和实例变量)与引用变量 所声明 的类型的成员变量绑定,这种绑定属于静态绑定,因为实际上在编译阶段就已经做了绑定。

    例如下段代码:

    Base obj = new Sub();                   //obj被声明为Base类型,引用Sub类型
    System.out.println(obj.var);            // 成员变量(实例变量) -> 声明类型 结果:base var
    System.out.println(obj.staticVar);      // 成员变量(静态变量) -> 声明类型 结果:baseStatic
    obj.method();                           // 实例方法 -> 实际引用 结果:sub method
    obj.staticMethod();                     // 静态方法 -> 声明类型 结果:base static method
    

    再一个例子:

    public abstract class A {
        abstract void method();
        void test() { method(); }
    }
    public class B extends A {
        void method() { System.out.println("Sub"); }
        public static void main(String[] args) {
            new B().test();                                 //结果:打印 "Sub"
        }
    }
    

    运行类 B 的 main() 方法将打印 “Sub”。方法 test() 在父类 A 中定义,它调用了方法 method()。虽然方法 method() 在类 A 中被定义为是抽象的,它仍然可以被调用,因为在运行时环境中, Java 虚拟机会执行类 B 的实例的 method() 方法。一个实例所属的类肯定实现了父类中所有的抽象方法(否则这个类不能被实例化)。
    再一个例子:

    public class A {
        void method() { System.out.println("Base"); }
        void test() { method(); }
    }
    public class B extends A {
        void method() { System.out.println("Sub"); }
        public static void main(String[] args) {
            new A().test();                                 //结果:打印 "Base"
            new B().test();                                 //结果:打印 "Sub"
        }
    }
    

    test() 方法在父类 A 中定义,它调用了 method() 方法,和上面一个例子的区别是父类 A 的 method() 方法不是抽象的。但是通过 new B().test() 调用 method() 方法,执行的仍然是子类 B 的 method() 方法 。 由此可以更深入地体会动态绑定的思想:在运行环境中,当通过 B 类的实例去调用一系列的实例方法(包括一个方法调用的另一个方法)时,将优先和 B 类本身包含的实例方法动态绑定,如果 B 类没有定义这个实例方法,才会和从父类 A 中继承来的实例方法动态绑定 。

6.7 继承的利弊与使用原则

继承是种提高程序代码的可重用性,以及提高系统的可扩展性的有效手段。

继承树的层次不可太多

如果继承树的层次过多,会导致以下弊端:

  • 对象模型的结构太复杂,难以理解,增加了设计和开发的难度。在继承树最底层的子类会继承上层所有直接父类或间接父类的方法和属性,假如子类和父类之间还有频繁的方法覆盖和属性被屏蔽的现象,那么会增加运用多态机制的难度,难以预计在运行时方法和属性到底和哪个类绑定。
  • 影响系统的可扩展性。继承树的层次越多,在继承树上增加一个新的继承分支需要创建的类越多。

继承树的上层为抽象层

当一个系统使用一棵继承树上的类时,应该尽可能地把引用变量声明为继承树的上层类型,这可以提高两个系统之间的松耦合。

位于继承树上层 的类具 有以下作用 :

  • 定义了下层子类都拥有的相同属性和方法,并且尽可能地为多数方法提供默认的实现,从而提高程序代码的可重用性。
  • 代表系统的接口,描述系统所能提供的服务。

继承关系最大的弱点:打破封装

每个类都应该封装它的属性及实现细节,这样,当这个类的实现细节发生变化时,不会对其他依赖它的类造成影响。而在继承关系中,子类能够访问父类的属性和方法,也就是说,子类会访问父类的实现细节,子类与父类之间是紧密耦合关系,当父类的实现发生变化时,子类的实现也不得不随之变化,这削弱了子类的独立性。

精心设计专门用于被继承的类

  • 对这些类必须提供良好的文档说明,使得创建该类子类的开发人员知道如何正确安全地扩展它。
  • 尽可能地封装父类的实现细节,也就是把代表实现细节的属性和方法定义为 private 类型。
  • 把不允许子类覆盖的方法定义为 final 类型。
  • 父类的构造方法不允许调用可被子类覆盖的方法,因为如果这样做,可能会导致程序运行时出现未预料的错误。

区分对象的属性和继承

对于一棵设计合理的继承树,子类之间会具有不同的属性和行为,子类继承父类的属性和行为,并且子类可以比父类拥有更多的属性和行为。

6.8 组合与继承

组合关系的分解过程对应继承关系的抽象过程

下面例子分别用组合关系与继承关系来建立一个对象模型。类 A 和类 B 有相同方法 method1()method2()method3(),此外类 A 和类 B 还分别拥有 methodA()methodB() 方法。在 method3()方法中访问 method1()方法,在 methodA()methodB() 方法中都会访问 method2() 方法。以下是类 A
和类 B 的源程序:

public class A {
    private void method1() { System.out.println("method1");} 
    private void method2() { System.out.println( "method2");} 
    public void method3() { method1(); System.out.println("method3");} 
    public void methodA() { method2(); System.out.println("methodA");} 
}
public class B {
    private void method1() { System.out.println("method1");} 
    private void method2() { System.out.println( "method2");} 
    public void method3() { method1(); System.out.println("method3");} 
    public void methodB() { method2(); System.out.println("methodB");} 
}
  1. 使用继承关系
    从类 A 和类 B 中抽象出父类 C, 它包含 method1()method2()method3() 方法。由于在类 A 和类 B 中都会访问 method2()方法,因此把 method2()方法声明为 protected 类型:
    public class C {
        private void method1() { System.out.println("method1"); }
        protected void method2() { System.out.println("method2"); }
        public void method3() { method1(); System.out.println("method3"); }
    }
    public class A extends C {
        public void methodA() { method2(); System.out.println("methodA"); }
    }
    public class B extends C {
        public void methodB() { method2(); System.out.println("methodB"); }
    }
    
  2. 使用组合关系
    在类 A 中定义了 C 类型的引用变量 C,类 A 的 method3() 方法直接调用类 C 的 method3() 方法。类 A 对类 C 进行了封装,类 A 被称为包装类,同样,类 B 也是包装类。由于在类 A 和类 B 中都会访问private 类型的 method2() 方法,因此不能把 method2() 方法放在类 C 中定义,因为如果这样做,就必须在类 C 中把本来封装起来的私有 method2() 方法定义为 public 类型,而这彻底破坏了封装。
    public class C {
        private void method1() { System.out.println("method1"); }
        public void method3() { method1(); System.out.println("method3"); }
    }
    public class A {
        private C c;
        public A(C c) { this.c = c; }
        private void method2() { System.out.println( "method2");} 
        public void method3() { c.method3(); }
        public void methodA() { method2(); System.out.println("methodA");} 
    }
    public class B {
        private C c;
        public B(C c) { this.c = c; }
        private void method2() { System.out.println( "method2");} 
        public void method3() { c.method3(); }
        public void methodB() { method2(); System.out.println("methodB");} 
    }
    

组合关系和继承关系相比,前者的最主要优势是不会破坏封装 ,当类 A 与类 C 之间为组合关系时,类 C 封装实现,仅向类 A 提供接口 。 而当类 A 与类 C 之间为继承关系时,类 C 会向类 A 暴露部分实现细节。组合关系的缺点是比继承关系要创建更多的对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值