継承と構成:選択方法

継承と構成は、開発者がクラスとオブジェクト間の関係を確立するために使用する2つのプログラミング手法です。継承はあるクラスを別のクラスから派生させますが、構成はクラスをその部分の合計として定義します。

継承関係で親またはスーパークラスを変更するとコードが破損するリスクがあるため、継承によって作成されたクラスとオブジェクトは緊密に結合されています。コンポジションによって作成されたクラスとオブジェクトは疎結合です。つまり、コードを壊すことなく、コンポーネントパーツをより簡単に変更できます。

疎結合コードは柔軟性が高いため、多くの開発者は、合成が継承よりも優れた手法であることを学びましたが、真実はより複雑です。プログラミングツールの選択は、正しいキッチンツールの選択に似ています。野菜を切るのにバターナイフを使用することはありません。同様に、すべてのプログラミングシナリオで構成を選択するべきではありません。 

このJavaチャレンジャーでは、継承と構成の違いと、どちらがプログラムに適しているかを判断する方法を学習します。次に、Java継承のいくつかの重要であるが挑戦的な側面を紹介します。メソッドのオーバーライド、superキーワード、型キャストです。最後に、継承の例を1行ずつ調べて、出力がどうあるべきかを判断することで、学んだことをテストします。

Javaで継承を使用する場合

オブジェクト指向プログラミングでは、子とその親クラスの間に「is a」関係があることがわかっている場合に、継承を使用できます。いくつかの例は次のとおりです。

  • 人間です。
  • 動物です。
  •   乗り物です。

いずれの場合も、子またはサブクラスは親またはスーパークラスの特殊バージョンです。スーパークラスからの継承は、コードの再利用の例です。この関係をよりよく理解するために、以下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 { } } 

この場合、家には居間と寝室があることがわかっているので、Bedroomと  LivingRoomオブジェクトをの構成で使用できますHouse。 

コードを取得する

このJavaチャレンジャーの例のソースコードを入手してください。例に従って、独自のテストを実行できます。

継承と構成:2つの例

次のコードについて考えてみます。これは継承の良い例ですか?

 import java.util.HashSet; public class CharacterBadExampleInheritance extends HashSet { public static void main(String... badExampleOfInheritance) { BadExampleInheritance badExampleInheritance = new BadExampleInheritance(); badExampleInheritance.add("Homer"); badExampleInheritance.forEach(System.out::println); } 

この場合、答えはノーです。子クラスは、決して使用しない多くのメソッドを継承するため、コードが密結合になり、混乱を招き、保守が困難になります。よく見ると、このコードが「isa」テストに合格していないことも明らかです。

それでは、合成を使用して同じ例を試してみましょう。

 import java.util.HashSet; import java.util.Set; public class CharacterCompositionExample { static Set set = new HashSet(); public static void main(String... goodExampleOfComposition) { set.add("Homer"); set.forEach(System.out::println); } 

このシナリオで構成を使用すると、CharacterCompositionExampleクラスはHashSetすべてのメソッドを継承せずに、2つのメソッドのみを使用でき  ます。これにより、コードが単純になり、結合が少なくなり、理解と保守が容易になります。

JDKでの継承の例

Java Development Kitには、継承の良い例がたくさんあります。

 class IndexOutOfBoundsException extends RuntimeException {...} class ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException {...} class FileWriter extends OutputStreamWriter {...} class OutputStreamWriter extends Writer {...} interface Stream extends BaseStream
    
      {...} 
    

これらの各例では、子クラスはその親の特殊バージョンであることに注意してください。たとえば、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クラスを作成します。次に、クラスのメソッドをオーバーライドして、作成する特定のサウンドを取得します。クラスタイプをとして宣言しましたが、インスタンス化すると猫の鳴き声が聞こえます。 AnimalemitSound()CatAnimalCat

メソッドのオーバーライドはポリモーフィズムです

You might remember from my last post that method overriding is an example of polymorphism, or virtual method invocation.

Does Java have multiple inheritance?

Unlike some languages, such as C++, Java does not allow multiple inheritance with classes. You can use multiple inheritance with interfaces, however. The difference between a class and an interface, in this case, is that interfaces don't keep state.

If you attempt multiple inheritance like I have below, the code won't compile:

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

A solution using classes would be to inherit one-by-one:

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

Another solution is to replace the classes with interfaces:

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

Using ‘super' to access parent classes methods

When two classes are related through inheritance, the child class must be able to access every accessible field, method, or constructor of its parent class. In Java, we use the reserved word super to ensure the child class can still access its parent's overridden method:

 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"); } } } 

In this example, Character is the parent class for Moe.  Using super, we are able to access Character's  move() method in order to give Moe a beer.

Using constructors with inheritance

When one class inherits from another, the superclass's constructor always will be loaded first, before loading its subclass. In most cases, the reserved word super will be added automatically to the constructor.  However, if the superclass has a parameter in its constructor, we will have to deliberately invoke the super constructor, as shown below:

 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 } } 

If the parent class has a constructor with at least one parameter, then we must declare the constructor in the subclass and use super to explicitly invoke the parent constructor. The super reserved word won't be added automatically and the code won't compile without it.  For example:

 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"); } } } 

Type casting and the ClassCastException

Casting is a way of explicitly communicating to the compiler that you really do intend to convert a given type.  It's like saying, "Hey, JVM, I know what I'm doing so please cast this class with this type." If a class you've cast isn't compatible with the class type you declared, you will get a ClassCastException.

In inheritance, we can assign the child class to the parent class without casting but we can't assign a parent class to the child class without using casting.

Consider the following example:

 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"); } } 

When we try to cast an Animal instance to a Dog we get an exception. This is because the Animal doesn't know anything about its child. It could be a cat, a bird, a lizard, etc. There is no information about the specific animal. 

The problem in this case is that we've instantiated Animal like this:

 Animal animal = new Animal(); 

Then tried to cast it like this:

 Dog dogAnimal = (Dog) animal; 

Because we don't have a Dog instance, it's impossible to assign an Animal to the Dog.  If we try, we will get a ClassCastException

In order to avoid the exception, we should instantiate the Dog like this:

 Dog dog = new Dog(); 

then assign it to Animal:

 Animal anotherDog = dog; 

In this case, because  we've extended the Animal class, the Dog instance doesn't even need to be cast; the Animal parent class type simply accepts the assignment.

Casting with supertypes

DogスーパータイプAnimalでaを宣言することは可能ですが、から特定のメソッドを呼び出したい場合は、それ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継承に挑戦してください!

継承のいくつかの重要な概念を学習したので、今度は継承の課題を試してみましょう。まず、次のコードを調べます。