最近Qiitaサボってますが8日ですからJava8ネタで書いてみます。
今回はJava8でこういうときこう書きたいけど、良いかな?ダメかな?と悩んでいることをちょっと調べてまとめることにしました。
ListをArrayにするとき悩む
String[] array = list.toArray(new String[list.size()]);
String[] array = list.stream().toArray(String[]::new);
listの変数名が4文字以上ならstream経由した方が短く書けるというこの二つ、
どちらで書きますか?
streamにする分無駄処理してるのはわかるけど短くかけるなら短く書きたいし、変数名が長ければ1回で済む後者を選んでしまいそう。
なので後者がどれだけ無駄なのか測定してみようと思いました。
List<String> list = new ArrayList<>(Arrays.asList("A"));
1,000,000,000回繰り返して測定します。
String[] array = list.toArray(new String[list.size()]);
21.613512471 s
String[] array = list.stream().toArray(String[]::new);
53.420359637 s
やはり長くても、stream経由するのはダメかもですね。
どうしても前者が気に入らなければ、
public static <T> T[] toArray(List<T> list, IntFunction<T[]> generator) {
return list.toArray(generator.apply(list.size()));
}
作っておいて、このメソッドを有するクラス名を短くすれば便利かもしれません。
インスタンス生成用情報はClassとSupplierどちらを使うか悩む
public <T extends Hoge> List<T> createHogeList(Class<T> type) throws Exception {
List<T> list = new ArrayList<>();
while (this.hasNext()) {
T t = type.newInstance();//今回のネタで重要なのはここだけ
t.bindData(this.next());
list.add(t);
}
return list;
}
みたいなメソッド作ったときに、Class渡してnewInstanceするだけなら、Java8ではもっと効率良い書き方がありますね。
public <T extends Hoge> List<T> createHogeList(Supplier<T> generator) {
List<T> list = new ArrayList<>();
while (this.hasNext()) {
T t = generator.get();//書き換え
t.bindData(this.next());
list.add(t);
}
return list;
}
当然、後者の方が効率良いんですけど、一応速度計測しようと思います。
1,000,000,000回繰り返して測定します。
public static <T> T newInstance(Class<T> type) throws InstantiationException, IllegalAccessException {
return type.newInstance();
}
newInstance(Test.class);
10.337521557 s
public static <T> T newInstance(Supplier<T> generator) {
return generator.get();
}
newInstance(Test::new);
3.364194031 s
ただインスタンス生成後、Reflection使って、MethodやFieldにアクセスする場合、
Supplier渡しだと1回Instance作ってからgetClassしてClassにアクセスしないといけないので、ロジックが煩雑になる可能性があるのと、煩雑になった分とgetClass等の速度劣化がどれだけ影響するのか測定してみる必要があるかもしれません。
あと、外部から見ればコンストラクタ参照を強制できるわけではないので、
変なコードを書かれるかもしれないという意味ではNGになるかもしれません。
ちなみに、コンストラクタ参照を使う場合、
privateコンストラクタでも参照可能な範囲なら利用できますし、
引数なしのコンストラクタが存在しない場合コンパイルエラーになってくれます。
ラムダ式は多用しても大丈夫か悩む
EnumにIgnoreCaseのOptionalで返すメソッド作ってて思ったんですけど、
public static Optional<TestEnum> find(String name) {
for (TestEnum e : values()) {
if(e.name().equalsIgnoreCase(name)){
return Optional.of(e);
}
}
return Optional.empty();
}
これvalues()で処理してるのをArrayで処理する汎用メソッドを別で用意しておけば使えるかもー。
public final class ArrayUtil {
public static <T> Optional<T> find(T[] array, Predicate<T> test) {
for (T t : array) {
if (test.test(t)) {
return Optional.of(t);
}
}
return Optional.empty();
}
}
public static Optional<TestEnum> find(String name) {
return ArrayUtil.find(values(), e -> e.name().equalsIgnoreCase(name));
}
っと、Enumのfindメソッドはすっきりしました。\( ̄▽ ̄)/
でもコレってラムダ式のインスタンス生成するんですよね。
気にするほどじゃないだろうけども、
気になったので計測することにしました。
同じく1,000,000,000回繰り返して測定します。
public static boolean test1(String s) {
return "A".equalsIgnoreCase(s);
}
7.181796312 s
public static boolean test2(String s) {
Predicate<String> test = t -> t.equalsIgnoreCase(s);
return test.test("A");
}
9.003788655 s
個人的には、これぐらいなら気にする必要ない
(ある程度ラムダ式多用しても問題ない)
と思ってます。個人的には。
一応もともと書き換えたEnumのfindメソッドでも計測しておきます。
元のコード(ラムダなし)
19.763773334 s
書き換え後(ラムダあり)
22.033178336 s
やはり気にする必要はなさそうというのが個人的見解です。
以上です。調べたおかげで勉強になりました。
これまでもJava8ネタ書いてます。
Java8で難解コーディング 良いのか悪いのかわからない活用法
Java8が好きになる話(StreamAPIの話はしない) その1 Map#computeIfAbsentとList#sort
Java8が好きになる話(StreamAPIの話はしない) その2 Optional#map
Java8が好きになる話(StreamAPIの話はしない) その3 インターフェースのデフォルト実装
Java8が好きになる話(StreamAPIの話はしない) その4 ラムダ式のインスタンス