目次
- はじめに
- 個別の型に語らせる
- 汎用的な型に語らせる
- さいごに
はじめに
皆さんも下記のようなコメント・ドキュメント付きの関数を使ったことがあるかと思います。
* ...
* 戻り値:
* このオブジェクトによって表される文字シーケンス内で、指定された文字が最初に出現する位置のインデックス。文字がない場合は-1。
*/
public int indexOf(int ch);
この関数の戻り値は、「位置のインデックス」という成功値と、「-1」という失敗値を同時に表現しようとしています。ここで、失敗値=-1 というのはおそらく、特に根拠のある値でなく、成功値>=0 に重ならなければ何でも良かったのだと思われます。従って、この手のIFの関数が成功したか失敗したかをチェックするためにはドキュメントを参照しなければいけません。つまり、この関数はコードに語らせられていないのです。
コードに語らせる、とは、コメントやドキュメントを参照しなくてもわかりやすく、使いやすいコードを書くということです。
以下では、同関数を題材にコードに語らせる例を記載します。
個別の型に語らせる
これは戻り値を独自定義のclassのobjectにするということです。
class StringUtil {
public static ResultOfIndexOf indexOf(String haystack, char needle) {
for (int i = 0; i < haystack.length(); ++i)
if (haystack.charAt(i) == needle)
return new ResultOfIndexOf(i);
return new ResultOfIndexOf();
}
}
class ResultOfIndexOf {
private final boolean isFound;
private final int foundIndex;
ResultOfIndexOf() {
this(false, 0);
}
ResultOfIndexOf(int foundIndex) {
this(true, foundIndex);
}
private ResultOfIndexOf(boolean isFound, int foundIndex) {
this.isFound = isFound;
this.foundIndex = foundIndex;
}
public boolean isFound() {
return isFound;
}
public int foundIndex() {
if (!isFound)
throw new RuntimeException("index not found");
return foundIndex;
}
}
これを使う側のコードは以下のようになります。(ideone)
class Ideone
{
public static void main (String[] args) throws java.lang.Exception
{
ResultOfIndexOf result = StringUtil.indexOf("hello, world", 'l');
if (result.isFound())
System.out.println(String.format("index=%d", result.foundIndex()));
}
}
中間の変数が邪魔なら、コールバックを渡して成功した場合のみ呼び出してもらいます。(ideone)
import java.util.*;
import java.lang.*;
import java.io.*;
import java.util.function.*;
class Ideone
{
public static void main (String[] args) throws java.lang.Exception
{
StringUtil.indexOf("hello, world", 'l')
.DoIfSuccess(foundIndex -> {
System.out.println(String.format("index=%d", foundIndex));
});
}
}
class StringUtil {
public static ResultOfIndexOf indexOf(String haystack, char needle) {
for (int i = 0; i < haystack.length(); ++i)
if (haystack.charAt(i) == needle)
return new ResultOfIndexOf(i);
return new ResultOfIndexOf();
}
}
class ResultOfIndexOf {
private final boolean isFound;
private final int foundIndex;
ResultOfIndexOf() {
this(false, 0);
}
ResultOfIndexOf(int foundIndex) {
this(true, foundIndex);
}
private ResultOfIndexOf(boolean isFound, int foundIndex) {
this.isFound = isFound;
this.foundIndex = foundIndex;
}
public void DoIfSuccess(Consumer<Integer> postAction) {
if (isFound && postAction != null)
postAction.accept(foundIndex);
}
}
汎用的な型に語らせる
上記の例だと、関数ごとに型が増えてしまうのでGenericsなclassを作成し、Maybeと名付けて利用します。
(ideone)
import java.util.*;
import java.lang.*;
import java.io.*;
import java.util.function.*;
class Ideone
{
public static void main (String[] args) throws java.lang.Exception
{
StringUtil.indexOf("hello, world", 'l')
.DoIfSuccess(foundIndex -> {
System.out.println(String.format("index=%d", foundIndex));
});
}
}
class StringUtil {
public static Maybe<Integer> indexOf(String haystack, char needle) {
for (int i = 0; i < haystack.length(); ++i)
if (haystack.charAt(i) == needle)
return Maybe.<Integer>just(i);
return Maybe.<Integer>nothing();
}
}
class Maybe<ResultType> {
public static <R> Maybe<R> nothing() {
return new Maybe(false, null);
}
public static <R> Maybe<R> just(R resultValue) {
return new Maybe(true, resultValue);
}
private final boolean isSucceeded;
private final ResultType resultValue;
private Maybe(boolean isSucceeded, ResultType resultValue) {
this.isSucceeded = isSucceeded;
this.resultValue = resultValue;
}
public void DoIfSuccess(Consumer<ResultType> postAction) {
if (isSucceeded && postAction != null)
postAction.accept(resultValue);
}
}
さいごに
個人的には Maybe 戻り値にするのが好みです。もちろん標準ライブラリにあればそれを使ったほうが良いと思います(java8ならOptional)。無ければ自作してでもコードに語らせましょう。