サーチ…


前書き

インタフェースはクラスと同様の参照型で、 interfaceキーワードを使用して宣言できます。インターフェイスには、定数、メソッドシグネチャ、デフォルトメソッド、静的メソッド、およびネストされた型のみを含めることができます。メソッド本体は、デフォルトメソッドと静的メソッドに対してのみ存在します。抽象クラスのように、インタフェースはインスタンス化することができません。インタフェースはクラスによってのみ実装でき、他のインタフェースで拡張することもできます。インタフェースは、Javaで完全な抽象化を実現するための一般的な方法です。

構文

  • パブリックインターフェイスFoo {void foo(); / *他のメソッド* /}
  • パブリックインターフェイスFoo1はFoo {void bar(); / *他のメソッド* /}
  • public class Foo2はFoo、Foo1を実装しています{/ * FooとFoo1の実装* /}

インターフェイスの宣言と実装

interfaceキーワードを使用したinterface宣言:

public interface Animal {
    String getSound(); // Interface methods are public by default
}

注釈を上書きする

@Override
public String getSound() {
    // Code goes here...
}

これにより、コンパイラはオーバーライドしていることを確認し、プログラムが新しいメソッドを定義したり、メソッドのシグネチャを誤ってしまうことを防ぎます。

インタフェースは、 implements キーワード を使用して implements ます。

public class Cat implements Animal {

    @Override 
    public String getSound() {
        return "meow";
    }
}

public class Dog implements Animal {

    @Override
    public String getSound() {
        return "woof";
    }
}

この例では、インターフェイスのメソッドが本質的に抽象メソッドであるため、クラスCatおよびDoggetSound()メソッドを定義する必要がありますgetSound()デフォルトメソッドを除く)。

インタフェースの使用

Animal cat = new Cat();
Animal dog = new Dog();

System.out.println(cat.getSound()); // prints "meow"
System.out.println(dog.getSound()); // prints "woof"

複数のインタフェースの実装

Javaクラスは、複数のインタフェースを実装できます。

public interface NoiseMaker {
    String noise = "Making Noise"; // interface variables are public static final by default

    String makeNoise(); //interface methods are public abstract by default
}

public interface FoodEater {
    void eat(Food food);
}

public class Cat implements NoiseMaker, FoodEater { 
    @Override
    public String makeNoise() {
        return "meow";
    }

    @Override
    public void eat(Food food) {
        System.out.println("meows appreciatively");
    }
}

Catクラスが両方のインタフェースで継承したabstractメソッドを実装する必要があることに注目してください。さらに、クラスが実際に必要な数のインタフェースを実際に実装できる方法に注意してください( JVMの制限のために65,535という制限があります)。

NoiseMaker noiseMaker = new Cat(); // Valid
FoodEater foodEater = new Cat(); // Valid
Cat cat = new Cat(); // valid

Cat invalid1 = new NoiseMaker(); // Invalid
Cat invalid2 = new FoodEater(); // Invalid

注意:

  1. インタフェースで宣言されたすべての変数はpublic static final
  2. インターフェイスのメソッドで宣言されたすべてのメソッドがされているpublic abstract (この文は唯一のJava 8からのJava 7を介して有効である、あなたは抽象的である必要はないインタフェースのメソッドを、持つことが許可されている。このような方法は、次のように知られているデフォルトの方法
  3. インタフェースをfinalとして宣言することはできません
  4. 複数のインタフェースが同一のシグネチャを持つメソッドを宣言している場合、実質的に1つのメソッドとして扱われ、どのインタフェースメソッドが実装されているかを区別できません
  5. 対応するInterfaceName.classファイルは、コンパイル時に各インタフェースに対して生成されます

インタフェースの拡張

インタフェースは、 extendsキーワードによって別のインタフェースを拡張することができます。

public interface BasicResourceService {
    Resource getResource();
}

public interface ExtendedResourceService extends BasicResourceService {
    void updateResource(Resource resource);
}

これで、 ExtendedResourceServiceを実装するクラスは、 getResource()updateResource()両方を実装する必要があります。

複数のインターフェースを拡張する

クラスとは異なり、 extendsキーワードを使用して、複数のインタフェースを(カンマで区切って)拡張することができ、インタフェースを新しいインタフェースに組み合わせることができます

public interface BasicResourceService {
    Resource getResource();
}

public interface AlternateResourceService {
    Resource getAlternateResource();
}

public interface ExtendedResourceService extends BasicResourceService, AlternateResourceService {
    Resource updateResource(Resource resource);
}

この場合、 ExtendedResourceServiceを実装するクラスは、 getResource()getAlternateResource() 、およびupdateResource()を実装する必要があります。

Genericsでのインタフェースの使用

たとえば、AMQP、JMSなどのさまざまな種類のチャネルとの間でデータをパブリッシュ/消費するインターフェイスを定義したいとしますが、実装の詳細を切り替えることができるようにしたいとしましょう。

複数の実装で再利用できる基本的なIOインターフェイスを定義しましょう:

public interface IO<IncomingType, OutgoingType> {

    void publish(OutgoingType data);
    IncomingType consume();
    IncomingType RPCSubmit(OutgoingType data);

}

今私はそのインタフェースをインスタンス化することができますが、これらのメソッドのデフォルトの実装がないので、インスタンス化する際には実装が必要です。

    IO<String, String> mockIO = new IO<String, String>() {

        private String channel = "somechannel";

        @Override
        public void publish(String data) {
            System.out.println("Publishing " + data + " to " + channel);
        }

        @Override
        public String consume() {
            System.out.println("Consuming from " + channel);
            return "some useful data";
        }

        @Override
        public String RPCSubmit(String data) {
            return "received " + data + " just now ";
        }

    };

    mockIO.consume(); // prints: Consuming from somechannel
    mockIO.publish("TestData"); // Publishing TestData to somechannel
    System.out.println(mockIO.RPCSubmit("TestData")); // received TestData just now

私たちはまた、そのインタフェースでもっと便利なことをすることができます。基本的なRabbitMQ関数をラップするために使いたいとしましょう:

public class RabbitMQ implements IO<String, String> {

    private String exchange;
    private String queue;

    public RabbitMQ(String exchange, String queue){
        this.exchange = exchange;
        this.queue = queue;
    }

    @Override
    public void publish(String data) {
        rabbit.basicPublish(exchange, queue, data.getBytes());
    }

    @Override
    public String consume() {
        return rabbit.basicConsume(exchange, queue);
    }

    @Override
    public String RPCSubmit(String data) {
        return rabbit.rpcPublish(exchange, queue, data);
    }

}

最後にシステムを再起動してから私のウェブサイトへの訪問数を数え、総訪問数を表示できるようにするためにこのIOインターフェイスを使用したいとしましょう。

import java.util.concurrent.atomic.AtomicLong;

public class VisitCounter implements IO<Long, Integer> {

    private static AtomicLong websiteCounter = new AtomicLong(0);
    
    @Override
    public void publish(Integer count) {
        websiteCounter.addAndGet(count);
    }

    @Override
    public Long consume() {
        return websiteCounter.get();
    }

    @Override
    public Long RPCSubmit(Integer count) {
        return websiteCounter.addAndGet(count);
    }
    
}

VisitCounterを使用しましょう:

    VisitCounter counter = new VisitCounter();

    // just had 4 visits, yay
    counter.publish(4);
    // just had another visit, yay
    counter.publish(1);

    // get data for stats counter
    System.out.println(counter.consume()); // prints 5

    // show data for stats counter page, but include that as a page view
    System.out.println(counter.RPCSubmit(1)); // prints 6

複数のインターフェイスを実装する場合、同じインターフェイスを2回実装することはできません。これは汎用インターフェースにも当てはまります。したがって、次のコードは無効で、コンパイルエラーが発生します。

interface Printer<T> {
    void print(T value);
}

// Invalid!
class SystemPrinter implements Printer<Double>, Printer<Integer> {
    @Override public void print(Double d){ System.out.println("Decimal: " + d); }
    @Override public void print(Integer i){ System.out.println("Discrete: " + i); }
}

インタフェースの有用性

多くの場合、インタフェースは非常に便利です。たとえば、動物のリストがあり、リストをループして、それぞれのサウンドを印刷したいとします。

{cat, dog, bird}

これを行う1つの方法は、インターフェイスを使用することです。これにより、すべてのクラスで同じメソッドを呼び出すことができます

public interface Animal {
    public String getSound();
}

implements Animalimplements Animalクラスでも、 getSound()メソッドが必要ですが、それらはすべて異なる実装を持つことができます

public class Dog implements Animal {
    public String getSound() {
        return "Woof";
    }
}

public class Cat implements Animal {
    public String getSound() {
        return "Meow";
    }
}

public class Bird implements Animal{
    public String getSound() {
        return "Chirp";
    }
}

3つのクラスがあり、それぞれにgetSound()メソッドがあります。これらのクラスのすべてがあるのでimplement Animal宣言するインターフェース、 getSound()メソッドを、任意のインスタンスAnimal持つことができますgetSound()それに呼びかけ

Animal dog = new Dog();
Animal cat = new Cat();
Animal bird = new Bird();

dog.getSound(); // "Woof"
cat.getSound(); // "Meow"
bird.getSound(); // "Chirp"

これらのそれぞれがあるのでAnimal 、私たちも、それらを介して、リスト内のループを、動物を入れて、その音をプリントアウトすることができ

Animal[] animals = { new Dog(), new Cat(), new Bird() };
for (Animal animal : animals) {
    System.out.println(animal.getSound());
}

配列の順序はDogCatBirdの順であるため、 「Woof Meow Chirp」がコンソールに出力されます。

インタフェースは、関数の戻り値としても使用できます。たとえば、入力が「dog」の場合はDog返し、入力が「cat」の場合はCat「bird」の場合はBird返し、その動物のサウンドを出力するには、

public Animal getAnimalByName(String name) {
    switch(name.toLowerCase()) {
        case "dog":
            return new Dog();
        case "cat":
            return new Cat();
        case "bird":
            return new Bird();
        default:
            return null;
    }
}

public String getAnimalSoundByName(String name){
    Animal animal = getAnimalByName(name);
    if (animal == null) {
        return null;
    } else { 
        return animal.getSound();
    }
}

String dogSound = getAnimalSoundByName("dog"); // "Woof"
String catSound = getAnimalSoundByName("cat"); // "Meow"
String birdSound = getAnimalSoundByName("bird"); // "Chirp"
String lightbulbSound = getAnimalSoundByName("lightbulb"); // null

新しいタイプのAnimalを追加したい場合は、実行する操作で何かを変更する必要がないため、インターフェイスは拡張性にも役立ちます。

抽象クラスでのインタフェースの実装

interface定義されるメソッドは、デフォルトでpublic abstractです。ときにabstract class実装interface 、で定義されているいずれかの方法でinterfaceで実装する必要はありませんabstract class 。これは、 abstractと宣言されたclass抽象メソッド宣言が含まれるためです。したがって、任意のインタフェースおよび/またはabstract classから継承したabstractメソッドを実装するのは、最初の具体的なサブクラスの責任です。

public interface NoiseMaker {
    void makeNoise();
}

public abstract class Animal implements NoiseMaker {
    //Does not need to declare or implement makeNoise()
    public abstract void eat();
}

//Because Dog is concrete, it must define both makeNoise() and eat()
public class Dog extends Animal {
    @Override
    public void makeNoise() {
        System.out.println("Borf borf");
    }

    @Override
    public void eat() {
        System.out.println("Dog eats some kibble.");
    }
}

Java 8以降では、 interfaceがメソッドのabstractメソッドではないことを意味するメソッドのdefault実装を宣言することが可能です。したがって、具体的なサブクラスはメソッドを実装する必要はありませんが、オーバーライドされない限りdefault実装を継承しdefault

デフォルトのメソッド

Java 8で導入されたデフォルトメソッドは、インタフェース内の実装を指定する方法です。これは、インタフェースの部分実装を提供し、サブクラス階層を制限することによって、典型的な "Base"または "Abstract"クラスを避けるために使用できます。

オブザーバーパターンの実装

たとえば、Observer-Listenerパターンを直接インターフェイスに実装して、実装するクラスに柔軟性を持たせることができます。

interface Observer {
    void onAction(String a);
}

interface Observable{
    public abstract List<Observer> getObservers();

    public default void addObserver(Observer o){
        getObservers().add(o);
    }

    public default void notify(String something ){
        for( Observer l : getObservers() ){
            l.onAction(something);
        }
    }
}

ここでObservableインターフェースを実装するだけで、どのクラスも「Observable」にできますが、別のクラス階層の一部として自由に使えます。

abstract class Worker{
    public abstract void work();
}

public class MyWorker extends Worker implements Observable {

    private List<Observer> myObservers = new ArrayList<Observer>();
    
    @Override
    public List<Observer> getObservers() {
        return myObservers;
    }

    @Override
    public void work(){
        notify("Started work");

        // Code goes here...

        notify("Completed work");
    }
    
    public static void main(String[] args) {    
        MyWorker w = new MyWorker();
       
        w.addListener(new Observer() {
            @Override
            public void onAction(String a) {
                System.out.println(a + " (" + new Date() + ")");
            }
        });
        
        w.work();
    }
}

ダイヤモンドの問題

Java 8のコンパイラは、クラスが同じシグネチャを持つメソッドを含むインタフェースを実装しているときに発生するダイヤモンドの問題を認識しています。

これを解決するには、実装クラスが共有メソッドをオーバーライドして、独自の実装を提供する必要があります。

interface InterfaceA {
    public default String getName(){
        return "a";
    }
}

interface InterfaceB {
    public default String getName(){
        return "b";
    }
}

public class ImpClass implements InterfaceA, InterfaceB {

    @Override
    public String getName() {    
        //Must provide its own implementation
        return InterfaceA.super.getName() + InterfaceB.super.getName();
    }
    
    public static void main(String[] args) {    
        ImpClass c = new ImpClass();
        
        System.out.println( c.getName() );                   // Prints "ab"
        System.out.println( ((InterfaceA)c).getName() );     // Prints "ab"
        System.out.println( ((InterfaceB)c).getName() );     // Prints "ab"
    }
}

コンパイルされない異なる戻り値の型を持つ同じ名前とパラメータを持つメソッドを持つことには依然として問題があります。

互換性の問題を解決するための既定のメソッドの使用

既定のメソッド実装は、インターフェイスがいくつかのクラスによって使用される既存のシステムのインターフェイスにメソッドが追加された場合に非常に便利です。

システム全体の破壊を避けるために、メソッドをインタフェースに追加するときに、デフォルトのメソッド実装を提供することができます。この方法では、システムはまだコンパイルされ、実際の実装は段階的に実行できます。


詳細については、「 デフォルトメソッド」のトピックを参照してください。

インタフェースの修飾子

Oracle Javaスタイル・ガイドには、

修飾子は暗黙のうちに書き出されるべきではありません。

(コンテキストおよび実際のOracleドキュメントへのリンクは、 Oracle Official Code Standardの 修飾語を参照してください。)

このスタイルガイダンスは、特にインターフェイスに適用されます。次のコードスニペットを考えてみましょう:

interface I {
    public static final int VARIABLE = 0;

    public abstract void method();

    public static void staticMethod() { ... }
    public default void defaultMethod() { ... }
}

変数

すべてのインタフェース変数は、暗黙のpublic (すべてアクセス可能)、 static (インタフェース名でアクセス可能)、 final (宣言時に初期化する必要があります)修飾子を持つ暗黙の定数です。

public static final int VARIABLE = 0;

メソッド

  1. 実装提供しないすべてのメソッドは、暗黙のうちにpublicabstractです。
public abstract void method();
Java SE 8
  1. staticまたはdefault修飾子を持つすべてのメソッドは、実装提供する必要があり、暗黙的にpublicます。
public static void staticMethod() { ... }

上記の変更がすべて適用されたら、次のようになります:

interface I {
    int VARIABLE = 0;
    
    void method();

    static void staticMethod() { ... }
    default void defaultMethod() { ... }
}

有界型パラメータを強化する

制限付きの型パラメーターを使用すると、汎用型の引き数に制限を設定できます。

class SomeClass {

}

class Demo<T extends SomeClass> {

}

しかし、型パラメータは単一のクラス型にしか束縛できません。

インターフェイスタイプは、すでにバインディングを持っているタイプにバインドできます。これは、 &記号を使用して実現されます。

interface SomeInterface {

}

class GenericClass<T extends SomeClass & SomeInterface> {

}

これによりバインドが強化され、複数の型から派生する型引数が必要になる可能性があります。

複数のインターフェイスタイプをタイプパラメータにバインドすることができます。

class Demo<T extends SomeClass & FirstInterface & SecondInterface> {

}

しかし、注意して使用する必要があります。複数のインタフェースバインディングは、通常、 コードの匂いの兆候であり、他のタイプのアダプタとして機能する新しいタイプを作成する必要があることを示唆しています。

interface NewInterface extends FirstInterface, SecondInterface {

}

class Demo<T extends SomeClass & NewInterface> {

}


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow