详细分析如何在java代码中使用继承和组合

继承与组合

在这里插入图片描述
继承和组合是程序员用在类和对象之间建立关系的两种编程技术。继承是从另一类继承一个类,而组合将一个类定义为其部分的总和。

通过继承创建的类和对象是强耦合,因为在继承关系中更改父类或超类可能会破坏代码。通过组合创建的类和对象是松耦合,这意味着可以更轻松地更改组件,而无需破坏代码。

因此组合提供了更大的灵活性,所以许多程序员已经知道,组合是比继承更好的选择。

继承与组合之间的区别,以及如何选择使用继承和组合。请继续下面干货。

何时在Java中使用继承

在面向对象的编程中,当我们知道孩子与其父母之间存在“是”关系时,就可以使用继承。一些示例是:

  • 猫是动物。
  • 汽车是车辆。
    在这种情况下,子类或子类都是父类或超类的专门版本。从超类继承是代码重用的一个示例。为了更好地理解这种关系,请看下面例子:
class Vehicle {

    String brand;
    String color;
    double weight;
    double speed;

    void move() {
        System.out.println("The vehicle is moving");
    }

}

public class Car extends Vehicle {

    String licensePlateNumber;
    String owner;
    String bodyStyle;

    public static void main(String... inheritanceExample) {
        System.out.println(new Vehicle().brand);
        System.out.println(new Car().brand);
        new Car().move();
    }

}

在考虑使用继承时,子类是否确实是超类的更专门的版本。在这种情况下,汽车是车辆的一种,因此继承关系很有意义。

何时在Java中使用组合

在面向对象的编程中,我们可以在一个对象“具有”另一个对象(或属于另一个对象)的情况下使用组合。一些示例是:

  • 汽车有电池(电池是汽车的一部分)。
  • 一个人有心脏(心脏是一个人的一部分)。
  • 房子有一个客厅(客厅是房子的一部分)。
    为了更好地理解这种类型的关系,请看下面例子:
public class CompositionExample {

    public static void main(String... houseComposition) {
        new House(new Bedroom(), new LivingRoom());
    }

    static class House {

        Bedroom bedroom;
        LivingRoom livingRoom;

        House(Bedroom bedroom, LivingRoom livingRoom) {
            this.bedroom = bedroom;
            this.livingRoom = livingRoom;
        }

    }

    static class Bedroom { }

    static class LivingRoom { }

}

在这种情况下,我们知道一所房子有一个客厅和一间卧室,因此我们可以使用Bedroom和 LivingRoom对象构成一个House。

继承与组成:两个例子

看下面的代码。这是继承的好例子吗?


import java.util.HashSet;

public class CharacterBadExampleInheritance extends HashSet<Object> {

    public static void main(String... badExampleOfInheritance) {
        BadExampleInheritance badExampleInheritance = new BadExampleInheritance();
        badExampleInheritance.add("Homer");
        badExampleInheritance.forEach(System.out::println);

    }

在这种情况下,子类继承了许多它永远不会使用的方法,导致紧耦合的代码既混乱又难以维护。

现在,让我们使用组合代码重写上面例子:


import java.util.HashSet;
import java.util.Set;

public class CharacterCompositionExample {

    static Set<String> set = new HashSet<>();

    public static void main(String... goodExampleOfComposition) {
        set.add("Homer");
        set.forEach(System.out::println);
    }

在这种情况下使用组合允许 CharacterCompositionExample类仅使用的两个HashSet方法,而无需继承所有方法。这样可以简化代码,减少耦合,从而更易于理解和维护。

用Java继承重写方法

继承使我们可以在新类中重用一个类的方法和其他属性,这非常方便。但是要使继承真正起作用,我们还需要能够在新的子类中更改某些继承的行为。请看具体例子:

class Animal {

    void emitSound() {
        System.out.println("The animal emitted a sound");
    }

}

class Cat extends Animal {

    @Override
    void emitSound() {
        System.out.println("Meow");
    }
}

class Dog extends Animal {
}

public class Main {

    public static void main(String... doYourBest) {
        Animal cat = new Cat(); // Meow
        Animal dog = new Dog(); // The animal emitted a sound
        Animal animal = new Animal(); // The animal emitted a sound

        cat.emitSound();
        dog.emitSound();
        animal.emitSound();
    }

}

以上例子是方法覆盖的Java继承。首先,我们扩展Animal类创建一个新的Cat类。接下来,我们覆盖Animal类的emitSound()方法来获取Cat特定声音。即使我们将类类型声明为Animal,当我们实例化Cat时也会得到猫的叫声。 方法重载是多态。

Java不具有多重继承

与某些语言(例如C ++)不同,Java不允许对类进行多重继承。但是,可以使用逐一继承或实现多个接口。
下面例子使用java多重继承,则代码将无法编译:


class Animal {}
class Mammal {}
class Dog extends Animal, Mammal {}

1、使用类逐一继承:

class Animal {}
class Mammal extends Animal {}
class Dog extends Mammal {}

2、使用接口


interface Animal {}
interface Mammal {}
class Dog implements Animal, Mammal {}

使用super访问父类方法

当两个类通过继承相关联时,子类必须能够访问其父类的每个可访问字段,方法或构造函数。在Java中,我们使用保留字super来确保子类仍然可以访问其父类的重写方法:

public class SuperWordExample {

    class Character {

        Character() {
            System.out.println("A Character has been created");
        }

        void move() {
            System.out.println("Character walking...");
        }

    }

    class Moe extends Character {

        Moe() {
            super();
        }

        void giveBeer() {
            super.move();
            System.out.println("Give beer");
        }
    }

}

在此示例中,Character是Moe的父类。在子类中使用super,我们可以访问Character的 move()方法。

构造函数与继承一起使用

当一个类继承自另一个类时,在加载其子类之前,始终会先加载超类的构造函数。在大多数情况下,保留字super将自动添加到构造函数中。但是,如果超类在其构造函数中有一个参数,我们将手动调用该super构造函数,如下所示:

public class ConstructorSuper {

    class Character {

        Character() {
            System.out.println("The super constructor was invoked");
        }

    }

    class Barney extends Character {

        // No need to declare the constructor or to invoke the super constructor
        // The JVM will to that

    }

}

如果父类的构造函数带有至少一个参数,则必须在子类中声明该构造函数,并使用super它显式调用父构造函数。super保留字不会被自动添加,没有它的代码将无法编译。例如:

public class CustomizedConstructorSuper {

    class Character {

        Character(String name) {
            System.out.println(name + "was invoked");
        }

    }

    class Barney extends Character {

        // We will have compilation error if we don't invoke the constructor explicitly
        // We need to add it
        Barney() {
            super("Barney Gumble");
        }

    }

}

类型转换和ClassCastException

强制转换是一种向编译器明确传达确实要转换给定类型的方式。 如果您强制转换的类与声明的类类型不兼容,就会产生ClassCastException。

在继承中,我们可以在不强制转换的情况下将子类转换成父类,但是在不使用强制转换的情况下不能将父类转换成子类。

public class CastingExample {

    public static void main(String... castingExample) {
        Animal animal = new Animal();
        Dog dogAnimal = (Dog) animal; // We will get ClassCastException
        Dog dog = new Dog();
        Animal dogWithAnimalType = new Dog();
        Dog specificDog = (Dog) dogWithAnimalType;
        specificDog.bark();
        Animal anotherDog = dog; // It's fine here, no need for casting
        System.out.println(((Dog)anotherDog)); // This is another way to cast the object
    }

}

class Animal { }
class Dog extends Animal { void bark() { System.out.println("Au au"); } }

当我们将Animal实例强制转换为Dog,会出现类型转换异常。这是因为Animal不知道转换成什,它可能是猫,鸟等。没有关于特定动物的信息。

Animal animal = new Animal();

然后做如下转换:


Dog dogAnimal = (Dog) animal;

由于没有Dog实例,因此无法将Animal转换为Dog。所以会报ClassCastException。
因此应该这样实例化Dog:

Dog dog = new Dog();

然后赋值给Animal:

Animal anotherDog = dog;

用超类型进行转换

可以使用超类型声明 Animal,但是如果我们要调用子类特定方法,则需要对其进行强制转换。例如,如果我们想调用子类bark()方法怎么办,但超类Animal不知道什么什么情况下,可以调用。请看下面例子:

Animal dogWithAnimalType = new Dog();
Dog specificDog = (Dog) dogWithAnimalType;
specificDog.bark();

如果不想声明另一个变量时,也可以使用以下情况强制转换。

System.out.println(((Dog)anotherDog)); // This is another way to cast the object

查看例子分析执行结果

public class InheritanceCompositionChallenge {
    private static int result;
    public static void main(String... doYourBest) {
        Character homer = new Homer();
        homer.drink();
        new Character().drink();
        ((Homer)homer).strangleBart();
        Character character = new Character();
        System.out.println(result);
        ((Homer)character).strangleBart();
    }

    static class Character {
        Character() {
            result++;
        }
        void drink() {
            System.out.println("Drink");
        }
    }

    static class Homer extends Character {
        Lung lung = new Lung();

        void strangleBart() {
            System.out.println("Why you little!");
        }
        void drink() {
            System.out.println("Drink beer");
            lung.damageLungs();
        }
    }

    static class Lung {
        void damageLungs() {
            System.out.println("Soon you will need a transplant");
        }
    }

}

运行main方法后输出是什么

A)

Drink
Drink
Why you little!
2
Exception in thread "main" java.lang.ClassCastException:....

B)

Drink beer
Soon you will need a transplant
Drink
Why you little!
Exception in thread "main" java.lang.ClassCastException:....

C)

Drink beer
Soon you will need a transplant
Drink
Why you little!
3
Exception in thread "main" java.lang.ClassCastException:....

D)

Drink beer
Soon you will need a transplant
Drink
Why you little!
2
Why you little!

正确答案是C
分析代码原因如下:

Character homer = new Homer();
 homer.drink();

Homer实例化对象,执行Homer方法实现,打印结果:

Drink beer
Soon you will need a transplant

然后,Character类中调用drink()。

new Character().drink();

打印结果为:

Drink beer

接着我们使用强制转换并调用strangleBart():

 ((Homer)homer).strangleBart();

打印结果为:

System.out.println(result);

因为super构造函数每次new对象都被调用,所以我们只需要计算Character或Homer实例化几次。
打印结果为:3

最后,我们调用类型转换:

((Homer) character).strangleBart();

因为我们要把父类转换为子类,就好像要把动物转换为鸟,因为会报ClassCastException异常。

发布了145 篇原创文章 · 获赞 26 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/TreeShu321/article/details/105026910