20070329 java programing tips

39
Javaオブジェクト指向プログラミングTIPS

Upload: shingo-furuyama

Post on 06-May-2015

962 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: 20070329 Java Programing Tips

Javaオブジェクト指向プログラミングTIPS

Page 2: 20070329 Java Programing Tips

目標

プログラミング勘を取り戻す 更なる向上も目指す 品質にかかわる基礎的なトピックスを理解する(等値性・コールバック)

フレームワークやサーバーサイドの技術的基礎となっているトピックスを理解する(リフレクション・マルチスレッド)

おまけ(シリアライゼーション)

Page 3: 20070329 Java Programing Tips

等値性の実装

Page 4: 20070329 Java Programing Tips

等値性の実装

String str1 = new String(“hoge”);String str2 = “hoge”;

System.out.println(str1 == str2);System.out.println(str1.equals(str2));

出力:FalseTrue

Page 5: 20070329 Java Programing Tips

str1 == str2 :                str1.equals(str2) : の比較

str2オブジェクトstr1オブジェクトchar[] value

==とequalsメソッド

str1

str2

str1オブジェクト

str2オブジェクト

char[] value

int length

boolean equals(Object obj)

char[] value

int length

boolean equals(Object obj)

スタック ヒープ

Page 6: 20070329 Java Programing Tips

java.lang.Stringの中身public final class String implements java.io.Serializable, Comparable<String>, CharSequence{ private final char value[]; private final int count; public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = count; if (n == anotherString.count) { char v1[] = value; char v2[] = anotherString.value; int i = offset; int j = anotherString.offset; while (n-- != 0) { if (v1[i++] != v2[j++]) return false; } return true; } } return false; }

Page 7: 20070329 Java Programing Tips

equalsメソッドをどうオーバーライドするか要求: 普通預金口座と定期預金口座を提供している銀行がある。顧客に総資産残高を提供するために、いずれか一方の口座番号からもう一方の口座番号を検索したい。

仕様: 口座番号上4桁を顧客コードとし、顧客コードが一致していれば口座種別にかかわらずTrueを返す。また、顧客コードが一致していなければ口座種別にかかわらずFalseを返す。方針: 抽象基底クラスAccountでequalsメソッドを実装する。普通預金口座と定期預金口座はAccountクラスを継承し、同じequalsメソッドによって上記仕様を満たすようにする。

Page 8: 20070329 Java Programing Tips

口座の上位クラスの実装

public abstract class Account{ private char[] custmorCode = new char[4]; Account(char[] custmorCode){ this.custmorCode = custmorCode; } public char[] getCustmorCode(){ return this.custmorCode; } @Override public boolean equals(Object obj){ //ここをどう実装するか? }}

Page 9: 20070329 Java Programing Tips

テストコード

SavingAccount a1 = new SavingAccount(new char[]{‘0’,’1’,’2’,’3’});SavingAccount a2 = new SavingAccount(new char[]{‘9’,’1’,’2’,’3’});

TimeDepositAccount a3 = new TimeDepositAccount(new char[]{‘0’,’1’,’2’,’3’});TimeDepositAccount a4 = new TimeDepositAccount(new char[]{‘9’,’1’,’2’,’3’});

System.out.println(a1.equals(a3));System.out.println(a2.equals(a4));System.out.println(a1.equals(a2));System.out.println(a2.equals(a3));

期待する出力:TrueTrueFalseFalse

Page 10: 20070329 Java Programing Tips

getClassメソッドとinstanceof演算子

boolean equals(Object obj)

引数がObjectクラスなのでダウンキャストしなければならない

ClassCastExceptionを発生させないために検証が必要

instanceof演算子・オブジェクトが同じクラスから生成されている→ true・右辺が左辺の派生クラスから生成されている→ true・その他 → false

Class getClass()メソッド・オブジェクトが同じクラスから生成されている→ true・その他→ false

Page 11: 20070329 Java Programing Tips

実装public abstract class Account{ //省略 @Override public boolean equals(Object obj){ if(obj instanceof Account){ //if(getClass() == obj.getClass()){ char[] compareCode = ((Account)obj).getCustmorCode(); if(custmorCode.length != compareCode.length){ return false; } for(int i = 0; i<custmorCode.length; i++){ if(custmorCode[i] != compareCode[i]){ return false; } } return true; } else { return false; } }}

Page 12: 20070329 Java Programing Tips

実行

Page 13: 20070329 Java Programing Tips

まとめ

equalsメソッドはクラスによって実装が異なる。java.lang.Objectは参照を比較するがStringは隠蔽しているchar[] valueを比較した結果を返す。

equalsメソッドを実装する際に、Instanceof演算子を使うかgetClassメソッドを使うかでequalsメソッドの挙動は変わってくるので注意する。

何を持って等しいとするかの定義があいまいだとバグが出やすい。自分がわかっていても使う人がわからないと意味がないので注意する。

Page 14: 20070329 Java Programing Tips

コールバック

Page 15: 20070329 Java Programing Tips

CASE:口座残高通知システム

要求: 顧客と顧客の債権者に、口座残高が一定額以下になった場合通知を出したい。

Page 16: 20070329 Java Programing Tips

ポーリングループによる実装

仕様: 口座を監視するInspectorクラスを作り、口座残高が一定額以下になっているか検証する。

方針: Inspectorクラスを独立したスレッドにし、100msごとに口座残高をチェックする

Page 17: 20070329 Java Programing Tips

ポーリングループによる実装

仕様: 口座を監視するInspectorクラスを作り、口座残高が一定額以下になっているか検証する。

方針: Inspectorクラスを独立したスレッドにし、100msごとに口座残高をチェックする

作業効率が著しく悪い

Page 18: 20070329 Java Programing Tips

ポーリングループ(マルチスレッド)のイメージ

100msスレッドを止める 100msスレッドを止める 100msスレッドを止める 100msスレッドを止める 100msスレッドを止める・・・ スレッドはCPUを浪費し続ける

AccountInspector

Page 19: 20070329 Java Programing Tips

コールバックによる実装

仕様: 口座を監視するInspectorクラスを作り、このクラスが口座から引き出しが行われたときに一定額以下になっているか検証する。

方針: 口座クラスの引き出しメソッドの実装を変更する。Inspectorクラスは口座クラスに登録し、引き出しメソッドが呼ばれると同時にInspectorに通知する。

Page 20: 20070329 Java Programing Tips

コールバックによる実装

事前に通知対象をセットする(addInspectorメソッド) 引き出しがあったときのみInspectorに通知

public class Account{ private byte[] lock = new byte[0]; private ArrayList<Inspector> targets = new ArrayList<Inspector>(); public int get(int amount){ synchronized(lock){ if(this.amount < amount){ return 0; } for(Inspector i : targets){ i.inspect(); } this.amount = this.amount - amount; return amount; } } public void addInspector(Inspector insp){ this.targets.add(insp); }

Page 21: 20070329 Java Programing Tips

まとめ

何らかの処理を待つ(オブジェクトが適当な状態になるまで待機する)処理は、ポーリングループとコールバックによって実装できる。

ポーリングループによる実装はCPUを浪費する。多分使わないほうがいい。

コールバックの実装は、通知すべきオブジェクトを設定し、状態が変化したときに通知を行う形式。CPUは浪費しない。設計上の見通しもよい(と思うよ。)

Page 22: 20070329 Java Programing Tips

リフレクション

Page 23: 20070329 Java Programing Tips

Stringによるインスタンスの生成public class Main{ public static void main(String[] args){ String className = "RefrectionTest"; RefrectionTest rt = null; try{ rt = (RefrectionTest)Class .forName(className).newInstance(); } catch(Exception e) { } rt.dosth(); }}

public class RefrectionTest{ public void dosth(){ System.out.println("Do Something"); }}

Page 24: 20070329 Java Programing Tips

Stringによるメソッドの実行

public class Main{ public static void main(String[] args){ String className = "RefrectionTest"; RefrectionTest rt = null; try{ rt = (RefrectionTest)Class.forName(className).newInstance(); } catch(Exception e) { } String methodName = "dosth"; Method method = null; try{ method = (Method)Class.forName(className) .getMethod(methodName,null); } catch(Exception e) { } method.invoke(Class.forName(className).newInstance(),null); }}

Page 25: 20070329 Java Programing Tips

シリアライゼージョン

Page 26: 20070329 Java Programing Tips

シリアライゼージョンimport java.io.*;public class Main{ public static void main(String[] args){ Account a1 = new Account("hoge",5000); a1.get(100); try{ ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("hoge.ser")); oos.writeObject(a1); oos.close(); } catch(Exception e) {}

System.out.println(a1); Account ser = null; try{ ObjectInputStream ois = new ObjectInputStream( new FileInputStream("hoge.ser")); ser = (Account)ois.readObject(); ois.close(); } catch(Exception e) {}

ser.get(100); System.out.println(ser); }}

public class Account implements Serializable{ //省略 }

Page 27: 20070329 Java Programing Tips

シリアライズされたファイルの中身

Page 28: 20070329 Java Programing Tips

マルチスレッド

Page 29: 20070329 Java Programing Tips

スレッド2口座残高を照会して100円追加

スレッド1口座残高を照会して100円追加

マルチスレッドの問題点

プライベートワーキングメモリ(スレッドに割り当てられるメモリ)と主メモリ内容の不一致

スレッド1が残高を変更しても、スレッド2のプライベートワーキングメモリは変更されないため口座の追加処理が正常に行われない

メインメモリ

残高0

残高0

100円に変更

100円に変更

プライベートワーキングメモリ プライベートワーキングメモリ

Page 30: 20070329 Java Programing Tips

対策① synchronized

Synchonized:対象オブジェクトをモニタリングする。モニタリングしているオブジェクトがほかのスレッドで占有されている場合は処理待ちになる。

public class Account{ private int amount; private byte[] lock = new byte[0]; Account(int amount){ this.amount = amount; } public int get(int amount){ synchronized(lock){ this.amount = this.amount - amount; return amount; } } public void put(int amount){ synchronized(lock){ this.amount = this.amount + amount; } }}

Page 31: 20070329 Java Programing Tips

スレッド2口座残高を照会して100円追加

スレッド1口座残高を照会して100円追加

対策① synchronized

スレッド2がスレッド1の残高更新前にメインメモリを照会してもブロックされているためアクセスできない

メインメモリ

残高0

100円に変更

残高0

200円に変更

プライベートワーキングメモリ プライベートワーキングメモリ

Page 32: 20070329 Java Programing Tips

対策② volatile

プライベートワーキングメモリと主メモリの内容が常に一致するように保ちつづける

読み込みについてのみ有効、書き込みはsyncronizedさせないと不一致発生 基本的にsysnronizedより高速だが変数へのアクセスが頻繁に行われる場合メモリ間の一致処理にオーバーヘッドのため一概には言えなくなる

public class Account{ private volatile int amount; Account(int amount){ this.amount = amount; } public int getAmount(){ return this.amount; }}

Page 33: 20070329 Java Programing Tips

スレッド2口座残高を照会する

スレッド1口座残高を照会して100円追加

対策②  volatile

スレッド1の残高更新後にメインメモリとプライベートワーキングメモリの内容を一致させるためエラーが起きない

厳密にはスレッド2での変数使用時にメインメモリを必ず読み込むためバグにならない

メインメモリ

残高0

残高0

100円に変更

100円で読込

プライベートワーキングメモリ プライベートワーキングメモリ

Page 34: 20070329 Java Programing Tips

対策③ Java.util.concurrentimport java.util.concurrent.atomic.*;

public class Account{ private AtomicInteger amount; Account(int amount){ this.amount = new AtomicInteger(amount); } public int get(int amount){ if(this.amount.get() < amount){ return 0; } do{ if(this.amount.get() < amount){ break; } } while(!this.amount.compareAndSet(this.amount.get(), this.amount.get() - amount)); return amount; } public void put(int amount){ do{ if(amount < this.amount.get()){ break; } } while(!this.amount.compareAndSet(this.amount.get(), this.amount.get() + amount)); } public int getAmount(){ return amount.get(); }}

Page 35: 20070329 Java Programing Tips

AtomicInteger

CAS実装を一部抜粋 CASによる変数アクセスの実装 Int型をクラスでラッピングしてデータへのアクセスをCASで実装

public class AtomicInteger extends Number implements java.io.Serializable { private volatile int value;public AtomicInteger(int initialValue) { value = initialValue;}public final int get() { return value;}public final void set(int newValue) { value = newValue;}public final int getAndSet(int newValue) { for (;;) { int current = get(); if (compareAndSet(current, newValue)) return current; }}public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}

}

Page 36: 20070329 Java Programing Tips

CAS Compare And Swap

プロセッサ上のアトミックな命令

読込時点と書込時点の値が一致していれば書込みを実行する

スレッド2の残高書込時点で、プライベートワーキングメモリにある残高0円とCASで取得した値(メインメモリの100円)が一致していないため、再度値の書込みを試みる

スレッド2口座残高を照会する

スレッド1口座残高を照会して100円追加

メインメモリ

残高0

残高0

100円に変更

CAS失敗再度読み込み値の更新

プライベートワーキングメモリ プライベートワーキングメモリ

Page 37: 20070329 Java Programing Tips

まとめ

synchonizeは対象オブジェクトをロックする。同じロックを複数スレッドが持つことができないようになっている。

volatileは読込の際にメインメモリとプライベートワーキングメモリを一致させる。

JDK5.0で追加されたjava.util.concurrentでマルチスレッドがサポートされている。Javaを使うときにプロセッサレベルで高速なプログラムを書くにはこのパッケージを使う。

Page 38: 20070329 Java Programing Tips

おわりに

Page 39: 20070329 Java Programing Tips

特に参考にした本です