抽象クラスのメリット
abstract class について、いまいちメリットが感じられないという話があったので、勝手に書きます。
まず、Javaのクラスをまとめてみましょう。
interface
こういう違いがあると思います。
んで、「仕様も書けるし、実装を書かなくてもいい」という点に注目してください。
見事に中途半端ですね。
interfaceでもなく、classでもない。でも、メリットはあるんですよ。
つまり、中途半端なクラスが作れるんです。
たとえば、以下のソースを見てください。
みなさん、ソートアルゴリズムに頭を悩ませたこと、結構あるんじゃないですか?
「もうソートはいや!」って人も結構いると思います。
でも、もしも上司にプログラムで「違う型のクラスもソートしろ」って言われたらどうです?
うんざりでしょう?
そもそもいいアルゴリズムのポイントの一つは「このアルゴリズムを他の型やクラスに適応できるかどうか」です。
これはint[]以外に適応できない、ダメな書き方です。
まあ、float、double、byte、char、longあたりはそのままいけそうですし、Integerの配列とかも行けそうです。
でも、Stirngだったり、自分で作った他のクラスだとどうします?
うっかり、アルゴリズムの部分を書き換えてしまう恐れもあります。ありえない話ではないです。
せっかく意味不明なアルゴリズムを完成させたんです。
動いているうちに、もう触らないようにしましょう。
ところで、ソートアルゴリズムに書いてある「昇順」、なぜ降順ではないのでしょうか?
そういう、融通も利きません。
他にも、どうやって比較するかという問題があります。
人間で言うと、背の高い順、年齢順、名前の50音順とかで並ぶことだってありますよね?
たとえば{"10","2","1"}文字列のソートをするとき、二つの方法があります。
この二つのソートをしてみましょう。
単純な方法は二つメソッドを作ってしまうことです。
さらに、なんでもソートできるように欲張ってここではObject[]をソートするメソッドにしましょう。
以下のようになりました。 型と、比較の部分が変わっていることを除けばアルゴリズムは何も変えていません。
これはあきらかにダサいです。
アルリズムの修正があったとき、二回も修正しなくてはいけないなんて。
しかもその逆順とかがあったらさらに二つもメソッドを定義しなくてはなりません。
そうなると当然、メソッドさんからは「比較のとこに文句があるならおめーがやれよ」と言われます。
そうさせていただきましょう。
int compare(Object a,Object b);がちょっと特殊ですね?
何でこうなったかというと、比較を何回も書くのがめんどくさかったんですよ。
比較する項目が多いオブジェクトだと圧倒的に面倒なんです。
たとえば双子の人なんていたら、こんなにもめんどくさい。特に書くのがめんどくさい。
だから、比較はメソッド一個にまとめて、intだけでやり取りをするんです。
大きければ +。
等しければ 0。
小さければ -。
もう、簡単ですね。
さて、よく見てみて下さい。
なんと!ソートアルゴリズムは一個で済みました!
なんと贅沢にも、Comparableはソートだけじゃなく、
しかも、==, !=, <, >, <=, >=まで使えるようになってる!!
今まではinterfaceで、いちいちメソッドにおんなじこと書いて肩がこったり、
classになーんか釈然としないメソッド書いていませんでした?
使わないメソッドは書かなくていいんですよ。
なあなあで中途半端なクラスを書いてもいいんですよ。
しかもなんと、このabstract classならお手入れはcompare()とsortBy()のみ!!
しかも既存のアルゴリズムのお手入れは要りません!
こうなったら使うしかないっしょ!!
みたいな話。
まず、Javaのクラスをまとめてみましょう。
interface
- 仕様は書ける
- 実装は書けない
- いくらでも仕様の継承(implement)できる
- インスタンス化は不可能
- 仕様を書ける
- 実装もかけるし、書かなくてもよい
- 継承は一個だけ
- インスタンス化は不可能
- 仕様を書いたら実装しなくてはならない
- 継承は一回のみ
- インスタンス化は可能
こういう違いがあると思います。
んで、「仕様も書けるし、実装を書かなくてもいい」という点に注目してください。
見事に中途半端ですね。
interfaceでもなく、classでもない。でも、メリットはあるんですよ。
つまり、中途半端なクラスが作れるんです。
たとえば、以下のソースを見てください。
// 何の変哲もない、選択ソート。 // int[]しかソートできない。 public class Sort { public static void sort(int ary[]){ for( int i=0; i<ary.length-1; i++ ) { int max = ary[i]; int maxIndex = i; for( int j=i+1; j<ary.length; j++ ) { if(ary[j] < max ) { //ここに注目 max = ary[j]; maxIndex = j; } } ary[maxIndex] = ary[i]; ary[i] = max; } } public static void main(String[] args) { int[] data = {9,0,1,8,2,7,3,6,4,5,}; sort(data); for( int i=0; i<data.length; i++ ) { System.out.println(data[i] ); } } }
みなさん、ソートアルゴリズムに頭を悩ませたこと、結構あるんじゃないですか?
「もうソートはいや!」って人も結構いると思います。
でも、もしも上司にプログラムで「違う型のクラスもソートしろ」って言われたらどうです?
うんざりでしょう?
そもそもいいアルゴリズムのポイントの一つは「このアルゴリズムを他の型やクラスに適応できるかどうか」です。
これはint[]以外に適応できない、ダメな書き方です。
まあ、float、double、byte、char、longあたりはそのままいけそうですし、Integerの配列とかも行けそうです。
でも、Stirngだったり、自分で作った他のクラスだとどうします?
うっかり、アルゴリズムの部分を書き換えてしまう恐れもあります。ありえない話ではないです。
せっかく意味不明なアルゴリズムを完成させたんです。
動いているうちに、もう触らないようにしましょう。
ところで、ソートアルゴリズムに書いてある「昇順」、なぜ降順ではないのでしょうか?
そういう、融通も利きません。
他にも、どうやって比較するかという問題があります。
人間で言うと、背の高い順、年齢順、名前の50音順とかで並ぶことだってありますよね?
たとえば{"10","2","1"}文字列のソートをするとき、二つの方法があります。
「値順」 => {"1","2","10"} 「辞書順」=> {"1","10","2"}両方が必要な場合、ソートアルゴリズムを二つ書かなければいけませんね。
この二つのソートをしてみましょう。
単純な方法は二つメソッドを作ってしまうことです。
さらに、なんでもソートできるように欲張ってここではObject[]をソートするメソッドにしましょう。
以下のようになりました。 型と、比較の部分が変わっていることを除けばアルゴリズムは何も変えていません。
public static void sortByDictionary(String ary[]){ for( int i=0; i<ary.length-1; i++ ) { String max = ary[i]; int maxIndex = i; for( int j=i+1; j<ary.length; j++ ) { if(((String)a).compareTo((String)b) < 0) { /* 辞書順で比較 */ max = ary[j]; maxIndex = j; } } ary[maxIndex] = ary[i]; ary[i] = max; } } public static void sortByIntValue(String ary[]){ for( int i=0; i<ary.length-1; i++ ) { String max = ary[i]; int maxIndex = i; for( int j=i+1; j<ary.length; j++ ) { /* 数値順で比較 */ if((new Integer((String)a).intValue() - new Integer((String)b).intValue()) < 0){ max = ary[j]; maxIndex = j; } } ary[maxIndex] = ary[i]; ary[i] = max; } }
これはあきらかにダサいです。
アルリズムの修正があったとき、二回も修正しなくてはいけないなんて。
しかもその逆順とかがあったらさらに二つもメソッドを定義しなくてはなりません。
そこで、委譲という概念。
明らかに比較する部分だけがもんだいですよね?そうなると当然、メソッドさんからは「比較のとこに文句があるならおめーがやれよ」と言われます。
そうさせていただきましょう。
// 委譲をするクラス。比較を任せさせてもらう。 // 継承したクラスで二つのメソッドを埋めると、 // (==, <, >, <=, >=)これらの演算子のようなメソッドが使えるようになる abstract class Comparable{ abstract public int compare(Object a,Object b); public boolean equals(Object a,Object b){ // a == b return compare(a,b) == 0; } public boolean notEquals(Object a,Object b){ // a != b return compare(a,b) != 0; } public boolean greater(Object a,Object b){ // a > b return compare(a,b) > 0; } public boolean lesser(Object a,Object b){ // a < b return compare(a,b) < 0; } public boolean greaterOrEqual(Object a,Object b){ // a >= b return compare(a,b) >= 0; } public boolean lesserOrEqual(Object a,Object b){ // a <= b return compare(a,b) <= 0; } abstract public boolean sortBy(Object a,Object b); // ソートアルゴリズムで、どちらが前に来るか? } class CompareByDictionary extends Comparable{ //辞書順でソートするメソッド public int compare(Object a,Object b){ return ((String)a).compareTo((String)b); } public boolean sortBy(Object a,Object b){ return lesser(a,b); } } class CompareByIntValue extends Comparable{ // 数値順でソートするメソッド public int compare(Object a,Object b){ int ia = new Integer((String)a).intValue(); int ib = new Integer((String)b).intValue(); return ia - ib; } public boolean sortBy(Object a,Object b){ return lesser(a,b); } } public class ObjectSort { public static void sort(Object ary[],Comparable compareByAnything){ for( int i=0; i<ary.length-1; i++ ) { Object max = ary[i]; int maxIndex = i; for( int j=i+1; j<ary.length; j++ ) { if(compareByAnything.sortBy(ary[j],max)) { //ここで委譲してる。アルゴリズムは一緒。 max = ary[j]; maxIndex = j; } } ary[maxIndex] = ary[i]; ary[i] = max; } } public static void main(String[] args){ String [] data = {"10","20","19","1","28","2","100","0"}; Comparable comparer; comparer = new CompareByDictionary(); sort(data,comparer); System.out.println("Sort By Dictionary"); for(int i=0; i<data.length; i++){ System.out.println(data[i]); } comparer = new CompareByIntValue(); sort(data,comparer); System.out.println("Sort By Integer Value"); for(int i=0; i<data.length; i++){ System.out.println(data[i]); } } }
int compare(Object a,Object b);がちょっと特殊ですね?
何でこうなったかというと、比較を何回も書くのがめんどくさかったんですよ。
比較する項目が多いオブジェクトだと圧倒的に面倒なんです。
たとえば双子の人なんていたら、こんなにもめんどくさい。特に書くのがめんどくさい。
↓ 住所 ↓ 電話番号 ↓ 誕生日 ↓ 苗字 > 名前 //ここでようやく違いがわかる。とてもじゃないが、何回も書くたくない。
だから、比較はメソッド一個にまとめて、intだけでやり取りをするんです。
大きければ +。
等しければ 0。
小さければ -。
もう、簡単ですね。
さて、よく見てみて下さい。
なんと!ソートアルゴリズムは一個で済みました!
なんと贅沢にも、Comparableはソートだけじゃなく、
しかも、==, !=, <, >, <=, >=まで使えるようになってる!!
今まではinterfaceで、いちいちメソッドにおんなじこと書いて肩がこったり、
classになーんか釈然としないメソッド書いていませんでした?
使わないメソッドは書かなくていいんですよ。
なあなあで中途半端なクラスを書いてもいいんですよ。
しかもなんと、このabstract classならお手入れはcompare()とsortBy()のみ!!
しかも既存のアルゴリズムのお手入れは要りません!
こうなったら使うしかないっしょ!!
みたいな話。
2005年11月01日(火) 23:42:16 Modified by mahalkita