S-JIS[2014-03-21/2016-12-17] 変更履歴

Java7→Java8の非互換性

JavaのJDK1.7から1.8で互換性が無い部分の抜粋。


DecimalFormat(NumberFormatFormat)

DecimalFormatの挙動がJDK1.8で変わった(というか正しくなった)らしい。

DecimalFormatを使ってdoubleの値を整形する場合に、JDK1.7と1.8では異なる値が返ってくることがある。
少し試した感じでは、DecimalFormatだけの違いであり、他の整形方法ではとりあえず相違なさそう?

JDK1.7の実行結果 JDK1.8の実行結果
DecimalFormat df = new DecimalFormat();
System.out.println(df.format(0.8055d));
0.806 0.805
BigDecimal dec = new BigDecimal("0.8055");
System.out.println(dec);
0.8055 0.8055
System.out.println(0.8055d); 0.8055 0.8055
System.out.printf("%5.3f%n", 0.8055d); 0.806 0.806
System.out.printf("%5.4f%n", 0.8055d); 0.8055 0.8055
System.out.printf("%5.5f%n", 0.8055d); 0.80550 0.80550

0.8055という値は、十進数で見ると切りのいい値だけど、浮動小数点(double)だと0.8054999…みたいな値になるらしい。
なので、それをDecimalFormatで四捨五入すると0.805になるのが正しい、ということのようだ。

Javaのバージョンが変わって出力結果が変わってしまうのは困るけれども、
そもそもそんなに厳密な結果が欲しいのであればdoubleを使っている方がおかしいから、
そんなに影響は無い気もする。


Collection#removeAll(null)

空のコレクション(Listとか)のremoveAll()およびretainAll()にnullを渡したときの挙動が変わったらしい。

JDK1.7の実行結果 JDK1.8の実行結果
List<String> list = new ArrayList<>();
list.removeAll(null);
(特にエラーなし) NullPointerException
List<String> list = new ArrayList<>();
list.retainAll(null);
(特にエラーなし) NullPointerException

空でないコレクションだとJDK1.7でもNullPointerExceptionが発生するから、JDK1.8で統一が取れたということかな?


ジェネリクス型とRAW型との変換

ジェネリクスが指定された型とRAW型(型消去されたクラス)との変換のチェックが強化されたのかな?

import java.util.List;

class SampleClass {

	static class Baz<T> {
		public static List<Baz<Object>> sampleMethod(Baz<Object> param) {
			return null;
		}
	}

	private static void bar(Baz arg) {
		Baz element = Baz.sampleMethod(arg).get(0);
	}
}

Bazクラスには型引数を取るように定義されているが、
barメソッドでは型引数を指定せずにBazクラスを使っている。

JDK1.7ではこれでもコンパイルは通るが、JDK1.8ではコンパイルエラーになる。

SampleClass.java:12: エラー: 不適合な型: ObjectをBazに変換できません:
                   Baz element = Baz.sampleMethod(arg).get(0);
                                                          ^

正直、細かい条件はよく分からない(爆)
例えば上記のsampleMethd()の引数を削除したメソッドにしてみると、ちゃんとコンパイル通るし。

	static class Baz<T> {
		public static List<Baz<Object>> sampleMethod(/* Baz<Object> param */) {
			return null;
		}
	}

	private static void bar(Baz arg) {
		Baz element = Baz.sampleMethod(/* arg */).get(0);
	}

Thread#stop(Throwable)廃止

Threadのstopメソッド(Throwableが引数のもの)が使えなくなった。
具体的には、常にUnsupportedOperationExceptionをスローするようになった。

なお、引数なしのstopメソッドは従来通り使える。

が、いずれにしてもThread#stop()は非推奨メソッドだし、動作も怪しいらしいし、使っている人はいないと思うので影響は無いだろう。


apt削除

apt(Annotation Processing Tool、アノテーションプロセッサー)が廃止になった。


匿名クラスから外側クラスのメソッドを探す方法のバグ修正

以下のようなソースが、JDK1.7ではコンパイルが通るが、JDK1.8でコンパイルエラーになる。[2016-12-17]
これは仕様上コンパイルエラーになるのが正しく、JDK1.6でも(Eclipseでも)コンパイルエラーになるらしい。つまりJDK1.7のjavacのバグ。

public class Outer {
	private void same(String s) {
	}

	class Inner {
		public void same(int n) {
		}
	}

	public void run() {
		new Inner() {
			@Override
			public void same(int n) {
			}

			public void test() {
				same("abc"); // コンパイルエラー
			}
		};
	}
}

これがコンパイルエラーになるのは、内部クラスからメソッドを呼ぶ場合は外側クラスに同名のメソッドがあっても、内部クラスのメソッドを呼び出すので、内部クラスで定義されているメソッドと呼び出したいメソッドで引数の型が合わない為。
上の例では、内部クラスInnerのsameメソッドの引数はintなので、Stringの引数のsameメソッド(外側クラスで定義されているメソッド)は呼び出せない。

自分で試した感じでは、このバグに引っかかる(JDK1.7でコンパイルエラーにならない)のは以下の条件の場合。

ちなみに、上の例では、メソッド呼び出しを「Outer.this.same("abc")」とすればコンパイルは通るようになる。
無名内部クラスからアクセスできる範囲

参考: bitter_foxさんのJavaSE8リリース記念!CGに載っていないマイナーなIncompatibilityを紹介してみる


Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま