[Java] すこしふしぎなsplitの挙動

# [Java] すこしふしぎなsplitの挙動

## 筆者の環境

Java8 (のはず)

## splitクイズ

突然ですが問題です。

以下はJavaのコードです。
実行結果はどうなるでしょうか。

第１問！デデン♪

```java
            String test = "a-i-u-e-o";
            String[] tests = test.split("-");
            System.out.println(tests.length);
            System.out.println(java.util.Arrays.toString(tests));
```

<details>
<summary>答え</summary>
<div>

>```
>5
>[a, i, u, e, o]
>```

</div>
</details>

正解しましたか?

次です。 こちらの結果はどうなるでしょうか。

```java
            String test = "--o";
            String[] tests = test.split("-");
            System.out.println(tests.length);
            System.out.println(java.util.Arrays.toString(tests));
```

<details>
<summary>答え</summary>
<div>

>```
>3
>[, , o]
>```

</div>
</details>

正解しましたか?
先頭にデリミタがある場合はこのような挙動になるのですね。

次はこちらです。 こちらの結果はどうなるでしょうか。
なんとなく予想がつくのではないでしょうか。

```java
            String test = "a----o";
            String[] tests = test.split("-");
            System.out.println(tests.length);
            System.out.println(java.util.Arrays.toString(tests));
```

<details>
<summary>答え</summary>
<div>

>```
>5
>[a, , , , o]
>```

</div>
</details>

正解しましたか？

さて最後です。 こちらの結果はどうなるでしょうか。
ここまで正解したなら<font color='aliceblue'>きっと</font>簡単ですね。 さくさくいきましょう。

```java
            String test = "a--";
            String[] tests = test.split("-");
            System.out.println(tests.length);
            System.out.println(java.util.Arrays.toString(tests));
```

<details>
<summary>答え</summary>
<div>

>```
>1
>[a]
>```

</div>
</details>

正解しましたか？
正解した方はおめでとうございます :tada:
外れてしまったかたは惜しかったですね...(´・ω・`)

ちなみにこれらの挙動を見たときの私の反応はこのような感じでした。

<details>
<summary>₍₍(ง˘ω˘)ว⁾⁾</summary>
<div>

> ？？？？？？？？？？？？？？？？？？？？？？？？？？
> え、ん...? あ、そう...ふーん( ´_ゝ｀)なんでやねん

</div>
</details>


## Javaのsplit

今まで出たものをまとめて俯瞰すると以下のようになります。

```java
/* 
 * Java Playground
 * https://code.sololearn.com
 */
class Main {
    public static void main(String[ ] args) {
        {
            String test = "a-i-u-e-o";
            String[] tests = test.split("-");
            System.out.println(tests.length); // 5
            System.out.println(java.util.Arrays.toString(tests)); // [a,i,u,e,o]
        }
        {
            String test = "a-";
            String[] tests = test.split("-");
            System.out.println(tests.length); // 1
            System.out.println(java.util.Arrays.toString(tests)); // [a]
        }
        {
            String test = "-o";
            String[] tests = test.split("-");
            System.out.println(tests.length); // 2
            System.out.println(java.util.Arrays.toString(tests)); // [,o]
        }
        {
            String test = "a--";
            String[] tests = test.split("-");
            System.out.println(tests.length); // 1
            System.out.println(java.util.Arrays.toString(tests)); // [a]
        }
        {
            String test = "--o";
            String[] tests = test.split("-");
            System.out.println(tests.length); // 3
            System.out.println(java.util.Arrays.toString(tests)); // [,,o]
        }
        {
            String test = "a----o";
            String[] tests = test.split("-");
            System.out.println(tests.length); // 5
            System.out.println(java.util.Arrays.toString(tests)); // [a,,,,o]
        }
    }
}
```

いかがでしょうか。
私は<font color='aliceblue'>正直気持ち悪いと思ゲフンゲフン</font>
空を無視するのかしないのかはっきりしてほしいと思いました。
しかしながら、このような挙動になっていることにはなにかしらの理由があるのかもしれませんね。[^1]

# 蛇足

## Golangのsplit

ちなみに言語ごとにsplitの挙動は異なるようですので、複数言語を扱う場合はご注意ください。
一例として、比較的わかりやすいGolangのサンプルを以下に貼ります。

<details>
<summary>Golangのsplitサンプル</summary>
<div>

```go
/*
 * Golang Playground
 * https://play.golang.org/
 */
package main

import (
	"fmt"
	"strings"
)

func main() {
	{
		test := "a-i-u-e-o"
		tests := strings.Split(test, "-")
		fmt.Println(len(tests)) // 5
		fmt.Println(tests) // [a i u e o]
	}
	{
		test := "a-"
		tests := strings.Split(test, "-")
		fmt.Println(len(tests)) // 2
		fmt.Println(tests) // [a ]
	}
	{
		test := "-o"
		tests := strings.Split(test, "-")
		fmt.Println(len(tests)) // 2
		fmt.Println(tests) // [ o]
	}
	{
		test := "a--"
		tests := strings.Split(test, "-")
		fmt.Println(len(tests)) // 3
		fmt.Println(tests) // [a  ]
	}
	{
		test := "--o"
		tests := strings.Split(test, "-")
		fmt.Println(len(tests)) // 3
		fmt.Println(tests) // [  o]
	}
	{
		test := "a----o"
		tests := strings.Split(test, "-")
		fmt.Println(len(tests)) // 5
		fmt.Println(tests) // [a    o]
	}
}
```

</div>
</details>

Javaの挙動を見たあとだと素直な挙動に感じますね...

## 付録

Javaのsplitは要するに右端のデリミタを無視すればよさそうなので、
Golangで同様の挙動を模倣する場合は、右端のデリミタを除去してからsplitするといいかもしれません。
需要があるかはわかりませんがサンプルを貼ります。 需要があるかはわかりませんが。

<details>
<summary>GolangでJavaのsplitのような挙動をさせ隊 (隊員1名)</summary>
<div>

```go
package main

import (
	"fmt"
	"strings"
)

func main() {
	{
		test := "a-i-u-e-o"
		tests := javaSplit(test, "-")
		fmt.Println(len(tests)) // 5
		fmt.Println(tests) // [a i u e o]
	}
	{
		test := "a-"
		tests := javaSplit(test, "-")
		fmt.Println(len(tests)) // 1
		fmt.Println(tests) // [a]
	}
	{
		test := "-o"
		tests := javaSplit(test, "-")
		fmt.Println(len(tests)) // 2
		fmt.Println(tests) // [ o]
	}
	{
		test := "a--"
		tests := javaSplit(test, "-")
		fmt.Println(len(tests)) // 1
		fmt.Println(tests) // [a]
	}
	{
		test := "--o"
		tests := javaSplit(test, "-")
		fmt.Println(len(tests)) // 3
		fmt.Println(tests) // [  o]
	}
	{
		test := "a----o"
		tests := javaSplit(test, "-")
		fmt.Println(len(tests)) // 5
		fmt.Println(tests) // [a    o]
	}
}

// Javaの String#split(delimiter) を模倣
func javaSplit(str string, delimiter string) []string {
    return strings.Split(strings.TrimRight(str, delimiter), delimiter)
}
```

</div>
</details>

え、JavaでGolangのような挙動にしたい場合...?
~~あー... .. . がんばってください！~~

【追記】
>え、JavaでGolangのような挙動にしたい場合...?

@saka1029 さんに教えていただきました！
> 第2引数に負の数を指定すればよいのでは？（[String.split(String, int)](https://docs.oracle.com/javase/jp/13/docs/api/java.base/java/lang/String.html#split(java.lang.String,int))）
>
> ```java
> String[] test = "a--".split("-", -1);
> System.out.println(test.length);               // -> 3
> System.out.println(Arrays.toString(test));     // -> [a, , ]
> ```
>

<details>
<summary>javadocより抜粋</summary>
<div>

> public String[] split​(String regex, int limit)
>
> limitパラメータは、このパターンの適用回数を制御するため、結果となる配列の長さに影響を及ぼします。
> 
> * 「制限」が正の場合、パターンはほとんどの「制限」に適用されます。-1回は配列の長さが「制限」を超えることはなく、最後に一致したデリミタを超えるすべての入力が配列の最後のエントリに含まれます。
> * 「制限」がゼロの場合、パターンは可能なかぎり何度も適用され、配列には任意の長さを指定でき、後続の空の文字列は破棄されます。
> * 「制限」が負の場合、パターンは可能なかぎり適用され、配列の長さは任意になります。

</div>
</details>

まさにこれです！いえーい！
~~というかjavadoc読んでから執筆しなさいよ~~
リンクはjava13のdocですが、[java8](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/String.html#split-java.lang.String-int-)も同様です。


# さいごに

「ここまずいですよ」や「そいつぁちげーぜ！」などがありましたらコメントいただけますと幸いです₍₍(ง˘ω˘)ว⁾⁾
ゼロは俺に何も言ってはくれない...

[^1]: とりあえず仕様であることだけは間違いありません。 https://docs.oracle.com/javase/jp/8/docs/api/java/lang/String.html#split-java.lang.String-