Java
Puzzlers
櫻庭 祐一
寺田 佳央
Dedicated to John
Carpenter
We are Click and Hack the Type-It brothers !!
コード中に発生する勘違い
Java プログラミングにおける
奇妙な振る舞いをする小さなプログラム
複数の選択肢から、何が表示される?
ミステリーの解明
問題の解決方法
教訓
Java Puzzlers のルール
public class JavaPuzzlers {
public static void main(String... args) {
System.out.println(“Japan Java User
Group Presents!”);
}
}
1.Japan Java User Group Presents!
2.Java Puzzlers
3.0xCAFEBABE
4.その他
1問目
問題1:They Live
public class TheyLive {
public static void main(String... args) {
int sum = 0;
for (int i = Integer.MIN_VALUE;
i < Integer.MAX_VALUE; i++) {
if (i != 0) sum += i / Math.abs(i);
}
System.out.println(sum);
}
}
選択肢1:They Live
選択肢
1. -1
2. 0
3. 1
4. その他
public class TheyLive {
public static void main(String... args) {
int sum = 0;
for (int i = Integer.MIN_VALUE;
i < Integer.MAX_VALUE; i++) {
if (i != 0) sum += i / Math.abs(i);
}
System.out.println(sum);
}
}
正解は ?!
解答1:They Live
選択肢
1. -1
2. 0
3. 1
4. その他
public class TheyLive {
public static void main(String... args) {
int sum = 0;
for (int i = Integer.MIN_VALUE;
i < Integer.MAX_VALUE; i++) {
if (i != 0) sum += i / Math.abs(i);
}
System.out.println(sum);
}
}
解説1:何が悪いのか?
-2147483648/abs(-2147483648) = -1
-2147483647/abs(-2147483648) = -1
2147483646/abs(2147483646) = +1+)
……
-2
解説1:何が悪いのか?
-2147483648/abs(-2147483648) = +1
-2147483647/abs(-2147483648) = -1
2147483646/abs(2147483646) = +1+)
……
0
解決1:どうやって直すのか
public class TheyLive {
public static void main(String... args) {
int sum = -1;
for (int i = Integer.MIN_VALUE+1;
i < Integer.MAX_VALUE; i++) {
if (i != 0) sum += i / Math.abs(i);
}
System.out.println(sum);
}
}
教訓1:
• 意図しない動作をするメソッドが存在する
• 使用時には必ずJavadocをチェックしよう
• メソッドを作成する場合には、
想像しやすい動作になるよう心がけよう
2問目
問題2:MarshmallowMan
public class MarshmallowMan{
public static void main(String... args) {
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += foo(i);
}
System.out.println(sum);
}
public static long foo(long l) {return l;}}
選択肢2:MarshmallowMan
public class MarshmallowMan {
public static void main(String... args) {
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += foo(i);
}
System.out.println(sum);
}
public static long foo(long l) {return l;}}
選択肢
1. 0
2. 45
3. 55
4. コンパイルエラー
正解は ?!
選択肢2:MarshmallowMan
public class Sum {
public static void main(String... args) {
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += foo(i);
}
System.out.println(sum);
}
public static long foo(long l) {return l;}}
選択肢
1. 0
2. 45
3. 55
4. コンパイルエラー
解説2:
sum = sum + data コンパイルエラー
sum += data コンパイルエラーにはならない
int sum = 0;
long data = 10;
System.out.println(sum += data);
この問題と同じ
解説2:
JLS §15.26.2. Compound Assignment Operators
E1 op= E2
E1 = (T) ((E1) op (E2)) ※ T は E1 の型
つまり
sum = (int) (sum + data)
https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.26.2
解決2:Java 8 以降での計算処理
IntStream.rangeClosed(1, 10).sum()
桁があふれた際に ArithmeticException を出力
java.lang.StrictMath#addExact
java.lang.Math#addExact
java.util.concurrent.atomic.LongAdder
java.util.concurrent.atomic.DoubleAdder
複数スレッドからの更新で Atomic 性を保つ場合
AtomicLong よりおすすめ
教訓2:
• 演算子の振る舞いについて正しく理解しましょう
• 何が最適な解決方法か考えましょう
3問目
問題3:The Thing
public class TheThing {
public static void main(String... args) {
StringBuilder builder
= new StringBuilder("J");
builder = builder.append("a");
Stream.of("v", "a")
.forEach(builder::append);
System.out.println(builder);
}
}
選択肢3:The Thing
public class TheThing {
public static void main(String... args) {
StringBuilder builder
= new StringBuilder("J");
builder = builder.append("a");
Stream.of("v", "a")
.forEach(builder::append);
System.out.println(builder);
}
}
選択肢
1. Ja
2. Java
3. Compile Error
4. Exception
正解は ?!
選択肢3:The Thing
public class TheThing {
public static void main(String... args) {
StringBuilder builder
= new StringBuilder("J");
builder = builder.append("a");
Stream.of("v", "a")
.forEach(builder::append);
System.out.println(builder);
}
}
選択肢
1. Ja
2. Java
3. Compile Error
4. Exception
解説3:何が悪いのか?
ラムダ式:
final 以外のローカル変数にアクセスできない
StringBuilder builder = new StringBuilder("J");
builder = builder.append("a");
// NG
Stream.of("v", "a")
.forEach(c -> builder.append(c));
解説3:何が悪いのか?
ラムダ式:
final 以外のローカル変数にアクセスできない
// Effectively Final
StringBuilder builder = new StringBuilder("Ja");
// OK
Stream.of("v", "a")
.forEach(c -> builder.append(c));
解説3:何が悪いのか?
メソッド参照:
finalでないローカル変数にアクセスできるい
StringBuilder builder = new StringBuilder("J");
builder = builder.append("a"); // Not-Final
// OK
Stream.of("v", "a")
.forEach(builder::append);
教訓3:
• ラムダ式とメソッド参照はほぼ同じだが、若干の違
いがあることを意識しよう
• ラムダ式と匿名クラスも違いがあるので、気をつけ
よう
• thisの扱いが異なる
• メソッド参照の使いすぎに注意しよう
• 他の人が読めないコードになりがち
4問目
問題4:OpenSesame
public class OpenSesame {
public static void main(String... argv){
List<Integer> integerList = Arrays.asList(1,2,3,4,5);
List<Integer> filterdData = integerList.stream()
.filter((int i) -> (i > 2))
.collect(Collectors.toList());
System.out.println(filterdData);
}
}
選択肢4:OpenSesame
public class OpenSesame {
public static void main(String... argv){
List<Integer> integerList = Arrays.asList(1,2,3,4,5);
List<Integer> filterdData = integerList.stream()
.filter((int i) -> (i > 2))
.collect(Collectors.toList());
System.out.println(filterdData);
}
}
選択肢
1. 1,2
2. 3,4,5
3. 何も表示されない
4. コンパイルエラー
正解は ?!
解答4:OpenSesame
public class OpenSesame {
public static void main(String... argv){
List<Integer> integerList = Arrays.asList(1,2,3,4,5);
List<Integer> filterdData = integerList.stream()
.filter((int i) -> (i > 2))
.collect(Collectors.toList());
System.out.println(filterdData);
}
}
選択肢
1. 1,2
2. 3,4,5
3. 何も表示されない
4. コンパイルエラー
解説4:何が悪いのか?
public class OpenSesame {
public static void main(String... argv){
List<Integer> integerList = Arrays.asList(1,2,3,4,5);
List<Integer> filterdData = integerList.stream()
.filter((int i) -> (i > 2))
.collect(Collectors.toList());
System.out.println(filterdData);
}
}
期待する型が見つからない
クラス Test のメソッド filter は指定された型に適用できません
期待値:List<T>, Predicate<T>
検出値:List, (int i) -> (i>2)
解決4:IntStream を使いましょう
public class OpenSesame {
public static void main(String... argv){
int[] filterdData =
IntStream.range(1, 6)
.filter(i -> i > 2)
.toArray();
System.out.println(Arrays.toString(filterdData));
}
}
教訓4:
• Lambda 式を記述する場合、部分的に型を
書くくらいならば 型を省略しましょう
• primitive 型を使用する場合、対応する
Stream を使いましょう
5問目
問題5:The Fog
class Ship<T> {
private T t;
public Ship(T t) { this.t = t; }
public void setCrew(T t) { this.t = t; }
public int hashCode() { return Objects.hashCode(t); }
}
public class TheFog {
public static void main(String... args) {
Map<Ship<String>, String> map = new HashMap<>();
Ship<String> a = new Ship<>("A");
Ship<String> b = new Ship<>("B");
map.put(a, "a"); a.setCrew("B");
map.put(b, "b");
System.out.println(map.values().size());
}}
選択肢5:The Fog
class Ship<T> {
private T t;
public Ship(T t) { this.t = t; }
public void setCrew(T t) { this.t = t; }
public int hashCode() { return Objects.hashCode(t); }
}
public class TheFog {
public static void main(String... args) {
Map<Ship<String>, String> map = new HashMap<>();
Ship<String> a = new Ship<>("A");
Ship<String> b = new Ship<>("B");
map.put(a, "a"); a.setCrew("B");
map.put(b, "b");
System.out.println(map.values().size());
}}
選択肢
1. 0
2. 1
3. 2
4. Exception
正解は ?!
解答5:The Fog
class Ship<T> {
private T t;
public Ship(T t) { this.t = t; }
public void setCrew(T t) { this.t = t; }
public int hashCode() { return Objects.hashCode(t); }
}
public class TheFog {
public static void main(String... args) {
Map<Ship<String>, String> map = new HashMap<>();
Ship<String> a = new Ship<>("A");
Ship<String> b = new Ship<>("B");
map.put(a, "a"); a.setCrew("B");
map.put(b, "b");
System.out.println(map.values().size());
}}
選択肢
1. 0
2. 1
3. 2
4. Exception
解説5:
• MapはKeyのハッシュ値を使用する
• 同じハッシュ値をputすると、上書きする
• ハッシュ値が後から変更された場合、同じハッシュ値の
エントリがあっても存在し続ける
• しかし、そのエントリにはアクセスできない
• MutableなオブジェクトをMapのキーにすることは危険
解決5class Ship<T> {
private T t;
public Ship(T t) { this.t = t; }
// public void setCrew(T t) { this.t = t; }
public int hashCode() { return Objects.hashCode(t); }
}
public class TheFog {
public static void main(String... args) {
Map<Ship<String>, String> map = new HashMap<>();
Ship<String> a = new Ship<>("A");
Ship<String> b = new Ship<>("B");
map.put(a, "a"); // a.setCrew("B");
map.put(b, "b");
System.out.println(map.values().size());
}}
教訓5:
• MutableなオブジェクトをMapのキーにしない
• Imutableなオブジェクトを使い慣れよう
• hashCodeメソッドをオーバーライドする場合、必ず
equalsメソッドもオーバーライドする
Java
Puzzlers
櫻庭 祐一
寺田 佳央
Dedicated to John
Carpenter

Java Puzzlers JJUG CCC 2016