ジェネリックな数値キャスト(プリミティブ拡大変換、縮小変換)

概要

今日は、ジェネリックな数値キャストをします。

やりたいこと

プリミティブ型の数値キャスト(プリミティブ拡大変換、縮小変換)と呼ばれる演算があります。
たとえば、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を得るのにはプリミティブ型のクラスが簡単ですが、ラッパー型でもラッパー→プリミティブ型変換ができれば可能です。

関連

*1:引数と戻り値はラッパー型になります