抽象化とインターフェース

JavaJavaBeginner
今すぐ練習

💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください

はじめに

抽象化(Abstraction)とインターフェース(Interface)は、Java のオブジェクト指向プログラミングにおける 2 つの基本的な概念です。抽象クラス(Abstract class)とインターフェースは異なる目的を持っていますが、いくつかの共通点もあります。この実験(Lab)では、これらの概念を理解して実装する方法を案内し、Java プログラムでいつ、どのように効果的に使用するかを理解する手助けをします。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL java(("Java")) -.-> java/ObjectOrientedandAdvancedConceptsGroup(["Object-Oriented and Advanced Concepts"]) java/ObjectOrientedandAdvancedConceptsGroup -.-> java/classes_objects("Classes/Objects") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/oop("OOP") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/inheritance("Inheritance") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/abstraction("Abstraction") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/interface("Interface") subgraph Lab Skills java/classes_objects -.-> lab-178542{{"抽象化とインターフェース"}} java/oop -.-> lab-178542{{"抽象化とインターフェース"}} java/inheritance -.-> lab-178542{{"抽象化とインターフェース"}} java/abstraction -.-> lab-178542{{"抽象化とインターフェース"}} java/interface -.-> lab-178542{{"抽象化とインターフェース"}} end

抽象化(Abstraction)の理解

抽象化(Abstraction)は、オブジェクト指向プログラミングにおける核心的な概念であり、実装の詳細を隠し、ユーザーに必要な機能のみを公開することに焦点を当てています。これにより、関連する部分のみを提示し、複雑な内部メカニズムを隠すことで、オブジェクトの簡略化されたビューを作成することができます。

Java では、抽象化は以下の方法で実現されます。

  • 抽象クラス(Abstract class)
  • インターフェース(Interface)

抽象クラス(Abstract class)とは?

抽象クラス(Abstract class)は、直接インスタンス化することができず、抽象メソッド(実装のないメソッド)を含むことがあるクラスです。抽象クラスは他のクラスの設計図として機能し、共通の構造と振る舞いを提供します。

抽象クラスの主要な特徴:

  • abstract キーワードを使用して宣言されます。
  • 抽象メソッドと非抽象メソッドを含むことができます。
  • 直接インスタンス化することはできません。
  • コンストラクタとインスタンス変数を持つことができます。
  • サブクラスはすべての抽象メソッドを実装するか、自身も抽象クラスとして宣言する必要があります。

WebIDE エディタを開き、ファイル /home/labex/project/abstractTest.java を変更して、抽象クラスを作成する方法を探ってみましょう。

// Abstract class example
abstract class Animal {
    // Abstract method - no implementation
    public abstract void makeSound();

    // Concrete method - has implementation
    public void eat() {
        System.out.println("The animal is eating");
    }
}

// Concrete subclass that implements the abstract method
class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks: Woof! Woof!");
    }
}

// Main class to test our implementation
public class abstractTest {
    public static void main(String[] args) {
        // Cannot create an instance of Animal
        // Animal animal = new Animal(); // This would cause compilation error

        // Create an instance of Dog
        Dog dog = new Dog();
        dog.makeSound(); // Call the implemented abstract method
        dog.eat();       // Call the inherited concrete method
    }
}

では、このコードを実行して結果を見てみましょう。

javac /home/labex/project/abstractTest.java
java abstractTest
compile and run

以下の出力が表示されるはずです。

Dog barks: Woof! Woof!
The animal is eating

これは、抽象クラスがサブクラスに対してテンプレートを提供する方法を示しています。Animal クラスは、サブクラスが持つべきメソッド(makeSound() メソッド)を定義すると同時に、共通の機能(eat() メソッド)も提供しています。

抽象クラス(Abstract class)の継承

抽象クラス(Abstract class)を扱う際に、継承は重要な役割を果たします。このステップでは、抽象クラスの継承に関するより複雑なシナリオを探っていきます。

抽象クラスが他の抽象クラスから継承できる方法を示すために、/home/labex/project/abstractTest.java ファイルを変更しましょう。

// Base abstract class
abstract class Animal {
    // Instance variable
    protected String name;

    // Constructor
    public Animal(String name) {
        this.name = name;
    }

    // Abstract method
    public abstract void makeSound();

    // Concrete method
    public void eat() {
        System.out.println(name + " is eating");
    }
}

// Another abstract class that extends Animal
abstract class Bird extends Animal {
    // Constructor calling parent constructor
    public Bird(String name) {
        super(name);
    }

    // Concrete method specific to Bird
    public void fly() {
        System.out.println(name + " is flying");
    }

    // Note: Bird doesn't implement makeSound(), so it remains abstract
}

// Concrete subclass of Bird
class Sparrow extends Bird {
    public Sparrow(String name) {
        super(name);
    }

    // Implementing the abstract method from Animal
    @Override
    public void makeSound() {
        System.out.println(name + " chirps: Tweet! Tweet!");
    }
}

// Concrete subclass of Animal
class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(name + " barks: Woof! Woof!");
    }
}

// Main class to test our implementation
public class abstractTest {
    public static void main(String[] args) {
        // Create instances of concrete classes
        Dog dog = new Dog("Buddy");
        Sparrow sparrow = new Sparrow("Jack");

        // Test methods
        dog.makeSound();
        dog.eat();

        sparrow.makeSound();
        sparrow.eat();
        sparrow.fly();
    }
}

この更新されたコードを実行しましょう。

javac /home/labex/project/abstractTest.java
java abstractTest

以下の出力が表示されるはずです。

Buddy barks: Woof! Woof!
Buddy is eating
Jack chirps: Tweet! Tweet!
Jack is eating
Jack is flying

この例はいくつかの重要な概念を示しています。

  1. 抽象クラスはコンストラクタとインスタンス変数を持つことができます。
  2. 抽象クラスは他の抽象クラスを拡張することができます。
  3. 抽象クラスが他の抽象クラスを拡張する場合、抽象メソッドを実装する必要はありません。
  4. 継承チェーンの最後の具象クラスは、すべての親クラスの抽象メソッドをすべて実装する必要があります。

抽象クラスは以下の場合に特に有用です。

  • 密接に関連するクラス間でコードを共有したい場合
  • サブクラスに多くの共通メソッドやフィールドがあることが予想される場合
  • 一部のメソッドにデフォルトの実装を提供する必要がある場合
  • 一部のメソッドのアクセス可能性を制御したい場合

インターフェース(Interface)の理解

インターフェース(Interface)は、Java で抽象化を実現する別の方法を提供します。抽象クラス(Abstract class)とは異なり、インターフェースは完全に抽象的で、(Java 8 以前は)メソッドの実装を含むことができません。

インターフェースは、実装するクラスが従わなければならない契約を定義します。つまり、クラスが何をできるかを指定しますが、それをどのように行うかを指示することはありません。

インターフェース(Interface)とは?

インターフェースの主要な特徴:

  • interface キーワードを使用して宣言されます。
  • すべてのメソッドは暗黙的に public かつ abstract です(Java 8 以前)。
  • すべてのフィールドは暗黙的に public、static、かつ final です(定数)。
  • クラスは複数のインターフェースを実装することができます。
  • インターフェースは複数のインターフェースを拡張することができます。

ファイル /home/labex/project/interfaceTest.java を変更して、基本的なインターフェースを作成しましょう。

// Define an interface
interface Animal {
    // Constants (implicitly public, static, final)
    String CATEGORY = "Living Being";

    // Abstract methods (implicitly public and abstract)
    void makeSound();
    void move();
}

// Class implementing the interface
class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks: Woof! Woof!");
    }

    @Override
    public void move() {
        System.out.println("Dog runs on four legs");
    }
}

// Main class to test the interface
public class interfaceTest {
    public static void main(String[] args) {
        // Create a Dog object
        Dog dog = new Dog();

        // Call the interface methods
        dog.makeSound();
        dog.move();

        // Access the interface constant
        System.out.println("Category: " + Animal.CATEGORY);
    }
}

インターフェースがどのように機能するかを確認するために、このコードを実行しましょう。

javac /home/labex/project/interfaceTest.java
java interfaceTest

以下の出力が表示されるはずです。

Dog barks: Woof! Woof!
Dog runs on four legs
Category: Living Being

この例は、インターフェースの基本的な概念を示しています。Animal インターフェースは、Dog クラスが実装しなければならない契約を定義しています。Animal インターフェースを実装するすべてのクラスは、インターフェースで宣言されたすべてのメソッドの実装を提供する必要があります。

複数のインターフェースとインターフェースの継承

インターフェースの主な利点の 1 つは、複数のインターフェースを実装し、インターフェースの階層構造を作成できることです。これは、単一継承のみをサポートする抽象クラス(Abstract class)と比較して、より高い柔軟性を提供します。

これらの概念を示すために、/home/labex/project/interfaceTest.java ファイルを更新しましょう。

// First interface
interface Animal {
    // Constants
    String CATEGORY = "Living Being";

    // Methods
    void makeSound();
    void eat();
}

// Second interface
interface Pet {
    // Constants
    String STATUS = "Domesticated";

    // Methods
    void play();
    void cuddle();
}

// Interface extending another interface
interface Bird extends Animal {
    // Additional method
    void fly();
}

// Class implementing multiple interfaces
class Dog implements Animal, Pet {
    @Override
    public void makeSound() {
        System.out.println("Dog barks: Woof! Woof!");
    }

    @Override
    public void eat() {
        System.out.println("Dog eats meat and dog food");
    }

    @Override
    public void play() {
        System.out.println("Dog plays fetch");
    }

    @Override
    public void cuddle() {
        System.out.println("Dog cuddles with its owner");
    }
}

// Class implementing the Bird interface
class Sparrow implements Bird {
    @Override
    public void makeSound() {
        System.out.println("Sparrow chirps: Tweet! Tweet!");
    }

    @Override
    public void eat() {
        System.out.println("Sparrow eats seeds and insects");
    }

    @Override
    public void fly() {
        System.out.println("Sparrow flies with its wings");
    }
}

// Main class to test our interfaces
public class interfaceTest {
    public static void main(String[] args) {
        // Create objects
        Dog dog = new Dog();
        Sparrow sparrow = new Sparrow();

        // Call methods from different interfaces
        System.out.println("--- Dog behaviors ---");
        dog.makeSound();
        dog.eat();
        dog.play();
        dog.cuddle();

        System.out.println("\n--- Sparrow behaviors ---");
        sparrow.makeSound();
        sparrow.eat();
        sparrow.fly();

        // Access constants from interfaces
        System.out.println("\n--- Interface Constants ---");
        System.out.println("Animal Category: " + Animal.CATEGORY);
        System.out.println("Pet Status: " + Pet.STATUS);
    }
}

この更新されたコードを実行しましょう。

javac /home/labex/project/interfaceTest.java
java interfaceTest

以下の出力が表示されるはずです。

--- Dog behaviors ---
Dog barks: Woof! Woof!
Dog eats meat and dog food
Dog plays fetch
Dog cuddles with its owner

--- Sparrow behaviors ---
Sparrow chirps: Tweet! Tweet!
Sparrow eats seeds and insects
Sparrow flies with its wings

--- Interface Constants ---
Animal Category: Living Being
Pet Status: Domesticated

この例は、いくつかの重要なインターフェースの概念を示しています。

  1. クラスは複数のインターフェースを実装できます(DogAnimalPet の両方を実装しています)。
  2. インターフェースは別のインターフェースを拡張できます(BirdAnimal を拡張しています)。
  3. インターフェースを実装するクラスは、そのすべてのメソッドと拡張されたインターフェースのメソッドをすべて実装する必要があります。
  4. インターフェースの定数は、インターフェース名を使用してアクセスできます。

インターフェースは、以下の場合に特に有用です。

  • 実装の詳細を伴わずに契約を定義したい場合。
  • 多重継承が必要な場合。
  • 関係のないクラスが同じ振る舞いを実装する必要がある場合。
  • サービスプロバイダーの振る舞いを指定したいが、その実装方法を指示したくない場合。

抽象クラス(Abstract class)とインターフェース(Interface)の比較

ここまで抽象クラス(Abstract class)とインターフェース(Interface)の両方を学んできました。では、この 2 つの抽象化手法を比較し、それぞれをいつ使うべきかを理解しましょう。

主な違い

違いを説明するために、/home/labex/project/ComparisonExample.java というファイルを作成しましょう。

// Abstract class example
abstract class Vehicle {
    // Instance variables
    protected String brand;

    // Constructor
    public Vehicle(String brand) {
        this.brand = brand;
    }

    // Abstract method
    public abstract void start();

    // Concrete method
    public void stop() {
        System.out.println(brand + " vehicle stops");
    }
}

// Interface example
interface ElectricPowered {
    // Constants
    String POWER_SOURCE = "Electricity";

    // Abstract methods
    void charge();
    void displayBatteryStatus();
}

// Class using abstract class and interface
class ElectricCar extends Vehicle implements ElectricPowered {
    private int batteryLevel;

    public ElectricCar(String brand, int batteryLevel) {
        super(brand);
        this.batteryLevel = batteryLevel;
    }

    // Implementing abstract method from Vehicle
    @Override
    public void start() {
        System.out.println(brand + " electric car starts silently");
    }

    // Implementing methods from ElectricPowered interface
    @Override
    public void charge() {
        batteryLevel = 100;
        System.out.println(brand + " electric car is charging. Battery now at 100%");
    }

    @Override
    public void displayBatteryStatus() {
        System.out.println(brand + " battery level: " + batteryLevel + "%");
    }
}

public class ComparisonExample {
    public static void main(String[] args) {
        ElectricCar tesla = new ElectricCar("Tesla", 50);

        // Methods from abstract class Vehicle
        tesla.start();
        tesla.stop();

        // Methods from interface ElectricPowered
        tesla.displayBatteryStatus();
        tesla.charge();
        tesla.displayBatteryStatus();

        // Access constant from interface
        System.out.println("Power source: " + ElectricPowered.POWER_SOURCE);
    }
}

このコードを実行しましょう。

javac /home/labex/project/ComparisonExample.java
java ComparisonExample

以下の出力が表示されるはずです。

Tesla electric car starts silently
Tesla vehicle stops
Tesla battery level: 50%
Tesla electric car is charging. Battery now at 100%
Tesla battery level: 100%
Power source: Electricity

いつ使うべきか?

抽象クラス(Abstract class)とインターフェース(Interface)をいつ使うべきかを判断するのに役立つ比較表を以下に示します。

特徴 抽象クラス(Abstract class) インターフェース(Interface)
メソッド 抽象メソッドと具象メソッドの両方を持つことができます すべてのメソッドは抽象的です(Java 8 以前)
変数 インスタンス変数を持つことができます 定数(public static final)のみを持つことができます
コンストラクタ コンストラクタを持つことができます コンストラクタを持つことができません
継承 単一継承をサポートします クラスは複数のインターフェースを実装できます
アクセス修飾子 メソッドは任意のアクセス修飾子を持つことができます メソッドは暗黙的に public です
目的 「is-a」関係(継承) 「can-do」能力(振る舞い)

抽象クラス(Abstract class)を使うべき場合:

  • 密接に関連するクラス間でコードを共有したい場合
  • いくつかのメソッドにデフォルトの実装を提供する必要がある場合
  • 非公開のメンバー(フィールド、メソッド)が必要な場合
  • コンストラクタまたはインスタンスフィールドが必要な場合
  • サブクラスのグループのテンプレートを定義する場合

インターフェース(Interface)を使うべき場合:

  • 関係のないクラスがインターフェースを実装することを想定している場合
  • 振る舞いを指定したいが、実装を指定したくない場合
  • 多重継承が必要な場合
  • サービスの契約を定義したい場合

多くの場合、良い設計では、ElectricCar の例のように、抽象クラス(Abstract class)とインターフェース(Interface)が連携して機能することがあります。

まとめ

この実験(Lab)では、Java における 2 つの強力な抽象化手法である抽象クラス(Abstract class)とインターフェース(Interface)を学びました。以下が要点です。

  • 抽象クラス(Abstract class):

    • 直接インスタンス化することはできません。
    • 抽象メソッドと具象メソッドの両方を持つことができます。
    • コンストラクタとインスタンス変数をサポートします。
    • 単一継承モデルに従います。
    • 「is-a」関係と共有実装に最適です。
  • インターフェース(Interface):

    • 実装するクラスが従わなければならない契約を定義します。
    • すべてのフィールドは定数(public、static、final)です。
    • メソッドは暗黙的に public かつ抽象的です(Java 8 以前)。
    • 実装を通じて多重継承をサポートします。
    • 「can-do」能力と緩い結合に最適です。

抽象クラス(Abstract class)とインターフェース(Interface)の両方は、オブジェクト指向プログラミングの基本原則の 1 つである抽象化を実現するための重要なツールです。どちらを選ぶかは設計上のニーズによって異なり、それぞれ特定のシナリオで独自の強みを持っています。

Java プログラミングの旅を続ける中で、抽象クラス(Abstract class)とインターフェース(Interface)を適切に使うことで、より保守しやすく、柔軟性が高く、堅牢なコード構造を構築できることがわかるでしょう。