Lombokの@ExtensionMethod(lombok.experimental.ExtensionMethod)アノテーションの利用サンプルです。
JavaにおいてString
(java.lnag.String)クラスに対して、メソッドを追加することはできません。
Stringに対する新しい操作がほしい場合は、Apache CommonsのStringUtils
クラスやGuavaのStrings
クラスなど、Stringクラスの外側で提供されているAPIを使用することになります。
しかし、@ExtensionMethod
を利用すると、あたかもStringクラス内にStringUtils
クラスやStrings
クラスで提供されてるAPIが追加(拡張)されたような状態にすることができます。
@ExtensionMethod
は所属パッケージが"experimental"になっており、その名の通り実験的なアノテーションです。
そのため、今後Lombokのバージョンが上がった際などに仕様が変更される可能性もあると思われます。
java.lang.StringをApache Commons LangのStringUtilsで拡張する
クラスに@ExtensionMethod
アノテーションを付与し、パラメータにStringUtils.class
を指定して拡張を行います
■@ExtensionMethodでApache Commons LangのStringUtilsを追加
Stringの値に対して、StringUtilsで定義されているメソッド(repeat
, isNumeric
, wrap
等)が使用できていることが分かります
import org.apache.commons.lang3.StringUtils; import lombok.experimental.ExtensionMethod; @ExtensionMethod({StringUtils.class}) public class StringClient { public static void main(String[] args) { String str1 = "Hello World."; String str2 = str1.repeat(5); System.out.println(str1 + ": repeat 5 -> " + str2); String str3 = "123456"; String str4 = "A123456C"; boolean isNumeric1 = str3.isNumeric(); boolean isNumeric2 = str4.isNumeric(); System.out.println(str3 + " is numeric? -> " + isNumeric1); System.out.println(str4 + " is numeric? -> " + isNumeric2); String wrappedStr1 = "hello".wrap("###"); System.out.println("hello wrap ### -> " + wrappedStr1); } }
■実行結果
StringUtilsの各APIの結果が得られていることが分かります
Hello World.: repeat 5 -> Hello World.Hello World.Hello World.Hello World.Hello World. 123456 is numeric? -> true A123456C is numeric? -> false hello wrap ### -> ###hello###
■実際に生成されるソースコード
文字列操作がStringUtilsのメソッドで実行されていることが分かります
import org.apache.commons.lang3.StringUtils; public class StringClient { public static void main(String[] args) { String str1 = "Hello World."; String str2 = org.apache.commons.lang3.StringUtils.repeat(str1, 5); System.out.println(str1 + ": repeat 5 -> " + str2); String str3 = "123456"; String str4 = "A123456C"; boolean isNumeric1 = org.apache.commons.lang3.StringUtils.isNumeric(str3); boolean isNumeric2 = org.apache.commons.lang3.StringUtils.isNumeric(str4); System.out.println(str3 + " is numeric? -> " + isNumeric1); System.out.println(str4 + " is numeric? -> " + isNumeric2); String wrappedStr1 = org.apache.commons.lang3.StringUtils.wrap("hello", "###"); System.out.println("hello wrap ### -> " + wrappedStr1); } }
java.lang.StringをGuavaのStringsで拡張する
クラスに@ExtensionMethod
アノテーションを付与し、パラメータにStrings.class
を指定して拡張を行います
■@ExtensionMethodでGuavaのStringsを追加
Stringの値に対して、GuavaのStrings
で定義されているメソッド(repeat
, padStart
, padEnd
, isNullOrEmpty
等)が使用できていることが分かります
import com.google.common.base.Strings; import lombok.experimental.ExtensionMethod; @ExtensionMethod({Strings.class}) public class StringClient2 { public static void main(String[] args) { String str1 = "Hello World."; String str2 = str1.repeat(5); System.out.println(str1 + ": repeat 5 -> " + str2); String str3 = "abc"; String paddedStr1 = str3.padStart(10, '*'); String paddedStr2 = str3.padEnd(10, '/'); System.out.println(str3 + " pad start. -> " + paddedStr1); System.out.println(str3 + " pad end. -> " + paddedStr2); String str4 = "not empty"; String str5 = ""; String str6 = null; System.out.println("[" + str4 + "] is null or empty? -> " + str4.isNullOrEmpty()); System.out.println("[" + str5 + "] is null or empty? -> " + str5.isNullOrEmpty()); System.out.println("[" + str6 + "] is null or empty? -> " + str6.isNullOrEmpty()); } }
■実行結果
Stringsの各APIの結果が得られていることが分かります
Hello World.: repeat 5 -> Hello World.Hello World.Hello World.Hello World.Hello World. abc pad start. -> *******abc abc pad end. -> abc/////// [not empty] is null or empty? -> false [] is null or empty? -> true [null] is null or empty? -> true
■実際に生成されるソースコード
文字列操作がStringsのメソッドで実行されていることが分かります
import com.google.common.base.Strings; public class StringClient2 { public static void main(String[] args) { String str1 = "Hello World."; String str2 = com.google.common.base.Strings.repeat(str1, 5); System.out.println(str1 + ": repeat 5 -> " + str2); String str3 = "abc"; String paddedStr1 = com.google.common.base.Strings.padStart(str3, 10, '*'); String paddedStr2 = com.google.common.base.Strings.padEnd(str3, 10, '/'); System.out.println(str3 + " pad start. -> " + paddedStr1); System.out.println(str3 + " pad end. -> " + paddedStr2); String str4 = "not empty"; String str5 = ""; String str6 = null; System.out.println("[" + str4 + "] is null or empty? -> " + com.google.common.base.Strings.isNullOrEmpty(str4)); System.out.println("[" + str5 + "] is null or empty? -> " + com.google.common.base.Strings.isNullOrEmpty(str5)); System.out.println("[" + str6 + "] is null or empty? -> " + com.google.common.base.Strings.isNullOrEmpty(str6)); } }
java.lang.Stringを独自クラスで拡張する
自分で拡張用のクラスを定義して、そのクラスを@ExtensionMethod
アノテーションのパラメータに指定することもできます。
(本サンプルではStringExtensions
という独自クラスを定義しています)
■Stringを拡張するための独自クラスを定義
public class StringExtensions { public static String toTitleCase(String in) { return "[" + in + "]"; } public static boolean isHello(String in) { return "Hello".equals(in); } public static int toInt(String in) { return Integer.valueOf(in); } }
■@ExtensionMethodでStringExtensionsを指定する
Stringの値に対して、StringExtensionsクラスで定義されているメソッド(toTitleCase
, isHello
, toInt
)が使用できていることが分かります
import lombok.experimental.ExtensionMethod; @ExtensionMethod({StringExtensions.class}) public class StringClient3 { public static void main(String[] args) { String str1 = "Hello World."; String str2 = "title"; System.out.println(str1 + "#toTitleCase -> " + str1.toTitleCase()); System.out.println(str2 + "#toTitleCase -> " + str2.toTitleCase()); String str3 = "Hello"; String str4 = "Dummy"; System.out.println(str3 + " is Hello? -> " + str3.isHello()); System.out.println(str3 + " is Hello? -> " + str4.isHello()); int val1 = "12345".toInt(); int val2 = "0".toInt(); System.out.println("val1: " + val1); System.out.println("val2: " + val2); } }
■実行結果
StringExtensionsの各APIの結果が得られていることが分かります
Hello World.#toTitleCase -> [Hello World.] title#toTitleCase -> [title] Hello is Hello? -> true Hello is Hello? -> false val1: 12345 val2: 0
■実際に生成されるソースコード
文字列操作がStringExtensionsクラスのメソッドで実行されていることが分かります
import sample.extension.StringExtensions; public class StringClient3 { public static void main(String[] args) { String str1 = "Hello World."; String str2 = "title"; System.out.println(str1 + "#toTitleCase -> " + sample.extension.StringExtensions.toTitleCase(str1)); System.out.println(str2 + "#toTitleCase -> " + sample.extension.StringExtensions.toTitleCase(str2)); String str3 = "Hello"; String str4 = "Dummy"; System.out.println(str3 + " is Hello? -> " + sample.extension.StringExtensions.isHello(str3)); System.out.println(str3 + " is Hello? -> " + sample.extension.StringExtensions.isHello(str4)); int val1 = sample.extension.StringExtensions.toInt("12345"); int val2 = sample.extension.StringExtensions.toInt("0"); System.out.println("val1: " + val1); System.out.println("val2: " + val2); } }
intの操作をGuavaのIntsで拡張する
クラスに@ExtensionMethod
アノテーションを付与し、パラメータにInts.class
を指定して拡張を行います
■@ExtensionMethodでGuavaのIntsを追加
int配列に対して、GuavaのInts
で定義されているメソッド(asList
, max
, min
, contains
等)が使用できていることが分かります
import java.util.List; import com.google.common.primitives.Ints; import lombok.experimental.ExtensionMethod; @ExtensionMethod({Ints.class}) public class IntClient { public static void main(String[] args) { int[] srcArray = new int[] { 1, 2, 3, 10, 5 }; List<Integer> intList = srcArray.asList(); System.out.println("intList: " + intList); System.out.println("srcArray#max: " + srcArray.max()); System.out.println("srcArray#min: " + srcArray.min()); System.out.println("srcArray contains 3? -> " + srcArray.contains(3)); System.out.println("srcArray contains 4? -> " + srcArray.contains(4)); } }
■実行結果
Intsの各APIの結果が得られていることが分かります
intList: [1, 2, 3, 10, 5] srcArray#max: 10 srcArray#min: 1 srcArray contains 3? -> true srcArray contains 4? -> false
■実際に生成されるソースコード
int配列に対する操作がIntsのメソッドで実行されていることが分かります
import java.util.List; import com.google.common.primitives.Ints; public class IntClient { public static void main(String[] args) { int[] srcArray = new int[] {1, 2, 3, 10, 5}; List<Integer> intList = com.google.common.primitives.Ints.asList(srcArray); System.out.println("intList: " + intList); System.out.println("srcArray#max: " + com.google.common.primitives.Ints.max(srcArray)); System.out.println("srcArray#min: " + com.google.common.primitives.Ints.min(srcArray)); System.out.println("srcArray contains 3? -> " + com.google.common.primitives.Ints.contains(srcArray, 3)); System.out.println("srcArray contains 4? -> " + com.google.common.primitives.Ints.contains(srcArray, 4)); } }
汎用的な拡張用クラスを定義して使用する
Genericsを用いて汎用的な拡張用のクラスを定義して、そのクラスを@ExtensionMethod
アノテーションのパラメータに指定することもできます
■@汎用的な拡張用クラスを定義
値がnullならデフォルト値を返すメソッドを持つクラス
public class Extensions { public static <T> T or(T obj, T defaultValue) { if (obj == null) { return defaultValue; } return obj; } }
■@ExtensionMethodでExtensionsを指定する
Extensionsで定義されているメソッド(or
)が使用できていることが分かります
import java.util.Arrays; import java.util.List; import lombok.experimental.ExtensionMethod; @ExtensionMethod({Extensions.class}) public class Client1 { public static void main(String[] args) { String[] strArray = new String[] { "abc", null, "000", null, "AAA" }; for (int i = 0; i < strArray.length; i++) { System.out.println("strArray ellement[ "+ i + "]: " + strArray[i].or("default")); } List<Long> longList = Arrays.asList(100L, null, null, 999L); for (Long l : longList) { System.out.println("longList ellement: " + l.or(-1L)); } } }
■実行結果
Extensionsの各APIの結果が得られていることが分かります
strArray ellement[ 0]: abc strArray ellement[ 1]: default strArray ellement[ 2]: 000 strArray ellement[ 3]: default strArray ellement[ 4]: AAA longList ellement: 100 longList ellement: -1 longList ellement: -1 longList ellement: 999
■実際に生成されるソースコード
Extensionsのメソッドで実行されていることが分かります
import java.util.Arrays; import java.util.List; import sample.extension.Extensions; public class Client1 { public static void main(String[] args) { String[] strArray = new String[] {"abc", null, "000", null, "AAA"}; for (int i = 0; i < strArray.length; i++) { System.out.println("strArray ellement[ " + i + "]: " + sample.extension.Extensions.or(strArray[i], "default")); } List<Long> longList = Arrays.asList(100L, null, null, 999L); for (Long l : longList) { System.out.println("longList ellement: " + sample.extension.Extensions.or(l, -1L)); } } }
補足
@ExtensionMethod
で既存クラスを拡張する場合の弱点として、追加されたメソッドはEclipseのコンテンツ・アシスト(コード補完)機能が使えない点があげられます。- (私の設定が悪い可能性もありますが)IntelliJ IDEA上だと
@ExtensionMethod
はそもそも正常に動作しないように見えます