ジェネリックな数値キャスト(プリミティブ拡大変換、縮小変換)
概要
今日は、ジェネリックな数値キャストをします。
やりたいこと
プリミティブ型の数値キャスト(プリミティブ拡大変換、縮小変換)と呼ばれる演算があります。
たとえば、double型をshort型に変換する場合
short s = (short)3.0;
これをジェネリックにするにはどうすればよいか。
以下のようなシグネチャのメソッドで、fromの型とtoClassの表す型がどのような数値型であってもキャストができる*1ものがほしい。
<T extends Number> T test.NumCast.numCast(Number from, Class<T> toClass)
同じ「キャスト」でもClass.castは参照型の拡大、縮小変換しかできません。
数値型を表すClassオブジェクトのcastメソッドを呼び出すと、ClassCastExceptionがスローされます。
// 同じ「キャスト」でもダメ int.class.cast(100);
ではどうするか。
方法
数値ラッパーインスタンスはNumberを継承しているので、
インスタンスメソッドxxxValue (xxxにはbyte, short, int, long, float, doubleが入る)を持ちます。
文字列"xxxValue"を生成し、リフレクションMethod.invokeで呼べば数値キャストと同じ効果があります。
それで欲しかったシグネチャをもつメソッドが定義できます。
<T extends Number> T test.NumCast.numCast(Number from, Class<T> toClass)
プログラムは以下。せっかくなので色々な型で実行してみましょう。
package test; import java.util.Arrays; import java.util.List; public class NumCast { @SuppressWarnings("unchecked") public static <T extends Number> T numCast(final Number num, final Class<T> prim) { if(! prim.isPrimitive()) { throw new IllegalArgumentException("not primitive: " + prim); } try { // Number.xxxValue return (T) Number.class.getMethod(prim.getSimpleName() + "Value").invoke(num); } catch (Exception e) { throw new AssertionError(e); } } public static void main(final String[] args) { short s = (short)3.0; // 同じ「キャスト」でもダメ // int.class.cast(100); List<Number> nums = Arrays.<Number> asList((byte)1, (short)2, 3, 4L, 5.5f, 6.6d, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NaN); List<? extends Class<? extends Number>> clazzs = Arrays.asList( byte.class, short.class, int.class, long.class, float.class, double.class); for (Number num : nums) { for (Class<? extends Number> clazz : clazzs) { System.out.println("(" + num.getClass().getSimpleName() + "->" + clazz + ") " + num + "->" + numCast(num, clazz)); } } // 型付き Integer i = numCast(3d, int.class); } }
実行結果
(Byte->byte) 1->1 (Byte->short) 1->1 (Byte->int) 1->1 (Byte->long) 1->1 (Byte->float) 1->1.0 (Byte->double) 1->1.0 (Short->byte) 2->2 (Short->short) 2->2 (Short->int) 2->2 (Short->long) 2->2 (Short->float) 2->2.0 (Short->double) 2->2.0 (Integer->byte) 3->3 (Integer->short) 3->3 (Integer->int) 3->3 (Integer->long) 3->3 (Integer->float) 3->3.0 (Integer->double) 3->3.0 (Long->byte) 4->4 (Long->short) 4->4 (Long->int) 4->4 (Long->long) 4->4 (Long->float) 4->4.0 (Long->double) 4->4.0 (Float->byte) 5.5->5 (Float->short) 5.5->5 (Float->int) 5.5->5 (Float->long) 5.5->5 (Float->float) 5.5->5.5 (Float->double) 5.5->5.5 (Double->byte) 6.6->6 (Double->short) 6.6->6 (Double->int) 6.6->6 (Double->long) 6.6->6 (Double->float) 6.6->6.6 (Double->double) 6.6->6.6 (Double->byte) -Infinity->0 (Double->short) -Infinity->0 (Double->int) -Infinity->-2147483648 (Double->long) -Infinity->-9223372036854775808 (Double->float) -Infinity->-Infinity (Double->double) -Infinity->-Infinity (Double->byte) Infinity->-1 (Double->short) Infinity->-1 (Double->int) Infinity->2147483647 (Double->long) Infinity->9223372036854775807 (Double->float) Infinity->Infinity (Double->double) Infinity->Infinity (Double->byte) NaN->0 (Double->short) NaN->0 (Double->int) NaN->0 (Double->long) NaN->0 (Double->float) NaN->NaN (Double->double) NaN->NaN
補足
- 文字列xxxを得るのにはプリミティブ型のクラスが簡単ですが、ラッパー型でもラッパー→プリミティブ型変換ができれば可能です。
- 浮動小数点型の±∞とNaNは挙動が読みづらいのでキャストしない方がよいでしょう。
関連
- 言語仕様 プリミティブ拡大、縮小変換
- Dynamic explicit cast of primitive types in Java using reflection - Stack Overflow
- 条件分岐でNumberのメソッドを呼びわけている例。こちらの方が速いと思います。
*1:引数と戻り値はラッパー型になります