2015/02/28

[Android]LabeledItem:RadioButtonを簡単に使うためのクラス


Stringについてのうんちくを使用と思ったのですが、書き始めるといろいろ裏を取らなければならないことがあり、そのためにはJavaの言語仕様やバーチャルマシン、Java Byte Codeの仕様まで読み込まなければならないと気が付いて気持ちが萎えています。
ネットに情報を上げている方々ってすごいですね、やっぱり。
一日考えたらいろいろ思いついたので追記しています。
ネットで発言はやっぱり難しい。

前に継承できるStringクラス、AbstractCharSequenceを紹介しました。

実は私は最終的にこのクラスを使用していません。
そのあとすぐに、もう少し便利で潰しが利くクラスを思いついたからです。

public class LabeledItem<T> implements CharSequence{
 private String label;
 private T value;

 public LabeledItem(String label, T value){
  this.label = label;
  this.value = value;
 }

 public T getValue(){
  return value;
 }

 @Override
 public String toString(){
  return label;
 }

 @Override
 public char charAt(int index) {
  return label.charAt(index);
 }

 @Override
 public int length() {
  return label.length();
 }

 @Override
 public CharSequence subSequence(int start, int end) {
  return label.subSequence(start, end);
 }

 public static int indexOf(LabeledItem<?>[]items, Object value){
  for(int i=0; i<items.length; ++i){
   if(value.equals(items[i].getValue())){
    return i;
   }
  }
  return -1;
 }
}

ジェネリッククラスになっていますので、データ型は自由に定義できます。こうするとAbstractCharSequenceを継承せずとも十分拡張性があるので、直接実装しています。
indexOfの第一引数はLabeledItemにしたかったのですが、文法エラーになります。
Tはインスタンスにかかる形容詞なのでstaticメソッドには使えません、とのこと。
型は指定しないがなんらかジェネリッククラス、という意味のにしてエラーは消えました。

ところで、LabeledItemをsetSingleChoiceItemsに適用するにはまだひと手間かかります。
第一引数に与えるためにLabeldItemの配列を作らなければならないのですが、素直に作った下記のクラスはコンパイルエラーとなります。

LabeledItem<Integer>[]items = new LabeledItem<Integer>items(){
 new LabeledItem<Integer>("item1",123),


ジェネリッククラスの配列は作ってはいけない模様。
釈然としないのですが、今は受け入れて回避策を練ってみました。

回避策1:CharSequenceの配列とする

CharSequence[]items = new LabeledItem<Integer>items(){
 new LabeledItem<Integer>("item1",123),

setSingleChoiceItemsの第一引数はCharSequence[]ですから、これで十分です。問題は選択された後、CharSequenceのインスタンスをLabeledItemにキャストしてあげる必要があり、ちょっとカッコ悪く、しかもワーニングになります。

回避策2:LabeledItemの配列とする
LabeledItem<?>[]items = new LabeledItem<Integer>items(){
 new LabeledItem<Integer>("item1",123),

型を不定にすると配列が作れるようになる、というのはやはり釈然としません。ここらへんの仕組みが血肉になっていないからなんだと思います。これもやはり選択された後にキャストが必要です。
なんとなく仕組みがわかってきました。そのうち解説しますが、このやり方はジェネリック文法の穴を突いた感じなのでよろしくないですね。

回避策3:LabeledItem<>を継承したクラスを作る
public class LabeledIntItem extends LabeledItem<Integer> {
 public LabeledIntItem(String label, Integer value) {
  super(label, value);
 }
}

LabeledIInttem[]items = new LabeledIntItem[](){
 new LabeledItem<Integer>("item1",123),


ただ1枚クラスを被せるだけでコンパイルを通ってしまうのもまた不思議です。これならキャストなしで使用できますが、せっかく流用できるように作ったジェネリッククラスをデコレートするクラスを作るというのが本末転倒です。
なんか違う方向に努力しているような気はしています。

回避策4:Object型の配列に格納する。

Object[]items = new LabeledItem<Integer>items(){
 new LabeledItem<Integer>("item1",123),

そもそもJavaの原始デザインにジェネリック型はありませんでした。コンテナクラスにはObject型で格納する、取り出す時にキャストするというのがルールだったのを思い出しました。
回避策1、2のようにワーニングすら出ませんからいっそすがすがしいと思います。

回避策5:コンテナクラスを使用する

  final List<LabeledItem<Integer>> items= new ArrayList<LabeledItem<Integer>>(){{
    new LabeledItem<Integer>("Zero", 0);
    new LabeledItem<Integer>("One", 1);
  }};

  .setSingleChoiceItems(items.toArray(new CharSequence[0]), 0, new OnClickListener(){

いろいろ考えた挙句、現在のJavaではこれが正解なのかな、と行きついた形です。
ジェネリッククラスで配列を作ることはできませんが、コンテナクラスを作ることはできます。これはCから受け継いだ配列という概念の限界を示しているのかもしれません。
上のように宣言すればコード上の扱いやすさは配列と大差ありません。
ポイントは、CharSequenceで扱われるときには正しくCharSequenceの配列として使用するというところでしょう。ここでジェネリッククラスの配列にしようとすると無限ループにはまります。

突っ込み歓迎します。

0 件のコメント:

コメントを投稿