このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。
二つの class を継承することを多重継承と言いますが、 Java7 までは基本的にはできませんでした。 これは Java7 までは、 class 同士は一つの class しか継承できなかったとい う意味です。 Java7 まででも、 interface 同士や、interface を class が実装する場合は、メソッドの実装が 無かったのでシグネチャ(メソッド名、仮引数型、戻り型)が一致する複数の 継承に対しても集約してしまえば問題ありませんでした。
Java7 までの継承において、同一シグネチャを複数継承しても、実装は単一で あることが保証されていました。
class A {
public String get(){
return "A";
}
}
interface B {
String get();
}
interface C {
String get();
}
class D extends A implements B,C {
}
class Ex1 {
public static void main(String[] arg){
D d = new D();
System.out.println(d.get());
}
}
ところが、Java8 では interface にメソッドを書けるようになったので、 同一シグネチャで実装が重複することが起きるようになりました。
interface と class で実装が重複した場合は、class の実装が優先されます。
class A {
public String get(){
return "A";
}
}
interface B {
default String get(){
return "B";
}
}
class C extends A implements B {
}
class Ex2 {
public static void main(String[] arg){
C c = new C();
System.out.println(c.get());
}
}
A
しかし、interface の同士の実装の衝突は問題となります。 次章以降で詳しく考えましょう。
アンドレイ・アレキサンドレスクの「Modern C++ Design」(ピアソン・ エデュケーション)と言う本では多重継承の例として TemporarySecretary (一 時雇いの秘書)というクラスが Temporary と Secretary の二つのクラスから の多重継承で、それぞれの性質を引き継いでいるというような説明がされてい ます(これはもともとは C++ の作者 Stroustrup が多重継承の必要性を説明す るためにした説明からきてます)。 つまり、 「一時雇いの秘書は一時雇いである」と「一時雇いの秘 書は秘書である」の両方共is-a 関係なので、二つのクラスを継承するのは当 然であるということです。 このような関係を実際に作ってみたのが、次の例です。
import java.util.Arrays;
interface Koyo {
String getKoyo();
}
interface IchijiYatoi extends Koyo {
@Override default String getKoyo(){
return "一次雇い";
}
}
interface Jokin extends Koyo {
@Override default String getKoyo(){
return "常勤";
}
}
interface Shigoto{
String getShigoto();
}
interface Hisho extends Shigoto {
@Override default String getShigoto(){
return "秘書";
}
}
interface Kyoin extends Shigoto {
@Override default String getShigoto(){
return "教員";
}
}
interface Shoku extends Koyo, Shigoto {
String shokumei();
}
abstract class AbstractShoku implements Shoku {
@Override public String shokumei(){
return getKoyo()+getShigoto();
}
}
class IchijiYatoiHisho extends AbstractShoku implements IchijiYatoi, Hisho {
}
class JokinHisho extends AbstractShoku implements Jokin, Hisho {
}
class IchijiYatoiKyoin extends AbstractShoku implements IchijiYatoi, Kyoin {
}
class JokinKyoin extends AbstractShoku implements Jokin, Kyoin {
}
class Ex3 {
public static void main(String[] arg){
Shoku[] s = {new IchijiYatoiHisho(), new JokinHisho(),
new IchijiYatoiKyoin(), new JokinKyoin()};
Arrays.stream(s).forEach(x->System.out.println(x.shokumei()));
}
}
さて、では、従来 Java で多重継承が避けられてきた理由を説明します。 これは それは多重継承を許すとさまざまな問題が生じるからです。 その問題の本質は、複数のクラスで同一の名前を使用しているメソッドの実装 があるとき、どの実装を使用するかということです。 以下の例では、実装はそれぞれ同じですが、各メソッドの所属は異なりますの でエラーになります。
interface A {
default String get(){
return "A";
}
}
interface B {
default String get(){
return "A";
}
}
class C implements A, B {
}
class Ex4 {
public static void main(String[] arg){
C c = new C();
System.out.println(c.get());
}
}
これは get メソッドを呼び出すとき、A の get か B の get かが区別がつか
ないからです。
これは次の例のようにオーバライドし、使いたいクラスのメソッドに関して
クラス名.super.メソッド呼び出し
で
解消します。
interface A {
default String get(){
return "A";
}
}
interface B {
default String get(){
return "B";
}
}
class C implements A, B {
@Override public String get(){
return A.super.get()+B.super.get();
}
}
class Ex5 {
public static void main(String[] arg){
C c = new C();
System.out.println(c.get());
}
}
また、菱形継承(ダイアモンド継承)という問題があります。 これは、共通の親クラスをオーバーライドするメソッドが複数ある時に、決定 できないという問題です。 次の例では、全てのメソッドが同一ですが、BとCのどちらの定義を取るか決定できな いとして、エラーになります。
interface A {
default String get(){
return "A";
}
}
interface B extends A {
@Override default String get(){
return "A";
}
}
interface C extends A {
@Override default String get(){
return "A";
}
}
class D implements B,C {
}
class Ex6 {
public static void main(String[] arg){
D d = new D();
System.out.println(d.get());
}
}
Java の interface のメソッドの実装を継承するルールは次のとおりです。
例えば、次のような例はエラーになりません。
以下の例ではB経由では A のメソッドが継承され、 C 経由では C のメソッド が継承されます。 C のメソッドは A のメソッドをオーバーライドしていて、しかも一つだけ存 在するので、結局、 D には A のメソッドをオーバーライドした C のメソッ ドのみが継承されます。
interface A {
default String get(){
return "A";
}
}
interface B extends A {
}
interface C extends A {
default String get(){
return "C";
}
}
class D implements B,C {
}
class Ex3 {
public static void main(String[] arg){
D d = new D();
System.out.println(d.get());
}
}
既に触れている interface を中心に、 Java の interface がどのように使わ れているかを説明します。
凡例
class | interface | |
---|---|---|
変数宣言 | 〇 | 〇 |
インスタンス化 | 〇 | × |
コンストラクタの定義 | 〇 | × |
フィールド変数 | 〇 | × |
class の継承 | extends で一つのみ | × |
interface の継承 | × | extends でいくつでも |
interface の実装 | implements でいくつでも | × |
インスタンスメソッドの定義 | 〇 | 8 |
static メソッドの定義 | 〇 | 8 |
static メソッドの継承 | 〇 | × |
無名クラスの定義 | 〇 | 〇 |
ラムダ式によるインスタンス化 | × | 8 |
また、Java8 からは一つだけabstract なメソッドを含んでいる interface を functionalInterface と呼び、無名クラスによるインスタンス生成がラムダ式 でできるようになりました。
特定の動作をするメソッドやクラスにおいて、Javaのインターフェイスを実装 しているオブジェクトを引数にするものがあります。 これがストラテジデザインパターンです。 インターフェイスの実装方法により、様々な動作をさせることが可能になりま す。 このようなインターフェイスは通常 FunctionalInterface (つまり abstractなメソッドは高々一つ)である ことが多いです。
また、イベントが発生した際に、動作させる機能をオブジェクトとしてイベン ト発生側に与えるのがオブザーバデザインパターンです。 これは動作させるメソッドが abstract な FunctionalInterface を用います。
Comparartor<E> は compare(T,T) メソッドのみが abstract な FunctionalInterface です。 これは java.util.Arrays や java.util.Collections の sort メソッドに 与えたり、 java.util.TreeSet や java.util.TreeMap のコンストラクタに与 えることにより、並び替えのルールを指定することができます。
ActionListener は actionPerformed(ActionEvent) メソッドのみが abstract な FunctionalInterface です。 これは GUI のボタンなどのユーザインタフェースの addActionListener メソッ ドを使ってオブジェクトを与えることにより、 そのユーザインターフェイスを操作した時の動作を指定することができます。
なお、このActionListenerのデザインパターンは、オブジェクトを唯一指定す るのではなく、多数のオブジェクトを登録できる点で、厳密にはオブザーバデザインパター ンやリスナと呼ばれます。
ストラテジデザインパターンやオブザーバーデザインパターンは特定のオブジェ クトにデータの操作を移譲し、そのオブジェクトを他のオブジェクトに渡すこ とで、データと操作を分離します。 このため、作成するオブジェクトは特定のクラスのデータを操作することが必 要となります。 その操作をpublicにしないで、作成するオブジェクトだけが操作可能なように カプセル化するには、オブジェクトの生成をデータの存在するクラスで行う必 要があります。 そのためのテクニックとして、 従来は無名クラスやインナークラスという手法がありましたが、 Java8 から はラムダ式を使うことが出きるようになりました。
ここではprivate変数 data を1増やす「inc」というラベルを持つボタンを生 成する getIncButton メソッドの定義をそれぞれの方法で行って、比較をして みましょう。
Java7 まではインナークラスを推奨していましたが、下記の記述性をみると Java8からはラムダ式の方がプログラムの可読性が良くなったと感じるでしょ う。
class A {
private int data;
public JButton getIncButton(){
JButton button = new JButton("inc");
button.addActionListener(e->data++);
return button;
}
}
class A {
private int data;
public JButton getIncButton(){
JButton button = new JButton("inc");
button.addActionListener(new ActionListener(){
@Override public void actionPerformed(ActionEvent e){
data++;
}
});
return button;
}
}
class A {
private int data;
public JButton getIncButton(){
JButton button = new JButton("inc");
button.addActionListener(new IncActionListener());
return button;
}
class IncActionListener implements ActionListener{
@Override public void actionPerformed(ActionEvent e){
data++;
}
}
}
Java のクラスライブラリでよく使用されている使いかたは、 interface を implements で指定して特定のメソッドを実装し、 そして、作成したオブジェクトを渡してメソッドを呼び出してもらい、目的の 機能を実現するものです。 機能には、整列、 foreach 構文など基本的かつ重要なものが含まれます。 そして、これらの機能は排他的でないので、必要に応じて多重継承を使うこと ができます。
Comparable<E> は compareTo(E) メソッドのみが abstract なインター フェイスです。 これを実装したクラスは比較や整列が可能になります。 java.util.Arrays.sort や java.util.Collections.sort などの整列するメソッ ドでは compareTo メソッドを呼び出します。 また、java.util.TreeSet や java.util.TreeMap の型引数の指定は E extends Comparable<? super E> とされていて、何らかの親クラスが Comparable を実装していることを条件としています。
Iterable インターフェイスは iterator() メソッドが abstarct です。 これを実装したクラスは拡張for文が使えます。
また、Java8 からは stream メソッドが加わったので、 Stream を生成できる ようになります。
interface java.util.Collection は java.lang.Iterable のサブインターフェ イスです。 さらに、この Collection のサブインターフェイスには java.util.List と java.util.Set があります。
java.util.ArrayList や java.util.LinkedList を使う際に、そのクラス特有 のメソッドを必ずしも使わず、Listであれば何でも良いとか、 Collectionで あれば何でも良いという発想で用いる場合は、変数型を List や Collection にしておくことで、オブジェクトクラスの仕様変更に耐えられるようなプログ ラムになります。
なお、java.util.List を実装しているクラスとして、 java.util.AbstractList や java.util.AbstractSet などの抽象クラスがあります。 これらは、実装の際に必要なメソッドのうちのいくつかが実装されている スケルトンと呼ばれるクラスです。 これは一部のメソッドのみをabstractにしておき、そのメソッドを使って実現 できる他のメソッドを実装したものです。 これにより、最低限度数のメソッドの実装で目的のクラスを完成させることが できます。
また、Java8 からは stream に対応したので、 MapReduce などマルチコアに よる計算が可能になりました。
マーカーインターフェイスとはメソッドがない interface です。 これを implement することで、そのクラスをその interface のサブクラスとし、動作を制御させるものです。 このような interface を implements しているかどうかは instanceof 演算子を使ったり、Generics を使うと判断できます。
interface Mark {}
abstract class A {}
class B extends A {}
class C extends A implements Mark {}
class Rei {
private static <E> void trait(E e){
System.out.println("method for unMarked");
}
private static <E extends Mark> void trait(E e){ //implements ではない
System.out.println("method for Marked");
}
public static void main(String[] arg){
A[] x = new A[]{new B(), new C()};
for(A a : x){
System.out.println( a instanceof Mark? "Marked" : "unMarked");
trait(a); //ポリモーフィズムではなく変数型に反応する
}
trait(new C());
}
}
java.util.RandomAccess にはメソッドが宣言されていません。 get(int index) メソッドなどが高速で利用できるクラスにおいて implements します。
java.lang.Cloneable もマーカーインターフェースです。 但し、このマーカーインターフェースが implements されてないクラスにおい て、 java.lang.Object.clone メソッドを super.clone() で呼び出そうとす ると java.lang.CloneNotSupportedException 例外が発生します。
clone メソッドは java.lang.Object において protected で宣言されてます ので、 clone をしたいときは clone() メソッドを実装する必要があります。 実装の中身は super.clone() の結果を返すだけで良いのですが、 clone メソッ ドは CloneNotSupportedException を throws する設定になってますので、 try/catch で囲む必要があります。 なお、 Cloneable インターフェイスを implements したら CloneNotSupportedException 例外は発生しませんから、もし発生したら、それ は何らかの動作異常だと思われます。したがって、 InternalError を発生さ せます。 以下は実装例です。
class A implements Cloneable {
public A clone() {
A a;
try{
a = (A) super.clone();
}catch(CloneNotSupportedException e){
throw new InternalError(e);
}
return a;
}
}
インスタンスをファイルに保存可能にするのが interface
java.io.Serializable というマーカーインターフェースです。
これを implements することで、 java.io.ObjectOutputStream に対して
writeObject でインスタンスを書き込むことができるようになります。
なお、この interface 自体はメソッドを宣言していませんが、
static final long serialVersionUID
という定数が必要になる
ようです。
これらの詳しい説明は必要になったら行います。
複数のテンプレートデザインパターンを持つクラスにおいて、 オーバーライドするメソッドの組み合わせを変えたい場合を考えます。
テンプレートデザインパターンを復習すると、 親クラスで値(主に文字列)を得る abstract なメソッドを定義し、さらに、 その abstract なメソッドを使って文字列などの出力を組み立てるメソッドを 実装します。 すると、その親クラスを継承し、 abstract なメソッドを実装することで、親 クラスで定義した出力用のメソッドの内容の一部を、そのメソッドの実装によっ て差し替えることができるというものです。
さて、前述の例だと、職員に関して、雇用形態に関するメソッドと、 職種に関するメソッドを abstract で定義しておき、 その abstract なメソッドを使用した、出力用のメソッドを作成しておきます。 そして、雇用形態のinterface として常勤と一次雇いを意味するメソッドを実 装した interface を作成し、 さらに、職種に関する interface に関しても、具体的な職種に関するメソッ ドを実装した interface を作成します。 そして、職種クラスを継承し、雇用形態と職種に関するインターフェイスをそ れぞれ実装することで、 abstract メソッドをすべて実装させることができま す。 このようにすると、具体的な実装を interface にまとめて意味づけをするこ とができます。
set メソッドで整数を入れられ、それを get メソッドで取り出せ、さらに clone できるクラスAを定義しなさい。 そして、次のプログラムと結合しなさい。
class Ex1 {
public static void main(String[] arg){
A a1 = new A();
a1.set(1);
A a2 = a1.clone();
a2.set(2);
System.out.println(a1.get());
System.out.println(a2.get());
}
}
1 2
以下のAbstractMoneyメソッドを継承する Dollar1, Yen1, Euro1 クラスが implements で多重継承する Dollar, Yen, Euro インターフェイスを定義しなさい。
import java.util.Arrays;
interface Money {
String prefix();
String postfix();
}
abstract class AbstractMoney implements Money {
private double value;
public AbstractMoney(double value){this.value = value;}
@Override public String toString(){
return prefix()+value+postfix();
}
}
class Dollar1 extends AbstractMoney implements Dollar {
public Dollar1(double value){super(value);}
}
class Yen1 extends AbstractMoney implements Yen {
public Yen1(double value){super(value);}
}
class Euro1 extends AbstractMoney implements Euro {
public Euro1(double value){super(value);}
}
class Ex1 {
public static void main(String[] arg){
Money[] moneyArray = {new Dollar1(1), new Yen1(2), new Euro1(3)};
Arrays.stream(moneyArray).forEach(System.out::println);
}
}
$1.0 2.0円 3.0Euro坂本直志 <sakamoto@c.dendai.ac.jp>