继承和构造是开发人员用来在类和对象之间建立关系的两种编程技术。继承是从另一类继承一个类,而组合将一个类定义为其部分的总和。
通过继承创建的类和对象紧密耦合,因为在继承关系中更改父类或超类可能会破坏您的代码。通过合成创建的类和对象是松散耦合的,这意味着您可以更轻松地更改组件,而不会破坏代码。
因为松散耦合的代码提供了更大的灵活性,所以许多开发人员已经了解到,构造是比继承更好的技术,但事实更复杂。选择编程工具类似于选择正确的厨房工具:您不会使用黄油刀切菜,并且同样的,您不应为每种编程场景都选择成分。
何时在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中使用合成
在面向对象的编程中,我们可以在一个对象“具有”(或属于)另一个对象的情况下使用组合。一些示例是:
· 汽车有电池(电池是汽车的一部分)。
· 一个人有心脏(心脏是人的一部分)。
· 房子有一个客厅(客厅是房子的一部分)。
为了更好地理解这种类型的关系,请考虑a的组成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 { }
}
在这种情况下,我们知道一所房子有一个客厅和一间卧室,因此我们可以使用Bedroom和 LivingRoom对象构成一个House。
获取代码
获取此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);
}
在这种情况下使用composition允许 CharacterCompositionExample类仅使用的两个HashSet方法,而无需继承所有方法。这样可以简化代码,减少耦合,使代码更易于理解和维护。
JDK中的继承示例
Java Development Kit充满了很好的继承示例:
class IndexOutOfBoundsException extends RuntimeException {...}
class ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException {...}
class FileWriter extends OutputStreamWriter {...}
class OutputStreamWriter extends Writer {...}
interface Streamextends BaseStreamStream> {...}
注意,在每个示例中,子类都是其父类的专门版本;例如,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父类的类型只是接受任务。
用超类型进行转换
可以Dog使用超类型声明a Animal,但是如果我们要从中调用特定方法Dog,则需要对其进行强制转换。例如,如果我们想调用该bark()方法怎么办?该Animal超有没有办法知道到底是什么动物的情况下,我们是在调用,所以我们要投Dog之前手动我们可以调用bark()方法:
Animal dogWithAnimalType = new Dog();Dog specificDog = (Dog) dogWithAnimalType;
specificDog.bark();
您也可以在不将对象分配给类类型的情况下使用强制转换。当您不想声明另一个变量时,此方法很方便:
System.out.println(((Dog)anotherDog)); // This is another way to cast the object
最后,开发这么多年我也总结了一套学习Java的资料与面试题,如果你在技术上面想提升自己的话,可以关注我,私信发送领取资料或者在评论区留下自己的联系方式,有时间记得帮我点下转发让跟多的人看到哦。