新トップページへ | Tip

アノテーションとかジェネリックの話

LastUpdate : 11/08/06

 仕事の都合上、JDK1.4の世界にずーっといたが、新案件でJDK1.6の世界に投げ出された。私の知識はJDK1.5で止まってる(昔自発的にSJC-PだとかいうのをJDK1.5の時代に取得した。よくやる気起きたなwwwwwwwww)。
ちと、いい機会なので、頭の中の情報を整理しつつ、時流にのってみようかと思う。
乗るしかない、このビックウェーブに!(最近JDK1.7もリリースされたしね!この時点で乗り遅れてんじゃん、私・・・orz

サンプルソースはプログラムとして意味のないものがほとんどです(うまい具合にソースがかけんw)。使い方・雰囲気をだすために、書いてます。。。

☆もくじ

  1. ジェネリクスについて 基本
  2. ジェネリクスについて メソッドのジェネリクス
  3. ジェネリクスを指定する際の?(ワイルドカードについて)
  4. 拡張for文
  5. 標準アノテーションについて
  6. 独自アノテーションについて

ジェネリクスについて 基本

ジェネリクスは・・・、C++でいう関数テンプレートみたいなもんだと勝手に思ってます。
基本的な使用方法は以下のとおりです。

また、ジェネリックとして指定しているインスタンスは、ソース上、Objectクラスの下位クラスとなるらしく、toStringメソッドやgetNameメソッドが使用可能です。

GenericSample0.java
public class GenericSample0<T> {
        
        private T val;
        
        public GenericSample0(T val) {
                this.val = val;
        }
        
        public void showObject() {
                //Tにどのようなクラスが指定されるかわからないが、以下のようにObject型として扱うことができる
                System.out.println("val : "+val.toString());
                System.out.println("class name : "+val.getClass().getName());
        }
}

呼び出し部分
                
//使い方その1(以下のように型を指定します)
GenericSample0<String> cls1 = new GenericSample0<String>("ゆがみねぇな");
cls1.showObject();
System.out.println();

//使い方その2(インターフェイスも指定可能です)
List<String> list = new LinkedList<String>();
list.add("仕方ないね");      list.add("だらしねぇな");     list.add("ゆがみねぇな");
GenericSample0<List<String>> cls2 = new GenericSample0<List<String>>(list);
cls2.showObject();
System.out.println();

//使い方その3(無論クラスも指定可能です)
Date date = new Date();
GenericSample0<Date> cls3 = new GenericSample0<Date>(date);
cls3.showObject();
System.out.println();

実行結果
val : ゆがみねぇな
class name : java.lang.String

val : [仕方ないね, だらしねぇな, ゆがみねぇな]
class name : java.util.LinkedList

val : Sat Aug 06 21:21:50 JST 2011
class name : java.util.Date

ジェネリクスについて メソッドのジェネリクス

クラスのほか、メソッドに対してもジェネリクスを使えます。

GenericSample0_1.java
import java.util.List;

public class GenericSample0_1 {
        /**
         * 引数の型をジェネリクスにしたサンプル
         */
        public <T> void showMsg1(T obj) {
                System.out.println("val : "+obj.toString());
                System.out.println("class name : "+obj.getClass());
        }
        
        /**
         * ジェネリックで指定した型がどのクラスの下位クラスか・・・なども指定可能です。
         * (これはクラスでも、メソッドでも両方可能です)
         */
        public <T1, T2 extends List<T1>> String showMsg2(T2 obj) {
                StringBuilder strBuilder = new StringBuilder();
                for(T1 str : obj) {
                        strBuilder.append(str+" ");
                }
                return strBuilder.toString();
        }
        
}

呼び出し部分
//異なるクラスで、同じメソッドを呼んでみる
GenericSample0_1 smpl = new GenericSample0_1();
List<String> list = new LinkedList<String>();
list.add("仕方ないね");      list.add("だらしねぇな");     list.add("ゆがみねぇな");
smpl.showMsg1(list);
smpl.showMsg1(12);

System.out.println();

//型を指定する際、どのクラスを継承しているか・・・という指定も可能
System.out.println("showMsg2 result : "+smpl.showMsg2(list));
List<Integer> list2 = new ArrayList<Integer>();
list2.add(1);   list2.add(10);  list2.add(100);
System.out.println("showMsg2 result : "+smpl.showMsg2(list2));

実行部分
val : [仕方ないね, だらしねぇな, ゆがみねぇな]
class name : class java.util.LinkedList
val : 12
class name : class java.lang.Integer

showMsg2 result : 仕方ないね だらしねぇな ゆがみねぇな 
showMsg2 result : 1 10 100 

ジェネリクスを指定する際の?(ワイルドカードについて)

型が不明の場合?を指定することができます。
またその?も、どのクラスを継承しているもののみ可能という制限(上限境界をもつワイルドカード)や、〜のクラスまでの下位クラスを設定可能というような制限(下限境界をもつワイルドカード)を設けることが可能です。

Type1.java
public class Type1 {
        
        private String msg;
        
        public Type1(String msg) {
                this.msg = msg;
        }
        
        public String getMsg(){ return msg;     }
}

Type2.java
public class Type2 extends Type1 {
        
        private String msg2;
        public Type2(String msg1, String msg2) {
                super(msg1);
                this.msg2 = msg2;
        }
        
        public String getMsg2() {       return msg2;    }
}

Type3.java
public class Type3 extends Type2 {
        
        private String msg3;
        public Type3(String msg1, String msg2, String msg3) {
                super(msg1, msg2);
                this.msg3 = msg3;
        }
        public String getMsg3() {       return msg3;    }
}

GenericSample1.java
public class GenericSample1 {
        
        /**
         * ?を記述する場合のサンプル
         * 何の型になるか実行してみてからじゃないとわかんない時は?を記述する
         * @param val 文字列
         * @return valにnullを指定するとIntegerのList、valに文字列を指定するとStringのListを返す。
         */
        public List<?> sampleA(String val) {
                List<?> list = null;
                if( val == null ) {
                         List<Integer> tmp = new LinkedList<Integer>();
                         tmp.add(10000000);
                         list = tmp;
                } else {
                        List<String> tmp = new LinkedList<String>();
                        tmp.add(val);
                        list = tmp;
                }
                return list;
        }
        
        
        /**
         * ?をした際、なんでもよいことになってしまうが、それについて制限を付ける方法 その1
         * ?の型は、特定の型の子クラスであること・・・という制限を付ける方法
         */
        public List<? extends Type1> sampleB(boolean flg) {
                List<? extends Type1> list = null;
                if(flg) {
                        List<Type1> tmp = new LinkedList<Type1>();
                        tmp.add(new Type1("a"));        tmp.add(new Type1("b"));        tmp.add(new Type1("c"));
                        list = tmp;
                } else {
                        List<Type3> tmp = new LinkedList<Type3>();
                        tmp.add(new Type3("d", "0", "A"));
                        tmp.add(new Type3("e", "1", "B"));
                        tmp.add(new Type3("f", "2", "C"));
                        list = tmp;
                }
                return list;
        }
        
        /**
         * ?をした際、なんでもよいことになってしまうが、それについて制限を付ける方法 その2
         * 特定のクラスより下位のクラスは不許可とする制限をつける方法
         */
        public List<? super Type2> sampleC() {
                //以下はOK
//              List<Type1> tmp = new LinkedList<Type1>();
//              return tmp;
                
                //以下もOK
                List<Type2> tmp1 = new LinkedList<Type2>();
                tmp1.add(new Type2("a", "0"));  tmp1.add(new Type2("b", "1"));  tmp1.add(new Type2("c", "2"));
                return tmp1;
                
                //以下はNG
//              List<Type3> tmp = new LinkedList<Type3>();
//              return tmp;
        }
}

実行部分
/**
 * ジェネリクスを使う その1
 * ?(ワイルドカード)について
 */
@SuppressWarnings("unchecked")
private void sample1work0() {
        GenericSample1 smpl = new GenericSample1();
        List<String> listA;
        listA = (List<String>)smpl.sampleA("あいうえお");
        for(String str : listA)System.out.println("list(String) : "+str);

        System.out.println();
        
        List<Integer>listB = (List<Integer>)smpl.sampleA(null);
        for(Integer i : listB)System.out.println("list(Integer) : "+i);
                        
}

/**
 * ジェネリクスを使う その2
 * ?(ワイルドカード)を制限
 * 上限境界をもつワイルドカード指定方法(extends)と、下限境界をもつワイルドカード指定方法(super)です。
 */
@SuppressWarnings("unchecked")
private void sample1work1() {
        GenericSample1 smpl = new GenericSample1();
        List<Type1> list1 = (List<Type1>)smpl.sampleB(true);
        for(Type1 t : list1)System.out.println("List<Type1> : "+t.getMsg());
        
        System.out.println();
                        
        List<Type3> list2 = (List<Type3>)smpl.sampleB(false);
        for(Type3 t : list2)System.out.println("List<Type3> : "+t.getMsg() + "/" + t.getMsg2() + "/" + t.getMsg3());
                        
        System.out.println();
        
        List<Type2> list3 = (List<Type2>)smpl.sampleC();
        for(Type2 t : list3)System.out.println("List<Type2> : "+t.getMsg() + "/" + t.getMsg2());
}

sample1work0メソッドの実行結果
list(String) : あいうえお

list(Integer) : 10000000
sample1work1メソッドの実行結果
List<Type1> : a
List<Type1> : b
List<Type1> : c

List<Type3> : d/0/A
List<Type3> : e/1/B
List<Type3> : f/2/C

List<Type2> : a/0
List<Type2> : b/1
List<Type2> : c/2

拡張for文

拡張for文というのが使えるようになったらしい。
ただし、使える対象は限られていて、Iterableインターフェイスを実装したものだけ。使用箇所は微妙なのかも。

IterableSample.java
/**
 * 拡張for文が使える独自クラスのサンプル
 * Iterableの実装をすればいいだけらしい。実装しないといけないのはIteratorを返すメソッドだけ。
 * Iteratorを返してあげることができればよい様子。
 */
public class IterableSample<T> implements Iterable<T> {
        
        private List<T> list;
        
        public IterableSample(List<T> list) {
                this.list = list;
        }
        
        public Iterator<T> iterator() {
                return list.iterator();
        }
}

実行部分
/**
 * 拡張for文の使い方。
 */
private void sample2work0() {
        //Iterableインターフェイスを実装しているクラスなら拡張for文が使えるらしい。
        //LinkedListクラスなんかも、Iterableインターフェイスを実装しているので使ってみる。
        LinkedList<String> list = new LinkedList<String>();
        list.add("a");  list.add("b");  list.add("c");
        
        for(String str : list) {
                System.out.println("list1 : "+str);
        }
        
        
        //また、Iterableを実装すれば独自のクラスでも拡張for文を利用可能
        IterableSample<String> smpl = new IterableSample<String>(list);
        
        for(String str : smpl) {
                System.out.println("list2 : "+str);
        }
        
}

sample2work0の実行結果
list1 : a
list1 : b
list1 : c
list2 : a
list2 : b
list2 : c

標準アノテーションについて

javaが標準で用意しているアノテーションです。
以下のようにして使うらしい。

AnnotationSample0.java
/**
 * 標準アノテーションについて(java側が用意しているもの)
 * 
 * 以下のものを標準アノテーションというらしいよ?
 * Override
 * Deprecated
 * SuppressWarnings
 * これら3つを使ってみる。
 */
public class AnnotationSample0 extends C1 {
        
        /**
         * 上位クラスC1のメソッドをオーバーライドする際、@Overrideをつけます。
         * オーバーライドになってない場合、知らせてくれます。
         * オーバーライドしているつもりがしていなかった・・・という状態を回避できます。
         */
        @Override
        public void showMsg() {
                String msg = "仕方ないね";
                System.out.println("AnnotationSample0#showMsg : "+msg);
        }
        
        /**
         * 非推奨メソッドを表します。
         * eclipseなんかだと、対象のメソッドを記述すると取り消し線が出てきます。
         */
        @Deprecated
        public void showMsg2() {
                System.out.println("これってレスリングじゃない・・・!?");
        }
        
        /**
         * むりくりなキャストなどを行った場合、場合ワーニングがでたりしますが
         * それを出力させなくします。
         * 指定できるオプションはいくつかあります。
         */
        @SuppressWarnings({ "unchecked", "rawtypes" })
        public void showMsg3() {
                List list = new LinkedList();
                list.add("aaaaaa");
        }
}

class C1 {
        public void showMsg() {
                System.out.println("C1#showMsg : "+"夏コミにスティック♂ナンバー見に行こうな");
        }
}

独自アノテーションについて

独自でアノテーションを作成することも可能です。
パッケージやクラス、フィールド、ローカル変数などにアノテーションを設定可能です。
ただし、アノテーションデータは動的に変化するものではない様子。あくまでも静的な設定情報な様子です。

アノテーションの作成方法を以下に示します。
今回の例では、クラスに対してアノテーション情報を設定しています。
以下のサンプルを元に、@Targetの値を変え、アノテーションを設定する場所を変えれば、フィールドなどにも同じ方法で設定できると思います。

以下のように作成する。ほとんど、インターフェイスの作成方法とかわらない・・と思う。
SmplAnn.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 独自アノテーションのサンプル その1
 * 
 * @Targetでどこに着けることができるアノテーションか指定します。
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SmplAnn {
        String val1();
        int val2();
        Class val3();
        SmplEnum val4();
        int[] val5();
}

SmplEnum.java
public enum SmplEnum {
        VAL1, VAL2, VAL3
}

AnnotationSample1.java
@SmplAnn(val1 = "にゃんにゃん", val2 = 100, val3 = Date.class, val4 = SmplEnum.VAL1, val5 = { 1,2,3,4,5 })
public class AnnotationSample1 {

}

実行処理部分
private void sample4work0() {
        //AnnotationSample1からアノテーションで設定した情報を取得する。
        Class cls = AnnotationSample1.class;
        
        Annotation[] ann = cls.getDeclaredAnnotations();
        for(int i=0; i<ann.length; i++) {
                SmplAnn a = (SmplAnn)ann[i];
                System.out.println("val1 : "+a.val1());
                System.out.println("val2 : "+a.val2());
                System.out.println("val3 : "+a.val3());
                System.out.println("val4 : "+a.val4());
                int[] arr = a.val5();
                for(int v : arr)System.out.println("val5 : "+v);
        }
}

sample4work0の実行結果
val1 : にゃんにゃん
val2 : 100
val3 : class java.util.Date
val4 : VAL1
val5 : 1
val5 : 2
val5 : 3
val5 : 4
val5 : 5


■自作するアノテーションに対し、どこへアノテーションを追加可能なのか指定する際の設定可能なもの一覧

@Targetでどこにこのアノテーションをつけることができるのか指定する
複数指定可能。

指定可能なオプション
詳細はこちら参照 → http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/annotation/ElementType.html
指定するオプション意味(指定可能な場所)
ElementType.TYPE
クラス、インタフェース (注釈型を含む)、または emum
ElementType.FIELDフィールド
ElementType.METHODメソッド
ElementType.PARAMETERパラメータ
ElementType.CONSTRUCTORコンストラクタ
ElementType.LOCAL_VARIABLEローカル変数
ElementType.ANNOTATION_TYPE注釈型(アノテーション)
ElementType.PACKAGEパッケージ

複数指定した場合の例 : @Target( { ElementType.FIELD, ElementType.METHHOD } )

■定義したアノテーションに対し、そのアノテーション情報をclassファイルに対して残すか残さないか・・・の指定方法

@Retentionで、指定したアノテーションをclassファイルへ残すか残さないか指定する。

指定可能オプション
詳細はこちら参照 → http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/annotation/RetentionPolicy.html
指定するオプション意味
RetentionPolicy.CLASSclassファイルにはアノテーション情報は記述されるが、実行時、これらの情報は読み取られない(?)
用途がわからんw(バイトコートの静的解析がしたいとき?
RetentionPolicy.RUNTIME普通はこれなはず。これを指定すれば、実行時、アノテーションの情報が取得できる。
RetentionPolicy.SOURCEコンパイラによって注釈が破棄される。
私には用途が思いつかなかった。。。