Javaのプリミティブ型とオブジェクト型の違い

Javaにおけるプリミティブ型とオブジェクト型(ラッパークラス)の違いについてメモ。  
検証では Java の REPL ツールである [JShell](https://docs.oracle.com/javase/jp/11/tools/jshell.html#GUID-C337353B-074A-431C-993F-60C226163F00) を使用する。

```
$ jshell

jshell> System.out.println("Hello");
Hello
```

そのため、変数の型を確認できる `/v` という JShell 特有の記法も使用しているので注意。  

```
jshell> /v 3
|    Integer nullableInteger = null
```

※ 以後コードを書く際は `jshell>` の表記を省略する

### 1. 値の比較

正直このメモを残すために記事を書いてる。  
オブジェクト型の比較では `equals()` メソッドを使用する必要がある。

```java
int a = 5;
int b = 5;
System.out.println(a == b); // true, 値に基づく比較

String c = new String("Hello");
String d = new String("Hello");
Integer d = new Integer(5);
System.out.println(c == d); // false, 参照が異なるため
System.out.println(c.equals(d)); // true, 値に基づく比較
```

### 2. コレクションフレームワーク

Javaのコレクションフレームワークはオブジェクトを格納するために設計されている。  
プリミティブ型をこれらのコレクションに直接格納することはできない。  

```java
var intList = List.of(1, 2, 3); // 自動的にプリミティブ型のintがIntegerになる

/v intList
// 出力:  List<Integer> intList = [1, 2, 3]
```

### 3. Comparator

`Arrays.sort` や `Collections.sort` メソッドを使用する際、`Comparator` を指定することで並び替えの基準をカスタマイズすることができる。  
ただし、これらのメソッドの第一引数には、プリミティブ型の配列は指定できず、オブジェクト型の配列やコレクションを使用する必要がある。  

```java
Integer[] array = {3, 1, 4, 1, 5, 9, 2, 6, 5};
Arrays.sort(array, Comparator.reverseOrder());
System.out.println(Arrays.toString(array)); // [9, 6, 5, 5, 4, 3, 2, 1, 1]
```

上記のコードでは、`Integer[]`(オブジェクト型の配列)を、`Comparator.reverseOrder()` を用いて降順にソートしている。

一方で、プリミティブ型の配列(例えば`int[]`)を用いる場合、`Arrays.sort` メソッドに `Comparator` を直接指定することはできない。  

```java
int[] primitiveArray = {3, 1, 4, 1, 5, 9, 2, 6, 5};
// Arrays.sort(primitiveArray, Comparator.reverseOrder()); // コンパイルエラー
```

これは、`Comparator`がオブジェクト間の比較を行うためのインターフェースであり、プリミティブ型には適用できないため。  
プリミティブ型の配列をカスタムの順序でソートするには、まず対応するラッパー型の配列に変換するか、手動でソートロジックを実装する必要がある。  


### 4. ジェネリクス

ジェネリクスはオブジェクト型のみを扱う。  
つまり、`List<Integer>`はできるが `List<int>`はできない。  

```java
List<Integer> validList = new ArrayList<>();
// List<int> invalidList = new ArrayList<>(); // コンパイルエラーになる
```

### 5. NULLの扱い

プリミティブ型は NULL を持つことができないが、オブジェクト型は NULL を持つことができる。  

```java
Integer nullableInteger = null;
// int nullInt = null; // コンパイルエラーになる
```

### 6. ストリームAPI

`Arrays.stream` の返り値の型に気をつける必要がある。  
例えば、`Stream<Integer>` と `IntStream` の違い。  

- `Stream<Integer>` : `Integer` オブジェクトのストリーム
- `IntStream`: プリミティブ型 `int` の値のストリーム

```java
Integer[] objectArray = {1, 2, 3, 4, 5};
Stream<Integer> objectStream = Arrays.stream(objectArray);
/v objectStream
// 出力: |    Stream<Integer> objectStream = java.util.stream.ReferencePipeline$Head@300ffa5d

```

```java
int[] primitiveArray = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(primitiveArray);
/v intStream
// 出力: |    IntStream intStream = java.util.stream.IntPipeline$Head@4d405ef7
```

これらの違いにより、ストリームの操作方法に違いが生じる。  
例えば、`Stream<Integer>` には `sum()` メソッドが定義されていないが、`IntStream` には `sum()` メソッドが存在する。  

```java
int[] primitiveArray = {1, 2, 3, 4, 5};
int sum = Arrays.stream(primitiveArray).sum();
System.out.println(sum); // 15

// Integer`オブジェクトの配列に対して同じ操作を行うと、エラーが発生
Integer[] objectArray = {1, 2, 3, 4, 5};
// int sumObject = Arrays.stream(objectArray).sum(); // コンパイルエラー
```

エラーの理由は、`Arrays.stream(objectArray)` が `Stream<Integer>` を生成し、この型のストリームには `sum()` メソッドが定義されていないため。  
この場合、`mapToInt()` メソッドを使用して `Stream<Integer>` を `IntStream` に変換する必要がある。  

```java
Integer[] objectArray = {1, 2, 3, 4, 5};
int sumObject = Arrays.stream(objectArray)
                      .mapToInt(Integer::intValue)
                      .sum();
System.out.println(sumObject); // 15
```

このコードでは、`mapToInt(Integer::intValue)`が各`Integer`オブジェクトをプリミティブ型の`int`に変換し、`sum()`メソッドを適用している。