Javaのインターフェース

Javaインターフェースはクラスとは異なり、Javaプログラムでそれらの特別なプロパティを使用する方法を知ることが重要です。このチュートリアルでは、クラスとインターフェースの違いを紹介し、Javaインターフェースを宣言、実装、および拡張する方法を示す例を紹介します。

また、デフォルトメソッドと静的メソッドが追加されたJava 8、および新しいプライベートメソッドが追加されたJava9でインターフェイスがどのように進化したかについても学習します。これらの追加により、経験豊富な開発者にとってインターフェイスがより便利になります。残念ながら、それらはクラスとインターフェースの間の境界線も曖昧にし、インターフェースプログラミングをJava初心者にとってさらに混乱させます。

ダウンロードコードを取得するこのチュートリアルのサンプルアプリケーションのソースコードをダウンロードします。JavaWorld用にJeffFriesenによって作成されました。

Javaインターフェースとは何ですか?

インターフェイスは、 2つのシステムが満たすポイントと相互作用です。たとえば、自動販売機のインターフェースを使用して、アイテムを選択し、支払いを行い、食べ物や飲み物のアイテムを受け取ることができます。プログラミングの観点からは、インターフェイスはソフトウェアコンポーネント間にあります。メソッドヘッダー(メソッド名、パラメーターリストなど)インターフェイスは、メソッドを呼び出す外部コードと、呼び出しの結果として実行されるメソッド内のコードの間にあると考えてください。次に例を示します。

System.out.println(average(10, 15)); double average(double x, double y) // interface between average(10, 15) call and return (x + y) / 2; { return (x + y) / 2; }

Javaの初心者にとってしばしば混乱するのは、クラスにもインターフェースがあるということです。Java 101:Javaのクラスとオブジェクトで説明したように、インターフェースは、クラスの外部にあるコードからアクセスできるクラスの一部です。クラスのインターフェースは、メソッド、フィールド、コンストラクター、およびその他のエンティティーの組み合わせで構成されています。リスト1を検討してください。

リスト1.Accountクラスとそのインターフェース

class Account { private String name; private long amount; Account(String name, long amount) { this.name = name; setAmount(amount); } void deposit(long amount) { this.amount += amount; } String getName() { return name; } long getAmount() { return amount; } void setAmount(long amount) { this.amount = amount; } }

Account(String name, long amount)コンストラクタとはvoid deposit(long amount)String getName()long getAmount()、およびvoid setAmount(long amount)方法が形成Accountクラスのインターフェイスを:彼らは外部コードにアクセスできます。private String name;そしてprivate long amount;フィールドはアクセスできません。

Javaインターフェースの詳細

Javaプログラムのインターフェースで何ができますか?ジェフのJavaインターフェースの6つの役割の概要を理解してください。

メソッドのインターフェイスをサポートするメソッドのコード、およびクラスのインターフェイスをサポートするクラスのその部分(プライベートフィールドなど)は、メソッドまたはクラスの実装と呼ばれます。実装は、進化する要件を満たすように変更できるように、外部コードから隠す必要があります。

実装が公開されると、ソフトウェアコンポーネント間に相互依存関係が生じる可能性があります。たとえば、メソッドコードが外部変数に依存し、クラスのユーザーが非表示になっているはずのフィールドに依存するようになる場合があります。この結合は、実装を進化させる必要がある場合に問題を引き起こす可能性があります(おそらく公開されたフィールドを削除する必要があります)。

Java開発者は、インターフェース言語機能を使用してクラスインターフェースを抽象化し、クラスをユーザーから切り離します。クラスではなくJavaインターフェイスに焦点を当てることで、ソースコード内のクラス名への参照の数を最小限に抑えることができます。これにより、ソフトウェアが成熟するにつれて、あるクラスから別のクラスへの変更が容易になります(おそらくパフォーマンスを向上させるため)。次に例を示します。

List names = new ArrayList() void print(List names) { // ... }

この例namesでは、文字列名のリストを格納するフィールドを宣言して初期化します。この例print()では、文字列のリストの内容(おそらく1行に1つの文字列)を出力するメソッドも宣言しています。簡潔にするために、メソッドの実装は省略しました。

Listオブジェクトの順次コレクションを記述するJavaインターフェースです。JavaインターフェースのArrayList配列ベースの実装を記述するクラスですListArrayListクラスの新しいインスタンスが取得され、List変数に割り当てられますnames。(ListおよびArrayList標準クラスライブラリのjava.utilパッケージに保存されます。)

山かっことジェネリック

山かっこ(<および>)は、Javaの汎用機能セットの一部です。これらはnames、文字列のリストを説明していることを示します(リストに格納できるのは文字列のみです)。ジェネリックスについては、今後のJava101の記事で紹介します。

クライアントコードがと対話するときnames、によって宣言されList、によって実装されるメソッドを呼び出しArrayListます。クライアントコードはと直接対話しませんArrayList。その結果、などの別の実装クラスLinkedListが必要な場合でも、クライアントコードは壊れません。

List names = new LinkedList() // ... void print(List names) { // ... }

print()メソッドのパラメータタイプはListであるため、このメソッドの実装を変更する必要はありません。ただし、タイプがであった場合は、タイプArrayListをに変更する必要がありLinkedListます。両方のクラスが独自のメソッドを宣言する場合は、print()の実装を大幅に変更する必要があるかもしれません。

Listから切り離してArrayListLinkedListクラス実装の変更の影響を受けないコードを記述できます。Javaインターフェースを使用することにより、実装クラスに依存することから発生する可能性のある問題を回避できます。このデカップリングが、Javaインターフェースを使用する主な理由です。

Javaインターフェースの宣言

ヘッダーとそれに続く本文で構成されるクラスのような構文に準拠することにより、インターフェースを宣言します。少なくとも、ヘッダーはキーワードとinterfaceそれに続くインターフェースを識別する名前で構成されます。ボディはオープンブレースの文字で始まり、クローズブレースで終わります。これらの区切り文字の間には、定数とメソッドヘッダーの宣言があります。

interface identifier { // interface body }

慣例により、インターフェイス名の最初の文字は大文字になり、後続の文字は小文字になります(たとえばDrawable)。名前が複数の単語で構成されている場合、各単語の最初の文字は大文字になります(などDrawableAndFillable)。この命名規則はCamelCasingとして知られています。

リスト2は、という名前のインターフェースを宣言していますDrawable

リスト2.Javaインターフェースの例

interface Drawable { int RED = 1; int GREEN = 2; int BLUE = 3; int BLACK = 4; int WHITE = 5; void draw(int color); }

Javaの標準クラスライブラリのインターフェイス

命名規則として、Javaの標準クラスライブラリの多くのインターフェイスは、有効なサフィックスで終わります。例としてはCallableCloneableComparableFormattableIterableRunnableSerializable、とTransferable。ただし、接尾辞は必須ではありません。標準クラスライブラリは、インタフェースが含まCharSequenceClipboardOwnerCollectionExecutorFutureIteratorListMapおよび他の多くの。

Drawable色定数を識別する5つのフィールドを宣言します。このインターフェイスはdraw()、アウトラインの描画に使用される色を指定するために、これらの定数の1つを使用して呼び出す必要があるメソッドのヘッダーも宣言します。(整数値を渡すことができるため、整数定数を使用することはお勧めできませんdraw()。ただし、簡単な例ではそれらで十分です。)

フィールドとメソッドのヘッダーのデフォルト

インターフェイスで宣言されているフィールドは暗黙的にpublic final staticです。インターフェイスのメソッドヘッダーは暗黙的にpublic abstractです。

Drawable何をするか(何かを描く)を指定するが、それをどのように行うかを指定しない参照型を識別します。実装の詳細は、このインターフェイスを実装するクラスに委託されます。このようなクラスのインスタンスは、自分自身を描画する方法を知っているため、ドローアブルと呼ばれます。

マーカーとタグ付けのインターフェース

本体が空のインターフェースは、マーカーインターフェースまたはタグ付けインターフェースと呼ばれます。インターフェースは、メタデータをクラスに関連付けるためにのみ存在します。たとえばCloneable(Javaでの継承、パート2を参照)は、その実装クラスのインスタンスを浅く複製できることを意味します。ときObjectさんclone()(実行時型情報を経由して)メソッドの呼び出しを検知インスタンスのクラスが実装つまりCloneable、それは浅くオブジェクトのクローン。

Javaインターフェースの実装

クラスは、Javaのimplementsキーワードに続いてインターフェイス名のコンマ区切りリストをクラスヘッダーに追加し、クラス内の各インターフェイスメソッドをコーディングすることにより、インターフェイスを実装します。リスト3は、リスト2のDrawableインターフェースを実装するクラスを示しています。

リスト3.Drawableインターフェースを実装するCircle

class Circle implements Drawable { private double x, y, radius; Circle(double x, double y, double radius) { this.x = x; this.y = y; this.radius = radius; } @Override public void draw(int color) { System.out.println("Circle drawn at (" + x + ", " + y + "), with radius " + radius + ", and color " + color); } double getRadius() { return radius; } double getX() { return x; } double getY() { return y; } }

リスト3のCircleクラスは、中心点と半径として円を記述しています。コンストラクターと適切なゲッターメソッドを提供するだけでなく、ヘッダーに追加し、(アノテーションで示されているように)'sメソッドヘッダーをオーバーライドすることによりCircleDrawableインターフェイスを実装します。implements DrawableCircle@OverrideDrawabledraw()

リスト4は、2番目の例を示していRectangleますDrawable。これも実装するクラスです。

リスト4.RectangleコンテキストでのDrawableインターフェースの実装

class Rectangle implements Drawable { private double x1, y1, x2, y2; Rectangle(double x1, double y1, double x2, double y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } @Override public void draw(int color) { System.out.println("Rectangle drawn with upper-left corner at (" + x1 + ", " + y1 + ") and lower-right corner at (" + x2 + ", " + y2 + "), and color " + color); } double getX1() { return x1; } double getX2() { return x2; } double getY1() { return y1; } double getY2() { return y2; } }

リスト4のRectangleクラスは、長方形を、この形状の左上隅と右下隅を示す1対の点として記述しています。と同様にCircleRectangleコンストラクターと適切なゲッターメソッドを提供し、Drawableインターフェイスも実装します。

インターフェイスメソッドヘッダーのオーバーライド

インターフェイス句abstractを含むimplementsが、インターフェイスのすべてのメソッドヘッダーをオーバーライドしない非クラスをコンパイルしようとすると、コンパイラはエラーを報告します。

An interface type's data values are the objects whose classes implement the interface and whose behaviors are those specified by the interface's method headers. This fact implies that you can assign an object's reference to a variable of the interface type, provided that the object's class implements the interface. Listing 5 demonstrates.

Listing 5. Aliasing Circle and Rectangle objects as Drawables

class Draw { public static void main(String[] args) { Drawable[] drawables = new Drawable[] { new Circle(10, 20, 15), new Circle(30, 20, 10), new Rectangle(5, 8, 8, 9) }; for (int i = 0; i < drawables.length; i++) drawables[i].draw(Drawable.RED); } }

Because Circle and Rectangle implement Drawable, Circle and Rectangle objects have Drawable type in addition to their class types. Therefore, it's legal to store each object's reference in an array of Drawables. A loop iterates over this array, invoking each Drawable object's draw() method to draw a circle or a rectangle.

Assuming that Listing 2 is stored in a Drawable.java source file, which is in the same directory as the Circle.java, Rectangle.java, and Draw.java source files (which respectively store Listing 3, Listing 4, and Listing 5), compile these source files via either of the following command lines:

javac Draw.java javac *.java

Run the Draw application as follows:

java Draw

You should observe the following output:

Circle drawn at (10.0, 20.0), with radius 15.0, and color 1 Circle drawn at (30.0, 20.0), with radius 10.0, and color 1 Rectangle drawn with upper-left corner at (5.0, 8.0) and lower-right corner at (8.0, 9.0), and color 1

Note that you could also generate the same output by specifying the following main() method:

public static void main(String[] args) { Circle c = new Circle(10, 20, 15); c.draw(Drawable.RED); c = new Circle(30, 20, 10); c.draw(Drawable.RED); Rectangle r = new Rectangle(5, 8, 8, 9); r.draw(Drawable.RED); }

ご覧のとおり、各オブジェクトのdraw()メソッドを繰り返し呼び出すのは面倒です。さらに、そうすることで、Drawのクラスファイルに余分なバイトコードが追加されます。CircleRectangleDrawablesと考えることで、配列と単純なループを活用してコードを単純化できます。これは、クラスよりもインターフェイスを優先するようにコードを設計することによる追加の利点です。

注意!