2013年7月10日水曜日

if -else を教えてる時に・・。

早速ですが、このコードの間違いを探してください。
(答えは本文で)





とある機会で、初歩からJavaプログラミングを教える機会があって、
前でプロジェクターで映しながら、こうやったらこうできますよみたいなハンズオンで説明してたんです。


文の最後には「セミコロンが要りますよ」とか基本的なことから教えて、

四則演算や%も教えました。


次に
if文を教えようと、奇数偶数を判定するようなコードを書いて、前に写して、
受講者の方に「こんなコード書いてみてください」と言って書いてもらったのですが、


そこ、受講者の方から
「奇数の時も偶数ですと出るのですが・・・」と質問されました。


そこで受講者の方が書いたのは、最初に載せたコードです。


私はパッと見た時に、すぐに気づきませんでした。。










わかりやすいようにフォーマッタをかけたのは下のコードです。






else のあとのセミコロンがあるために、elseの中身が空文として扱われてしまうんですね。

そして そのあとの{}が空ブロックとして扱われてif-elseと関係なく実行されてしまうんですね。


今まで長くプログラミングをして来ましたが、
こういうコードが書けるのというが新しい発見でした。
(使いドコロは無さそうですが)

やっぱり、プログラミングを教えたりすると、たまにこういう発見をするのが嬉しいですね。


豆知識として是非。




2013年2月9日土曜日

[Java][Android]Arrays.asListについて

Javaには
Arrays.asList()という配列をリストに変換するメソッドがあります。

仕様上の問題で、配列ではなくリストで渡さないといけないというのが
たまーにあるので重要という程でもないですが、知っていると便利なメソッドです。



こういうコードを書いてみました。

これを実行すると、asList.remove(0)で例外が発生します。



実は、Arrays.asList()で取得したリストは不変(addもremoveもできない)という仕様があります。
(ドキュメントにもちゃんと書いてあります)

たまに忘れます...


ではこのArrays.asList()で取得したリストとは何なのでしょうか。



少し調査してみました。(Java6を対象にしています)


Eclipse でasListの部分をクリックして 宣言を開く (F3) をしてみます。
ArraysクラスのasList の宣言が見れます。


そうするとあれ??
ArrayListに引数のものを入れて返してるだけじゃないですか。

つまりasList()で取得したものはArrayListということになり、可変(addもremove)も出来るはず。


しかしここに落とし穴があり。

 
さらにこのArrayListのところで F3を押して ArrayListの宣言を見てみます。





(一部分のみ)


気づきにくいですが、これは Arraysクラスの中の内部クラスとして宣言されているものなのです。

我々がよく使うArrayListとは別物なのです。

ちなみにadd メソッドは ここでは宣言されてなくて extends のAbstractListを辿って
add(E element)を辿り

add(int index, E element) 
の宣言部分

を見るとこのようになっております。
中身が例外だけ・・・・。(゚д゚)!

とりあえずこの仕組みによって例外が投げられているのですね。



ちなみに、通常のArrayListを調べると 同じくextends AbstractList されていますが、

きちんとオーバーライドでaddの中身が実装されています。


ちなみに
Arraysの中のArrayList(T[] array)という宣言は 通常のArrayListのコンストラクタにはありません。


なので配列の要素を全部渡すときは これがエレガントな方法です。


(2013/11/30 追加  yozaさんthx)




今回主にJavaに関してのことでしたが Androidアプリでも同様です
(そもそもきっかけはこちら)

ListViewの表示でよく使う ArrayAdapterで なぜかremove()をするとなぜか失敗していたので調べた結果たどり着いたのが

ArrayAdapterのコンストラクタのこの部分。
Arrays.asList()が使われてます。


つまりこれで作成されたArrayAdapterの内部で使われるリストは不変になってしまいます。

JavaDocの説明に これで渡すと不変であると書いてないじゃないですか。。。orz  
(テストしてなかったこちらも悪いですけど)

まとめ

Arrays.asList で取得したリストは不変です。
宣言の中身を見ると、同名のArrayListが使われてます。(ややこしい)

不変の理由は、addやremoveの中身が無い実装のAbstractListのため

AndroidのArrayAdapterのコンストラクタで配列を渡すと不変リストになってしまうので注意です。重要


2012年10月31日水曜日

Nexus 7でのAppWidgetのサイズ

Nexus 7 
(Resolution 800*1025,DPI 195dpi*201dpi ( tv dpi ) )

でのAppWidgetを設置する時、
xmlで記述する
<appwidget-provider>の属性 minWidthやminHeightで設定するDPIとその設置後のセルの大きさが以下のガイドラインに書かれてるものは違ったのでメモします。
App Widget Design Guidelines
Determining a size for your widget



結果だけ書くと、上記のページの表の表記に従うと

Portrait (縦長使用時)
n Columns  =  100*n- 120 dp(minWidth
n Rows      =    70*n- 30 dp(minHeight ) 表のまま


Landscape(横長使用時)
n Columns  =    70*n- 30 dp  (minWidth ) 表のまま
n Rows      =    87.9*n- 104 dp(minHeight ) 



という謎な感じになってました。

dpi とAppWidgetの関係について理解しきれてないところがあるかも知れませんが、
とりあえずメモ代わりに。


また、いろいろと面倒になりそうですね。。

2012年8月4日土曜日

ただの願望。。

Androidアプリって、開発者側から古いバージョンを使ってるユーザーへ
の強制アップデートのオプションできないかなぁ。。

特にWebのAPIを使ってデータ取得するようなアプリ作ってると、過去への互換性や安定性
が正直しんどい。


毎回起動時に自分でサーバに最新バージョンを取得して、更新を促すっていうのが普通なんだろうけど、実装が面倒だなっていうだけ。
(本来はしないといけないんだろうけど)


確かに、そういうオプションがあると、
最初は普通のアプリに見せかけて、悪質なアプリ化されてしまうよなこともできてしまうけど。。。

うーむ。


2012年7月26日木曜日

AsyncTaskを継承してるクラスでのstaticメソッド呼び出しの罠


(ソースコード見難いと思います・・すいません)

(API レベル4以上で再現する現象・・のはず)


staticメソッドを持った AsyncTaskを継承したクラス(ExtendsAsyncTask)を作成。 


public class ExtendsAsyncTask extends AsyncTask<Void, Void, Void> {
public static void method() {
}
@Override
protected Void doInBackground(Void... params) {
return null;
}
}
上のクラスの staticメソッドに別スレッドでアクセスすると
 public class TestActivity extends Activity{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
Runnable r = new Runnable() { @Override
public void run() {
ExtendsAsyncTask.method();
}
};
new Thread(r).start();
}
}
なんと ExtendsAsyncTask.method(); の呼び出しで
 java.lang.ExceptionInInitializerError が発生してアプリが落ちてしまいます。


ちょっと原因をさぐってみました。

公開されてるAndroidのソースコード(今回は4.0.4)のAsyncTaskをみて


607行目
private static class InternalHandler extends Handler {
・・・
}
さらに

190行目
 private static final InternalHandler sHandler = new InternalHandler();
が怪しいと判断 (判断するまでに至った理由は省略)

ちなみに static フィールドはClassがロードされた時に実行される (参考:static イニシャライザ)

ここでさきほどのExtendsAsyncTaskを以下のように変更
public class ExtendsAsyncTask {
public static void method() {
} 
private static InternalHandler handler = new InternalHandler();
public static class InternalHandler extends Handler {
@Override
public void handleMessage(Message msg) {
}
}
}
これでもおなじく



 java.lang.ExceptionInInitializerError が発生しました。

さらにコンパクトに下記の様にしても同様です。

public class ExtendsAsyncTask {
public static void method() {
}
private static Handler handler = new Handler();
}

つまりHandler 周りが怪しい?
( Handler さんには昔、かなりお世話になっておりました。。)



そして次にActivity自体を以下のようにしてみます。
public class TestActivity extends Activity
{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
Runnable r = new Runnable() {
@Override
public void run() {
new Handler();
}
};
new Thread(r).start();
}
}
 java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

という例外が発生します。


ほほう。。

ちょっとググったところ、以下のページがとても参考になったので掲載しておきます。

つまり内部動作を理解すると
各スレッドで Looper.prepare() を呼び出す前にnew Hander()を呼び出すな ということです。


(この記事の発展にもあるように UIスレッド場合は、フレームワーク側が呼び出している)

つまり
public class TestActivity extends Activity
{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
Runnable r = new Runnable() {
@Override
public void run() {
Looper.prepare();
new Handler();
}
};
new Thread(r).start();
}
}
こうすると落ちなくなります。

つまり、
public class TestActivity extends Activity
{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
Runnable r = new Runnable() {
@Override
public void run() {
 Looper.prepare();
ExtendsAsyncTask.method();
}
};
new Thread(r).start();
}
} 
元のコードはこうしても落ちなくなります。


そして、注目したいのは
public class TestActivity extends Activity
{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
new ExtendsAsyncTask();
Runnable r = new Runnable() {
@Override
public void run() {
ExtendsAsyncTask.method();
}
};
new Thread(r).start();
}
}
というように UIスレッド上でExtendsAsyncTaskのインスタンスを生成すると、
アプリが落ちることはありません。

これらが(Looper.prepare()をされたあとの)UIスレッド上で実行されるためです。
private static final InternalHandler sHandler = new InternalHandler();
private static class InternalHandler extends Handler {
・・・
}

筆者の場合

ExtendsAsyncTask.method();を呼び出す前に

ほとんどUIスレッド上でnew ExtendsAsyncTask();
でインスタンスを生成していたのですが、

ある処理の時、ExtendsAsyncTaskのインスタンスを一度も生成せずに
ExtendsAsyncTaskをクラスをロードせずに)
直接 ExtendsAsyncTask.method();を別スレッドで実行していたことが判明し、
問題が発覚し、原因を追求しました。


他の状態に依存しない public static メソッドは
どこで定義しても大丈夫だろうという思い込みがあったのは
確かです。


まぁAndroidのバグではないと思いますが、つまづきやすい仕様というか 
なんというかという感じですが。

何かの参考になっていただければ幸いです。


まとめ

AsyncTaskを継承してるクラスのpublic staticメソッド呼び出しはUIスレッド
で行いましょう。

(というまとめでいいのかわかりませんが?)

私の設計が悪いのでは・・ということは、言ってはいけません