Chúng ta có thể mô phỏng việc triển khai các phương thức mở rộng C # trong Java bằng cách sử dụng triển khai phương thức mặc định có sẵn từ Java 8. Chúng ta bắt đầu bằng cách xác định một giao diện cho phép chúng ta truy cập đối tượng hỗ trợ thông qua phương thức cơ sở (), như vậy:
public interface Extension<T> {
default T base() {
return null;
}
}
Chúng tôi trả về null vì các giao diện không thể có trạng thái, nhưng điều này phải được sửa sau đó thông qua proxy.
Nhà phát triển tiện ích mở rộng sẽ phải mở rộng giao diện này bằng giao diện mới chứa các phương thức mở rộng. Giả sử chúng tôi muốn thêm một người tiêu dùng forEach trên giao diện Danh sách:
public interface ListExtension<T> extends Extension<List<T>> {
default void foreach(Consumer<T> consumer) {
for (T item : base()) {
consumer.accept(item);
}
}
}
Vì chúng tôi mở rộng giao diện Tiện ích mở rộng, chúng tôi có thể gọi phương thức cơ sở () bên trong phương thức tiện ích mở rộng của mình để truy cập đối tượng hỗ trợ mà chúng tôi đính kèm.
Giao diện Tiện ích mở rộng phải có phương thức xuất xưởng sẽ tạo ra phần mở rộng của đối tượng hỗ trợ nhất định:
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;
}
}
}
Chúng tôi tạo một proxy thực hiện giao diện mở rộng và tất cả giao diện được triển khai theo loại đối tượng hỗ trợ. Trình xử lý lời gọi được cung cấp cho proxy sẽ gửi tất cả các cuộc gọi đến đối tượng hỗ trợ, ngoại trừ phương thức "cơ sở", phải trả về đối tượng hỗ trợ, nếu không, việc triển khai mặc định của nó sẽ trả về null:
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);
}
}
Sau đó, chúng ta có thể sử dụng phương thức Extension.create () để đính kèm giao diện chứa phương thức mở rộng vào đối tượng hỗ trợ. Kết quả là một đối tượng có thể được truyền tới giao diện mở rộng mà chúng ta vẫn có thể truy cập đối tượng hỗ trợ gọi phương thức base (). Có tham chiếu được truyền tới giao diện mở rộng, bây giờ chúng ta có thể gọi một cách an toàn các phương thức mở rộng có thể có quyền truy cập vào đối tượng hỗ trợ, để bây giờ chúng ta có thể đính kèm các phương thức mới vào đối tượng hiện có, nhưng không phải là kiểu xác định của nó:
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);
}
}
Vì vậy, đây là cách chúng ta có thể mô phỏng khả năng mở rộng các đối tượng trong Java bằng cách thêm các hợp đồng mới vào chúng, cho phép chúng ta gọi các phương thức bổ sung trên các đối tượng đã cho.
Dưới đây bạn có thể tìm thấy mã của giao diện Tiện ích mở rộng:
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;
}
}
}