c++ 继承 组成
继承和组合是开发人员用来在类和对象之间建立关系的两种编程技术。 继承是从另一类继承一个类,而composition将一个类定义为其部分的总和。
通过继承创建的类和对象紧密耦合,因为在继承关系中更改父类或超类可能会破坏您的代码。 通过合成创建的类和对象是松散耦合的 ,这意味着您可以更轻松地更改组件而不会破坏代码。
因为松散耦合的代码提供了更大的灵活性,所以许多开发人员已经了解到,组合是比继承更好的技术,但事实更复杂。 选择编程工具类似于选择正确的厨房工具:您不会使用黄油刀切菜,并且同样地,您不应为每种编程场景都选择成分。
在此Java Challenger中,您将学习继承与组合之间的区别,以及如何确定哪个对您的程序正确 。 接下来,我将向您介绍Java继承的几个重要但具有挑战性的方面:方法覆盖, super
关键字和类型转换。 最后,您将通过逐行处理一个继承示例来确定输出内容,以测试所学内容。
何时在Java中使用继承
在面向对象的编程中,当我们知道孩子与其父类之间存在“是”关系时,就可以使用继承。 一些示例是:
- 一个人就是一个人。
- 猫是动物。
- 汽车就是车辆。
在每种情况下,子类或子类都是父类或超类的专门版本。 从超类继承是代码重用的一个示例。 为了更好地理解这种关系,花点时间研究一下Car
类,该类继承自Vehicle
:
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中使用合成
在面向对象的编程中,我们可以在一个对象“具有”(或属于)另一个对象的情况下使用组合。 一些示例是:
- 汽车有电池(电池是汽车的一部分 )。
- 一个人有心脏(心脏是一个人的一部分 )。
- 房子有一个客厅(客厅是房子的一部分 )。
为了更好地理解这种类型的关系,请考虑House
的组成:
public class CompositionExample {
public static void main(String... houseComposition) {
new House(new Bedroom(), new LivingRoom());
// The house now is composed with a Bedroom and a 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 { }
}
在这种情况下,我们知道房屋有一个客厅和一间卧室,因此我们可以在House
的组成中使用Bedroom
和LivingRoom
对象。
获取代码
获取此Java Challenger中示例的源代码 。 您可以在遵循示例的同时运行自己的测试。
继承与组成:两个例子
考虑以下代码。 这是继承的好例子吗?
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
的两个方法,而无需继承所有方法。 这样可以简化代码,减少耦合,使代码更易于理解和维护。
JDK中的继承示例
Java Development Kit充满了很好的继承示例:
class IndexOutOfBoundsException extends RuntimeException {...}
class ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException {...}
class FileWriter extends OutputStreamWriter {...}
class OutputStreamWriter extends Writer {...}
interface Stream<T> extends BaseStream<T, Stream<T>> {...}
注意,在每个示例中,子类都是其父类的专门版本; 例如, IndexOutOfBoundsException
是RuntimeException
。
用Java继承重写方法
继承使我们可以在新类中重用一个类的方法和其他属性,这非常方便。 但是,要使继承真正起作用,我们还需要能够在新的子类中更改某些继承的行为。 例如,我们可能要专门化Cat
发出的声音:
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不允许对类进行多重继承。 但是,您可以对接口使用多重继承。 在这种情况下,类和接口之间的区别在于接口不保持状态。
如果像我在下面那样尝试多重继承,则代码将无法编译:
class Animal {}
class Mammal {}
class Dog extends Animal, Mammal {}
使用类的解决方案将是逐一继承:
class Animal {}
class Mammal extends Animal {}
class Dog extends Mammal {}
另一个解决方案是用接口替换类:
interface Animal {}
interface Mammal {}
class Dog implements Animal, Mammal {}
使用“超级”访问父类方法
当两个类通过继承关系关联时,子类必须能够访问其父类的每个可访问字段,方法或构造函数。 在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()
方法来给Moe喝啤酒。
将构造函数与继承一起使用
当一个类继承自另一个类时,在加载其子类之前,始终会先加载超类的构造函数。 在大多数情况下,保留字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
强制转换是一种向编译器明确传达您确实打算转换给定类型的方式。 就像说,“嘿,JVM,我知道我在做什么,所以请使用这种类型转换此类。” 如果您强制转换的类与声明的类类型不兼容,则将收到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 animal = new Animal();
然后尝试将其投射如下:
Dog dogAnimal = (Dog) animal;
因为我们没有Dog
实例,所以不可能将Animal
分配给Dog
。 如果尝试,将得到ClassCastException
。
为了避免异常,我们应该像这样实例化Dog
:
Dog dog = new Dog();
然后将其分配给Animal
:
Animal anotherDog = dog;
在这种情况下,因为我们扩展了Animal
类,所以甚至不需要Dog
实例; Animal
父类类型仅接受分配。
用超类型进行转换
可以用超Animal
声明一个Dog
,但是如果我们想从Dog
调用一个特定的方法,我们将需要转换它。 例如,如果要调用bark()
方法怎么办? Animal
超类型无法确切知道我们正在调用哪个动物实例,因此在调用bark()
方法之前,我们必须手动转换Dog
:
Animal dogWithAnimalType = new Dog();
Dog specificDog = (Dog) dogWithAnimalType;
specificDog.bark();
您也可以在不将对象分配给类类型的情况下使用强制转换。 当您不想声明另一个变量时,此方法很方便:
System.out.println(((Dog)anotherDog)); // This is another way to cast the object
接受Java继承挑战!
您已经了解了一些重要的继承概念,因此现在是时候尝试继承挑战了。 首先,请研究以下代码:
翻译自: https://www.infoworld.com/article/3409071/java-challenger-7-debugging-java-inheritance.html
c++ 继承 组成