Kita dapat mensimulasikan implementasi metode ekstensi C # di Java dengan menggunakan implementasi metode default yang tersedia sejak Java 8. Kita mulai dengan mendefinisikan antarmuka yang akan memungkinkan kita untuk mengakses objek dukungan melalui metode base (), seperti:
public interface Extension<T> {
default T base() {
return null;
}
}
Kami mengembalikan nol karena antarmuka tidak dapat memiliki keadaan, tetapi ini harus diperbaiki nanti melalui proxy.
Pengembang ekstensi harus memperluas antarmuka ini dengan antarmuka baru yang berisi metode ekstensi. Katakanlah kita ingin menambahkan konsumen forEach pada antarmuka Daftar:
public interface ListExtension<T> extends Extension<List<T>> {
default void foreach(Consumer<T> consumer) {
for (T item : base()) {
consumer.accept(item);
}
}
}
Karena kami memperluas antarmuka Ekstensi, kami dapat memanggil metode base () di dalam metode ekstensi kami untuk mengakses objek dukungan yang kami lampirkan.
Antarmuka Ekstensi harus memiliki metode pabrik yang akan membuat ekstensi objek dukungan yang diberikan:
public interface Extension<T> {
...
static <E extends Extension<T>, T> E create(Class<E> type, T instance) {
if (type.isInterface()) {
ExtensionHandler<T> handler = new ExtensionHandler<T>(instance);
List<Class<?>> interfaces = new ArrayList<Class<?>>();
interfaces.add(type);
Class<?> baseType = type.getSuperclass();
while (baseType != null && baseType.isInterface()) {
interfaces.add(baseType);
baseType = baseType.getSuperclass();
}
Object proxy = Proxy.newProxyInstance(
Extension.class.getClassLoader(),
interfaces.toArray(new Class<?>[interfaces.size()]),
handler);
return type.cast(proxy);
} else {
return null;
}
}
}
Kami membuat proksi yang mengimplementasikan antarmuka ekstensi dan semua antarmuka yang diterapkan oleh jenis objek dukungan. Penangan doa yang diberikan kepada proxy akan mengirimkan semua panggilan ke objek dukungan, kecuali untuk metode "basis", yang harus mengembalikan objek dukungan, jika tidak, implementasi defaultnya adalah mengembalikan nol:
public class ExtensionHandler<T> implements InvocationHandler {
private T instance;
private ExtensionHandler(T instance) {
this.instance = instance;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if ("base".equals(method.getName())
&& method.getParameterCount() == 0) {
return instance;
} else {
Class<?> type = method.getDeclaringClass();
MethodHandles.Lookup lookup = MethodHandles.lookup()
.in(type);
Field allowedModesField = lookup.getClass().getDeclaredField("allowedModes");
makeFieldModifiable(allowedModesField);
allowedModesField.set(lookup, -1);
return lookup
.unreflectSpecial(method, type)
.bindTo(proxy)
.invokeWithArguments(args);
}
}
private static void makeFieldModifiable(Field field) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField
.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}
}
Kemudian, kita bisa menggunakan metode Extension.create () untuk melampirkan antarmuka yang berisi metode ekstensi ke objek dukungan. Hasilnya adalah sebuah objek yang dapat dilemparkan ke antarmuka ekstensi dimana kita masih dapat mengakses objek dukungan yang memanggil metode base (). Setelah referensi dilemparkan ke antarmuka ekstensi, kita sekarang dapat memanggil metode ekstensi dengan aman yang dapat memiliki akses ke objek dukungan, sehingga sekarang kita dapat melampirkan metode baru ke objek yang ada, tetapi tidak dengan jenis definisi:
public class Program {
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c");
ListExtension<String> listExtension = Extension.create(ListExtension.class, list);
listExtension.foreach(System.out::println);
}
}
Jadi, ini adalah cara kita dapat mensimulasikan kemampuan untuk memperluas objek di Jawa dengan menambahkan kontrak baru kepada mereka, yang memungkinkan kita untuk memanggil metode tambahan pada objek yang diberikan.
Di bawah ini Anda dapat menemukan kode antarmuka Ekstensi:
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
public interface Extension<T> {
public class ExtensionHandler<T> implements InvocationHandler {
private T instance;
private ExtensionHandler(T instance) {
this.instance = instance;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if ("base".equals(method.getName())
&& method.getParameterCount() == 0) {
return instance;
} else {
Class<?> type = method.getDeclaringClass();
MethodHandles.Lookup lookup = MethodHandles.lookup()
.in(type);
Field allowedModesField = lookup.getClass().getDeclaredField("allowedModes");
makeFieldModifiable(allowedModesField);
allowedModesField.set(lookup, -1);
return lookup
.unreflectSpecial(method, type)
.bindTo(proxy)
.invokeWithArguments(args);
}
}
private static void makeFieldModifiable(Field field) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}
}
default T base() {
return null;
}
static <E extends Extension<T>, T> E create(Class<E> type, T instance) {
if (type.isInterface()) {
ExtensionHandler<T> handler = new ExtensionHandler<T>(instance);
List<Class<?>> interfaces = new ArrayList<Class<?>>();
interfaces.add(type);
Class<?> baseType = type.getSuperclass();
while (baseType != null && baseType.isInterface()) {
interfaces.add(baseType);
baseType = baseType.getSuperclass();
}
Object proxy = Proxy.newProxyInstance(
Extension.class.getClassLoader(),
interfaces.toArray(new Class<?>[interfaces.size()]),
handler);
return type.cast(proxy);
} else {
return null;
}
}
}