Javaに関する日記

I like JAVA TEA

JavaのintとIntegerの変換について(autoboxing/unboxing)

疑問

Javaで int や Integer のようなプリミティブとラッパークラスは意識して使い分けるべきか?

結論

意識して使い分けたほうが良い。

Autoboxing/Unboxingを使うのは、数値をコレクションに入れるなどの、インピーダンス・ミスマッチがある場合にのみ使用したほうが良い。

と、Oracleのサイトに書いてありました。

docs.oracle.com

余談

Autoboxing/Unboxingがあるので、Integerで宣言してもintとして使えるし、intで宣言してもIntegerと同じようにCollectionに突っ込むことができます。

import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String...args) {
        Integer i = 3;
        int j = 4;
        System.out.println("i + j = " + (i + j));

        List<Integer> list = new ArrayList<>();
        list.add(i);
        list.add(j);
        for (Integer x: list) {
            System.out.println(">> " + x);
        }
    }
}

これをコンパイルして実行すると以下のような結果になります。

$ javac Test.java
$ java Test
i + j = 7
>> 3
>> 4

コンパイルに成功するし実行時エラーも出ません。

Integerで宣言してもintとして使えるし、intで宣言してもIntegerと同じようにCollectionに突っ込むことができます。(2回目)

コンパイル

以下のメソッドを逆コンパイルしてどういうコードが生成されているかを確認しました。

/**
  * int int -> int
  */
public int addIntIntToInt(int x, int y) {
    return x + y;
}

/**
  * Integer int -> int
  */
public int addIntegerIntoToInt(Integer x, int y) {
    return x + y;
}

/**
  * int Integer -> int
  */
public int addIntIntegerToInto(int x, Integer y) {
    return x + y;
}

/**
 * Integer Integer -> int
 */
public int addIntegerIntegerToInt(Integer x, Integer y) {
    return x + y;
}

/**
  * int int -> Integer
  */
public Integer addIntIntToInteger(int x, int y) {
    return x + y;
}

/**
 * Integer int -> Integer
 */
public Integer addIntegerIntoToInteger(Integer x, int y) {
    return x + y;
}

/**
 * int Integer -> Integer
 */
public Integer addIntIntegerToInteger(int x, Integer y) {
    return x + y;
}

/**
 * Integer Integer -> Integer
 */
public Integer addIntegerIntegerToInteger(Integer x, Integer y) {
    return x + y;
}

int と int を受け取って int を返す

引数をスタックに積んで add してその値を返しています。

/**
  * int int -> int
  */
public int addIntIntToInt(int x, int y) {
    return x + y;
}
  public int addIntIntToInt(int, int);
    descriptor: (II)I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: ireturn
      LineNumberTable:
        line 6: 0

Integer と int を受け取って int を返す

x + yコンパイルすると x.intValue() + y になっているのがわかります。

/**
  * Integer int -> int
  */
public int addIntegerIntoToInt(Integer x, int y) {
    return x + y;
}
  public int addIntegerIntoToInt(java.lang.Integer, int);
    descriptor: (Ljava/lang/Integer;I)I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_1
         1: invokevirtual #7                  // Method java/lang/Integer.intValue:()I
         4: iload_2
         5: iadd
         6: ireturn
      LineNumberTable:
        line 13: 0

int と Integer を受け取って int を返す

x + yx + y.intValue()コンパイルされてます。

/**
  * int Integer -> int
  */
public int addIntIntegerToInto(int x, Integer y) {
    return x + y;
}
  public int addIntIntegerToInto(int, java.lang.Integer);
    descriptor: (ILjava/lang/Integer;)I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: aload_2
         2: invokevirtual #7                  // Method java/lang/Integer.intValue:()I
         5: iadd
         6: ireturn
      LineNumberTable:
        line 20: 0

Integer と Integer を受け取って int を返す

x + yx.intValue() + y.intValue()コンパイルされています。

/**
 * Integer Integer -> int
 */
public int addIntegerIntegerToInt(Integer x, Integer y) {
    return x + y;
}
  public int addIntegerIntegerToInt(java.lang.Integer, java.lang.Integer);
    descriptor: (Ljava/lang/Integer;Ljava/lang/Integer;)I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_1
         1: invokevirtual #7                  // Method java/lang/Integer.intValue:()I
         4: aload_2
         5: invokevirtual #7                  // Method java/lang/Integer.intValue:()I
         8: iadd
         9: ireturn
      LineNumberTable:
        line 27: 0

int と int を受け取って Integer を返す

x + y の結果に対して Integer.valueOf() を実行して Integer にしているのがわかります。 Integer.sum は使っていません。

/**
  * int int -> Integer
  */
public Integer addIntIntToInteger(int x, int y) {
    return x + y;
}
  public java.lang.Integer addIntIntToInteger(int, int);
    descriptor: (II)Ljava/lang/Integer;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: invokestatic  #13                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         6: areturn
      LineNumberTable:
        line 34: 0

Integer と int を受け取って Integer を返す

Integerint にして計算したあと、 Integer.ValueOf()Integer に変換しています。

/**
 * Integer int -> Integer
 */
public Integer addIntegerIntoToInteger(Integer x, int y) {
    return x + y;
}
  public java.lang.Integer addIntegerIntoToInteger(java.lang.Integer, int);
    descriptor: (Ljava/lang/Integer;I)Ljava/lang/Integer;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_1
         1: invokevirtual #7                  // Method java/lang/Integer.intValue:()I
         4: iload_2
         5: iadd
         6: invokestatic  #13                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         9: areturn
      LineNumberTable:
        line 41: 0

int と Integer を受け取って Integer を返す

こちらも同様です。

/**
 * int Integer -> Integer
 */
public Integer addIntIntegerToInteger(int x, Integer y) {
    return x + y;
}
  public java.lang.Integer addIntIntegerToInteger(int, java.lang.Integer);
    descriptor: (ILjava/lang/Integer;)Ljava/lang/Integer;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: aload_2
         2: invokevirtual #7                  // Method java/lang/Integer.intValue:()I
         5: iadd
         6: invokestatic  #13                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         9: areturn
      LineNumberTable:
        line 48: 0

Integer と Integer を受け取って Integer を返す

これも同じく、 Integer をわざわざ int にして計算し、その結果を Integer に変換しています。

/**
 * Integer Integer -> Integer
 */
public Integer addIntegerIntegerToInteger(Integer x, Integer y) {
    return x + y;
}
  public java.lang.Integer addIntegerIntegerToInteger(java.lang.Integer, java.lang.Integer);
    descriptor: (Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_1
         1: invokevirtual #7                  // Method java/lang/Integer.intValue:()I
         4: aload_2
         5: invokevirtual #7                  // Method java/lang/Integer.intValue:()I
         8: iadd
         9: invokestatic  #13                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        12: areturn
      LineNumberTable:
        line 55: 0

性能試験

は面倒なのでやらないですが、コンパイラの出力を見ればループの中で Autoboxing/Unboxing を使うのはちょっとキモチワルイ感じがします。