javaチョットデキルへの道〜javaコアsdkに見る真似したいコード10選〜

Post on 21-Jan-2018

4.427 Views

Category:

Engineering

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Javaチョットデキルへの道~JavaコアSDKに見る真似したいコード10選~

JJUG CCC 2017 Spring2017/05/20#jjug_ccc

#ccc_l7Made with

エルナナ

自己紹介

• 株式会社ジャストシステム 福嶋 航• Twitter @fukushiw• Java歴約20年、JavaでWebサービス作っています• #Java100 本ノックの人https://github.com/JustSystems/java-100practices

今日お話しすること

無料でJava力アップ!

JavaコアAPIのソースはまさに宝の山。Java 100本ノック作者がその中から選りすぐりの

10コードブロックについて、どう優れているのかこれから書くコードに是非採用したくなるような、匠の技を

紹介します。

はじめに

JavaコアAPIとは

• JDKをインストールするとついてくる src.zip• Java SE Development Kit 8u131

コメント+空行が約53.6%

7,6501,087,6431,004,582

251,0132,343,238

FilesLines of Code EffectiveLines of Code CommentLines of Code BlankLines of Code Total

本題

初級編1~4

1

NullPointerException

ダメ。ゼッタイ。

NullPointerException ダメ。ゼッタイ。

外からやってくるものは必ず null チェック!

例:java.util.Arrays LL.3998-4007

public static int hashCode(char a[]) { if (a == null) return 0;

int result = 1; for (char element : a) result = 31 * result + element;

return result; }

NullPointerException ダメ。ゼッタイ。

特にインスタンスを生成するときは上記のように明示的にnullチェックをしておく。インスタンスは生成できたが、使うときに(インスタンス生成時のnull渡しが原因で)

NullPointerException が発生、となると原因が追いにくくなる。

例:java.time.LocalDateTime LL.373-377

public static LocalDateTime of(LocalDate date, LocalTime time) { Objects.requireNonNull(date, "date"); Objects.requireNonNull(time, "time"); return new LocalDateTime(date, time); }

2

ガード節

ガード節例:java.lang.Thread LL.325-341

public static void sleep(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); }

if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); }

if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; }

sleep(millis); }

典型的なガード節と例外スロー。その後の処理ロジックで異常な状態を考慮しなくてよいようにす

ることで可読性を向上している。例外スロー時にちゃんとメッセージを設定しているところも注目。

3

例外メッセージは丁寧に

例外メッセージは丁寧に

何がいけないのかをちゃんと説明する

例:java.net.HttpURLConnection LL.237-251

* @throws IllegalStateException if URLConnection is already connected * or if a different streaming mode is already enabled. * * @see #setFixedLengthStreamingMode(int) * @since 1.5 */ public void setChunkedStreamingMode (int chunklen) { if (connected) { throw new IllegalStateException ("Can't set streaming mode: already connected"); } if (fixedContentLength != -1 || fixedContentLengthLong != -1) { throw new IllegalStateException ("Fixed length streaming mode set"); } chunkLength = chunklen <=0? DEFAULT_CHUNK_SIZE : chunklen; }

4

メソッドは短くシンプルに

メソッドは短くシンプルに例:java.util.ArrayList LL.463-480

/** * Inserts the specified element at the specified position in this * list. Shifts the element currently at that position (if any) and * any subsequent elements to the right (adds one to their indices). * * @param index index at which the specified element is to be inserted * @param element element to be inserted * @throws IndexOutOfBoundsException {@inheritDoc} */ public void add(int index, E element) { rangeCheckForAdd(index);

ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }

• コメント半分• 直接関係ないロジックは

サブルーチン化

1

23

45

中級編5~8

5

パフォーマンスコンシャス

パフォーマンスコンシャス例:java.lang.String LL.2066-2091

public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; /* avoid getfield opcode */

while (++i < len) { if (val[i] == oldChar) { break; } } if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(buf, true); } } return this; }

1. oldChar != newChar で変える必要性を確認

2. ループの中でインスタンス変数にアクセスしない

3. わざわざ oldChar の出現箇所を探している※もし出現しない場合は i == len となるので new char[len] も new String() もしない。

4. 3のために途中までしたループを無駄にしない

• 極力newしない• 極力インスタンス変数にアクセスしない

パフォーマンスコンシャス(拡大1) public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; /* avoid getfield opcode */

1. oldChar != newChar で変える必要性を確認2. ループの中でインスタンス変数にアクセスしない

パフォーマンスコンシャス(拡大2) while (++i < len) { if (val[i] == oldChar) { break; } } if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; }

3. わざわざ oldChar の出現箇所を探している※もし出現しない場合は i == len となるので new char[len] も new String() もしない。

4. 3のために途中までしたループを無駄にしない

• 極力newしない• 極力インスタンス変数にアクセスしない

6CloneNotSupportedException

の処理

CloneNotSupportedExceptionの処理

CloneNotSupportedException は実装次第では絶対に起きない。(そもそもなぜ checked exception なのか不明)

ここではその例外をキャッチ、起きえないことをコメントで示し、InternalError をスローすることで対処。

→ assert ができる前の苦肉の策と思われる。他に同様のクラスあり。

例:java.util.Vector LL.672-681

try { @SuppressWarnings("unchecked") Vector<E> v = (Vector<E>) super.clone(); v.elementData = Arrays.copyOf(elementData, elementCount); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); }

ちなみにhttp://bugs.java.com/bugdatabase/view_bug.do?bug_id=4220218

JDK-4220218 : Please make CloneNotSupportedException uncheckedResolution : Won't Fix

7

デザインパターンの適用

デザインパターンの適用

↑Factory Methodパターン

その他GoF23パターンがどのコアAPIで使われているか、↓このまとめが秀逸http://stackoverflow.com/questions/1673841/examples-of-gof-

design-patterns-in-javas-core-libraries#answer-2707195

例:java.util.Calendar LL.313, 1611-1614 ※説明の便宜上折り返しを追加

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {

public static Calendar getInstance() { return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT)); }

8サブクラスで

スーパークラスに定義された型を

狭める

サブクラスでスーパークラスに定義された型を狭める

UncheckedIOExceptionでは cause に IOException しか取らないように制限している。 Serializable なので、デシリアライズで変なものが注入されないよう

に、readObject()メソッドでちゃんとガードしている。同様のガード例として、

java.time.chrono.JapaneseChronology#readObject()メソッドは常に InvalidObjectException をスローするようになっている、など。

例:java.io.UncheckedIOException LL.82-89 private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); Throwable cause = super.getCause(); if (!(cause instanceof IOException)) throw new InvalidObjectException("Cause must be an IOException"); }

上級編9~10

9

立つ鳥跡を濁さず

立つ鳥跡を濁さず例:java.util.Timer LL.103-117

/** * This object causes the timer's task execution thread to exit * gracefully when there are no live references to the Timer object and no * tasks in the timer queue. It is used in preference to a finalizer on * Timer as such a finalizer would be susceptible to a subclass's * finalizer forgetting to call it. */ private final Object threadReaper = new Object() { protected void finalize() throws Throwable { synchronized(queue) { thread.newTasksMayBeScheduled = false; queue.notify(); // In case queue is empty. } } }; コアAPIの中でも数少ない finalize() のオーバーライド。この

threadReaperはどこからも参照されていないので、このfinalize()が呼ばれるのはこのTimerインスタンスがお掃除されるとき。このときにちゃ

んと(中で起動している)スレッドを終了させるための仕掛け。

10

final変数への代入

final変数への代入 ※黒魔術例:java.math.BigInteger LL.4395-4465 private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { : // Commit final fields via Unsafe UnsafeHolder.putSign(this, sign); : }

// Support for resetting final fields while deserializing private static class UnsafeHolder { private static final sun.misc.Unsafe unsafe; : static void putSign(BigInteger bi, int sign) { unsafe.putIntVolatile(bi, signumOffset, sign); } : } よい子は真似してはいけません!

最後に

お願い

いいものを見つけたら、 Twitter: @fukushiwまで何卒ご一報下さい!

top related