web-dev-qa-db-ja.com

正しいメソッドを持っているだけでなく、インターフェイスにはもっと多くのものがありますか

だから私はこのインターフェイスを持っているとしましょう:

public interface IBox
{
   public void setSize(int size);
   public int getSize();
   public int getArea();
  //...and so on
}

そして、それを実装するクラスがあります:

public class Rectangle implements IBox
{
   private int size;
   //Methods here
}

インターフェイスIBoxを使用したい場合、実際にはその方法でインスタンスを作成できません。

public static void main(String args[])
{
    Ibox myBox=new Ibox();
}

右?だから私は実際にこれをしなければなりません:

public static void main(String args[])
{
    Rectangle myBox=new Rectangle();
}

それが当てはまる場合、インターフェースの唯一の目的は、インターフェースを実装するクラスが、インターフェースによって記述された正しいメソッドを持っていることを確認することですか?または、インターフェイスの他の使用はありますか?

159
Click Upvote

インターフェイスは、コードをより柔軟にする方法です。あなたがすることはこれです:

Ibox myBox=new Rectangle();

その後、後で別の種類のボックスを使用することを決定した場合(別の種類のライブラリがあり、より良い種類のボックスを使用している場合)、コードを次のように切り替えます。

Ibox myBox=new OtherKindOfBox();

それに慣れると、それが素晴らしい(実際に不可欠な)作業方法であることがわかります。

別の理由は、たとえば、ボックスのリストを作成し、各ボックスに対して何らかの操作を実行したいが、リストにさまざまな種類のボックスを含める場合です。各ボックスで次のことができます。

myBox.close()

(IBoxにはclose()メソッドがあると仮定します)myBoxの実際のクラスは、どのボックスを繰り返しているかによって変わりますが。

143
morgancodes

私が読んだ多くの用途の1つは、Javaで複数の継承を使用するインターフェースなしでは難しい場合です。

class Animal
{
void walk() { } 
....
.... //other methods and finally
void chew() { } //concentrate on this
} 

今、次の場合を想像してください:

class Reptile extends Animal 
{ 
//reptile specific code here
} //not a problem here

しかし、

class Bird extends Animal
{
...... //other Bird specific code
} //now Birds cannot chew so this would a problem in the sense Bird classes can also call chew() method which is unwanted

より良いデザインは次のとおりです。

class Animal
{
void walk() { } 
....
.... //other methods 
} 

動物にはchew()メソッドがありませんが、代わりに次のようにインターフェースに配置されます:

interface Chewable {
void chew();
}

そして、爬虫類クラスに鳥ではなくこれを実装させます(鳥は噛むことができないため):

class Reptile extends Animal implements Chewable { } 

そして、単に鳥の場合:

class Bird extends Animal { }
118
peevesy

インターフェースの目的は、多態性、別名型置換です。たとえば、次のメソッドが与えられた場合:

public void scale(IBox b, int i) {
   b.setSize(b.getSize() * i);
}

scaleメソッドを呼び出すとき、IBoxインターフェイスを実装するタイプの値を提供できます。言い換えると、RectangleSquareの両方がIBoxを実装している場合、Rectangleが期待される場所であれば、SquareまたはIBoxのいずれかを指定できます。 。

46
Apocalisp

インターフェイスを使用すると、静的に型指定された言語でポリモーフィズムをサポートできます。オブジェクト指向の純粋主義者は、言語が完全な機能を備えたオブジェクト指向言語であるためには、継承、カプセル化、モジュール性、および多態性を提供する必要があると主張します。動的に型付けされた(またはダック型にされた)言語(Smalltalkなど)では、多態性はささいなものです。ただし、静的に型付けされた言語(JavaやC#など)では、多態性はささいなものではありません(実際、表面的には、強い型付けの概念と対立しているようです)。

デモさせてください:

動的に型付けされた(またはダック型の)言語(Smalltalkなど)では、すべての変数がオブジェクトへの参照です(これ以上でもそれ以上でもありません)。したがって、Smalltalkでは、次のことができます。

|anAnimal|    
anAnimal := Pig new.
anAnimal makeNoise.

anAnimal := Cow new.
anAnimal makeNoise.

そのコード:

  1. AnAnimalと呼ばれるローカル変数を宣言します(変数のTYPEを指定しないことに注意してください-すべての変数はオブジェクトへの参照であり、それ以上でもそれ以下でもありません)。
  2. 「Pig」という名前のクラスの新しいインスタンスを作成します
  3. Pigの新しいインスタンスを変数anAnimalに割り当てます。
  4. メッセージmakeNoiseを豚に送信します。
  5. 牛を使用してすべてを繰り返しますが、Pigとまったく同じ変数に割り当てます。

同じJavaコードは次のようになります(DuckとCowはAnimalのサブクラスであると仮定して:

Animal anAnimal = new Pig();
duck.makeNoise();

anAnimal = new Cow();
cow.makeNoise();

クラスの野菜を紹介するまでは、これで十分です。野菜には動物と同じ行動がありますが、すべてではありません。たとえば、動物と野菜の両方が成長する可能性がありますが、明らかに野菜は音を立てず、動物は収穫できません。

Smalltalkでは、これを書くことができます:

|aFarmObject|
aFarmObject := Cow new.
aFarmObject grow.
aFarmObject makeNoise.

aFarmObject := Corn new.
aFarmObject grow.
aFarmObject harvest.

これはアヒル型であるため、Smalltalkで完全に機能します(アヒルのように歩き、アヒルのように鳴く場合-それはアヒルです)。この場合、メッセージがオブジェクトに送信されると、ルックアップが実行されます。受信者のメソッドリスト。一致するメソッドが見つかった場合は呼び出されます。そうでない場合は、何らかの種類のNoSuchMethodError例外がスローされますが、すべて実行時に行われます。

しかし、静的に型付けされた言語であるJavaでは、変数にどの型を割り当てることができますか?トウモロコシは成長をサポートするために野菜から継承する必要がありますが、ノイズを発生しないため、動物からは継承できません。牛はmakeNoiseをサポートするためにAnimalを継承する必要がありますが、vegetableを実装してはならないため、Vegetableから継承することはできません。 多重継承-複数のクラスから継承する機能が必要なようです。しかし、それはポップアップするすべてのEdgeケースのためにかなり難しい言語機能であることがわかります(複数の並列スーパークラスが同じメソッドを実装するとどうなりますか?など)

インターフェースに沿って...

それぞれがGrowableを実装して動物と野菜のクラスを作成すると、牛は動物で、トウモロコシは野菜であると宣言できます。また、動物と野菜の両方が成長可能であると宣言することもできます。これにより、すべてを成長させるためにこれを記述できます。

List<Growable> list = new ArrayList<Growable>();
list.add(new Cow());
list.add(new Corn());
list.add(new Pig());

for(Growable g : list) {
   g.grow();
}

そして、動物の鳴き声を出すためにこれを行うことができます:

List<Animal> list = new ArrayList<Animal>();
list.add(new Cow());
list.add(new Pig());
for(Animal a : list) {
  a.makeNoise();
}

アヒル型言語の利点は、本当に素晴らしいポリモーフィズムが得られることです。動作を提供するためにクラスが行う必要があるのは、メソッドを提供することだけです。誰もがナイスをプレイし、定義されたメソッドに一致するメッセージのみを送信する限り、すべてが良好です。欠点は、以下の種類のエラーが実行時までキャッチされないことです:

|aFarmObject|
aFarmObject := Corn new.
aFarmObject makeNoise. // No compiler error - not checked until runtime.

静的型付け言語は、コンパイル時に以下の2種類のエラーをキャッチするため、はるかに優れた「契約によるプログラミング」を提供します。

// Compiler error: Corn cannot be cast to Animal.
Animal farmObject = new Corn();  
farmObject makeNoise();

-

// Compiler error: Animal doesn't have the harvest message.
Animal farmObject = new Cow();
farmObject.harvest(); 

だから...要約すると:

  1. インターフェースの実装により、オブジェクトが実行できる種類(相互作用)を指定でき、クラスの継承により、処理方法(実装)を指定できます。

  2. インターフェイスは、コンパイラの型チェックを犠牲にすることなく、「真の」ポリモーフィズムの多くの利点を提供します。

33
Jared

通常、インターフェイスは、使用するインターフェイスを定義します(名前のとおり;-))。サンプル


public void foo(List l) {
   ... do something
}

これで、関数fooArrayLists、LinkedLists、... 1つのタイプだけを受け入れます。

Javaで最も重要なことは、複数のインターフェースを実装できますが、1つのクラスしか拡張できないことです!サンプル:


class Test extends Foo implements Comparable, Serializable, Formattable {
...
}

class Test extends Foo, Bar, Buz {
...
}

上記のコードはIBox myBox = new Rectangle();にもなります。重要なことは、myBoxにはIBoxのメソッド/フィールドのみが含まれ、Rectangleの他のメソッド(存在する可能性がある)は含まれないことです。

9
Johannes Weiss

インターフェースが行うすべてを理解していると思いますが、インターフェースが役立つ状況をまだ想像していません。

オブジェクトのインスタンス化、使用、リリースをすべて狭いスコープ内(たとえば、1つのメソッド呼び出し内)で行う場合、インターフェイスは実際には何も追加しません。あなたが述べたように、具象クラスは知られています。

インターフェイスが役立つのは、オブジェクトを1か所で作成し、実装の詳細を気にしない呼び出し元に返す必要がある場合です。 IBoxの例をShapeに変更しましょう。これで、Rectangle、Circle、TriangleなどのShapeの実装が可能になりました。getArea()およびgetSize()メソッドの実装は、具体的なクラスごとに完全に異なります。

これで、渡されたパラメーターに応じて適切なShapeを返すさまざまなcreateShape(params)メソッドでファクトリーを使用できます。明らかに、ファクトリーはどのタイプのShapeが作成されているかを認識しますが、呼び出し元は円か、正方形かなどに注意します。

ここで、図形に対して実行する必要があるさまざまな操作があることを想像してください。領域ごとに並べ替え、すべてを新しいサイズに設定してから、UIに表示する必要があるかもしれません。シェイプはすべてファクトリーによって作成され、ソーター、サイザー、ディスプレイクラスに非常に簡単に渡すことができます。将来、六角形クラスを追加する必要がある場合は、ファクトリー以外を変更する必要はありません。インターフェイスがなければ、別のシェイプを追加するのは非常に面倒なプロセスになります。

6
Ickster

あなたができる

Ibox myBox = new Rectangle();

そうすれば、このオブジェクトをIboxとして使用し、本当にRectangleであることを気にしません。

6
IAdapter

なぜインターフェイス???????

それは犬から始まります。特に、pug

パグにはさまざまな動作があります。

public class Pug { 
private String name;
public Pug(String n) { name = n; } 
public String getName() { return name; }  
public String bark() { return  "Arf!"; } 
public boolean hasCurlyTail() { return true; } }

そして、あなたはラブラドールを持っています。ラブラドールも行動のセットを持っています。

public class Lab { 
private String name; 
public Lab(String n) { name = n; } 
public String getName() { return name; } 
public String bark() { return "Woof!"; } 
public boolean hasCurlyTail() { return false; } }

いくつかのパグとラボを作成できます。

Pug pug = new Pug("Spot"); 
Lab lab = new Lab("Fido");

そして、それらの動作を呼び出すことができます。

pug.bark() -> "Arf!" 
lab.bark() -> "Woof!" 
pug.hasCurlyTail() -> true 
lab.hasCurlyTail() -> false 
pug.getName() -> "Spot"

犬小屋を経営していて、飼っている犬をすべて追跡する必要があるとしましょう。 I パグとラブラドールを別々の配列に保存する必要があります

public class Kennel { 
Pug[] pugs = new Pug[10]; 
Lab[] labs = new Lab[10];  
public void addPug(Pug p) { ... } 
public void addLab(Lab l) { ... } 
public void printDogs() { // Display names of all the dogs } }

しかし、これは明らかに最適ではありません。 いくつかのプードルを収容したいの場合、犬小屋の定義を変更してプードルの配列を追加する必要があります。実際、犬の種類ごとに別々の配列が必要です。

洞察力:パグとラブラドール(およびプードル)はどちらも犬の一種であり、同じ行動をとっています。つまり、(この例の目的のために)すべての犬がbarえ、名前を持ち、巻き毛の尾を持つ場合と持たない場合があります。インターフェイスを使用して、すべての犬ができることを定義できますが、特定の種類の犬に任せて、特定の行動を実装できます。インターフェイスには「ここにすべての犬ができることがある」とありますが、それぞれの行動がどのように行われるかは言われていません。

public interface Dog 
{
public String bark(); 
public String getName(); 
public boolean hasCurlyTail(); }

次に、PugクラスとLabクラスを少し変更して、Dogの動作を実装します。パグは犬であり、ラボは犬であると言えます。

public class Pug implements Dog {
// the rest is the same as before } 

public class Lab implements Dog { 
// the rest is the same as before 
}

以前と同じようにPugs and Labsをインスタンス化できますが、今では新しい方法も取得できます。

Dog d1 = new Pug("Spot"); 
Dog d2 = new Lab("Fido");

これは、d1が犬であるだけでなく、具体的にはパグであると言います。また、d2はDog、特にLabです。ビヘイビアを呼び出すことができ、以前と同様に機能します。

d1.bark() -> "Arf!" 
d2.bark() -> "Woof!" 
d1.hasCurlyTail() -> true 
d2.hasCurlyTail() -> false 
d1.getName() -> "Spot"

余分な作業がすべて報われる場所です。 Kennelクラスはよりシンプルになります。 1つの配列と1つのaddDogメソッドのみが必要です。どちらも犬である任意のオブジェクトで動作します。つまり、Dogインターフェースを実装するオブジェクトです。

public class Kennel {
Dog[] dogs = new Dog[20]; 
public void addDog(Dog d) { ... } 
public void printDogs() {
// Display names of all the dogs } }

使用方法は次のとおりです。

Kennel k = new Kennel(); 
Dog d1 = new Pug("Spot"); 
Dog d2 = new Lab("Fido"); 
k.addDog(d1); 
k.addDog(d2); 
k.printDogs();

最後のステートメントは次のように表示されます:Spot Fido

インターフェイスを使用すると、インターフェイスを実装するすべてのクラスが共通して共有する一連の動作を指定できます。その結果、変数やコレクション(配列など)を定義できます。これらは、それらが保持する特定のオブジェクトの種類を事前に知る必要はなく、インターフェースを実装するオブジェクトを保持するだけです。

これが Factory Patterns およびその他の作成パターンがJavaで非常に人気がある理由です。それらがなければ、Javaはインスタンス化を簡単に抽象化するためのすぐに使えるメカニズムを提供しません。それでも、)メソッドでオブジェクトを作成しない場所(どこでもコードの大部分)で抽象化されます。

余談ですが、一般的には、インターフェイスの命名に「IRealname」メカニズムを使用しないことをお勧めします。これはWindows/COMであり、ハンガリーの表記法に片足を踏み込んで本当に必要なわけではありません(Javaは既に強く型付けされており、インターフェースを持つことの全体的な目的は、可能な限りクラス型とほとんど区別できないことです)。

3

後日existingクラスを取得し、IBoxを実装すると、すべてのボックス対応コードで使用可能になることを忘れないでください。

インターフェイスの名前が-ableの場合、これは少し明確になります。例えば.

public interface Saveable {
....

public interface Printable {
....

など(命名スキームは常に機能するとは限りません。たとえば、Boxableがここで適切かどうかわかりません)

3
Brian Agnew

インターフェースの使用方法の好例は、コレクションフレームワークにあります。 Listをとる関数を作成する場合、ユーザーがVectorまたはArrayListまたはHashListなどを渡しても構いません。そして、そのListCollectionまたはIterableインターフェイスを必要とする関数に渡すこともできます。

これにより、Listの実装方法に関係なく、Collections.sort(List list)などの機能が可能になります。

3
Kip

インターフェースの唯一の目的は、インターフェースを実装するクラスが、インターフェースによって記述された正しいメソッドを持っていることを確認することですか?または、インターフェイスの他の使用はありますか?

Java 8バージョンで導入されたインターフェースの新機能で答えを更新しています。

インターフェイスの概要 のOracleドキュメントページから:

インターフェイス宣言にはを含めることができます

  1. メソッド署名
  2. デフォルトの方法
  3. 静的メソッド
  4. 定数の定義。

実装を持つ唯一のメソッドは、デフォルトおよび静的メソッドです。

インターフェースの使用

  1. contractを定義するには
  2. 関係のないクラスを機能にリンクするには(たとえば、Serializableインターフェイスを実装するクラスは、そのインターフェイスを実装することを除いて、それらの間にリレーションを持っている場合と持っていない場合があります)
  3. interchangeable実装を提供するために 例:戦略パターン
  4. デフォルトのメソッドにより、ライブラリのインターフェイスに新しい機能を追加し、それらのインターフェイスの古いバージョン用に記述されたコードとのバイナリ互換性を確保できます
  5. 静的メソッドを使用してライブラリ内にヘルパーメソッドを編成します(別のクラスではなく、同じインターフェイスのインターフェイスに固有の静的メソッドを保持できます)

abstract classinterfaceの違いに関するいくつかの関連するSEの質問と、使用例の使用例:

インターフェイスと抽象クラスの違いは何ですか?

インターフェイスクラスと抽象クラスの違いをどのように説明したらよいですか?

Java 8に追加された新機能を理解するには、 documentation ページをご覧ください:default methods and static methods

3
Ravindra babu

インターフェイスの目的はabstraction、または実装からの分離です。

プログラムに抽象化を導入する場合、可能な実装については気にしません。興味があるのはwhatそれができるかどうかhow、そしてinterfaceを使用して、これをJavaで表現します。

2
eljenso

CardboardBoxとHtmlBox(どちらもIBoxを実装)がある場合、IBoxを受け入れる任意のメソッドに両方を渡すことができます。それらは非常に異なっており、完全に交換可能ではありませんが、「開く」または「サイズ変更」を気にしないメソッドは引き続きクラスを使用できます(おそらく、画面に何かを表示するのに必要なピクセル数を気にするためです)。

1
Todd R

多重継承を可能にするためにJavaに機能が追加されたインターフェース。 Javaの開発者は、多重継承を持つことは「危険な」機能であると認識しました。そのため、インターフェイスのアイデアを思いつきました。

次のようなクラスがあるため、多重継承は危険です。


class Box{
    public int getSize(){
       return 0;
    }
    public int getArea(){
       return 1;
    }

}

class Triangle{
    public int getSize(){
       return 1;
    }
    public int getArea(){
       return 0;
    }

}

class FunckyFigure extends Box, Triable{
   // we do not implement the methods we will used the inherited ones
}

これは、使用するときに呼び出すメソッドです


   FunckyFigure.GetArea(); 

すべての問題はインターフェイスで解決されます。インターフェイスを拡張できること、クラス化メソッドがないことを知っているからです。もちろん、コンパイラはニースであり、メソッドを実装していないかどうかを教えてくれますが、より興味深いアイデアの副作用。

1
mandel

ここに、インターフェイスの利点についての私の理解があります。間違っている場合は修正してください。 OSを開発しており、他のチームが一部のデバイスのドライバーを開発していると想像してください。そこで、StorageDeviceインターフェイスを開発しました。他の開発チームが提供する2つの実装(FDDとHDD)があります。

次に、StorageDeviceインターフェイスを実装したクラスのインスタンスを渡すだけで、saveDataなどのインターフェイスメソッドを呼び出すことができるOperatingSystemクラスがあります。

ここでの利点は、インターフェイスの実装を気にしないことです。もう一方のチームは、StorageDeviceインターフェースを実装することで仕事をします。

package mypack;

interface StorageDevice {
    void saveData (String data);
}


class FDD implements StorageDevice {
    public void saveData (String data) {
        System.out.println("Save to floppy drive! Data: "+data);
    }
}

class HDD implements StorageDevice {
    public void saveData (String data) {
        System.out.println("Save to hard disk drive! Data: "+data);
    }
}

class OperatingSystem {
    public String name;
    StorageDevice[] devices;
    public OperatingSystem(String name, StorageDevice[] devices) {

        this.name = name;
        this.devices = devices.clone();

        System.out.println("Running OS " + this.name);
        System.out.println("List with storage devices available:");
        for (StorageDevice s: devices) {
            System.out.println(s);
        }

    }

    public void saveSomeDataToStorageDevice (StorageDevice storage, String data) {
        storage.saveData(data);
    }
}

public class Main {

    public static void main(String[] args) {

        StorageDevice fdd0 = new FDD();
        StorageDevice hdd0 = new HDD();     
        StorageDevice[] devs = {fdd0, hdd0};        
        OperatingSystem os = new OperatingSystem("Linux", devs);
        os.saveSomeDataToStorageDevice(fdd0, "blah, blah, blah...");    
    }
}
0