Java Language
インターフェイス
サーチ…
前書き
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
およびDog
はgetSound()
メソッドを定義する必要があります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
注意:
- インタフェースで宣言されたすべての変数は
public static final
- インターフェイスのメソッドで宣言されたすべてのメソッドがされている
public abstract
(この文は唯一のJava 8からのJava 7を介して有効である、あなたは抽象的である必要はないインタフェースのメソッドを、持つことが許可されている。このような方法は、次のように知られているデフォルトの方法 ) - インタフェースを
final
として宣言することはできません - 複数のインタフェースが同一のシグネチャを持つメソッドを宣言している場合、実質的に1つのメソッドとして扱われ、どのインタフェースメソッドが実装されているかを区別できません
- 対応する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 Animal
をimplements 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());
}
配列の順序はDog
、 Cat
、 Bird
の順であるため、 「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;
メソッド
- 実装を提供しないすべてのメソッドは、暗黙のうちに
public
でabstract
です。
public abstract void method();
-
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> {
}