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
配列ベースの実装を記述するクラスですList
。ArrayList
クラスの新しいインスタンスが取得され、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
から切り離してArrayList
、LinkedList
クラス実装の変更の影響を受けないコードを記述できます。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の標準クラスライブラリの多くのインターフェイスは、有効なサフィックスで終わります。例としてはCallable
、Cloneable
、Comparable
、Formattable
、Iterable
、Runnable
、Serializable
、とTransferable
。ただし、接尾辞は必須ではありません。標準クラスライブラリは、インタフェースが含まCharSequence
、ClipboardOwner
、Collection
、Executor
、Future
、Iterator
、List
、Map
および他の多くの。
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メソッドヘッダーをオーバーライドすることによりCircle
、Drawable
インターフェイスを実装します。implements Drawable
Circle
@Override
Drawable
draw()
リスト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対の点として記述しています。と同様にCircle
、Rectangle
コンストラクターと適切なゲッターメソッドを提供し、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 Drawable
s. 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
のクラスファイルに余分なバイトコードが追加されます。Circle
とRectangle
をDrawable
sと考えることで、配列と単純なループを活用してコードを単純化できます。これは、クラスよりもインターフェイスを優先するようにコードを設計することによる追加の利点です。