文章目录
继承与组合
继承和组合是程序员用在类和对象之间建立关系的两种编程技术。继承是从另一类继承一个类,而组合将一个类定义为其部分的总和。
通过继承创建的类和对象是强耦合,因为在继承关系中更改父类或超类可能会破坏代码。通过组合创建的类和对象是松耦合,这意味着可以更轻松地更改组件,而无需破坏代码。
因此组合提供了更大的灵活性,所以许多程序员已经知道,组合是比继承更好的选择。
继承与组合之间的区别,以及如何选择使用继承和组合。请继续下面干货。
何时在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异常。