Ответ 1
Java не поддерживает методы расширения.
Вместо этого вы можете создать обычный статический метод или написать свой собственный класс.
Я хочу реализовать функциональность в списке объектов, как я бы в С#, используя метод расширения.
Что-то вроде этого:
List<DataObject> list;
// ... List initialization.
list.getData(id);
Как это сделать на Java?
Java не поддерживает методы расширения.
Вместо этого вы можете создать обычный статический метод или написать свой собственный класс.
Методы расширения - это не просто статический метод, а не просто удобный синтаксис сахара, на самом деле это довольно мощный инструмент. Главное, что есть возможность переопределить различные методы, основанные на создании экземпляров разных генерических параметров. Это похоже на классы типа Haskells, и на самом деле похоже, что они находятся на С# для поддержки Monads С# s (например, LINQ). Даже отбрасывая синтаксис LINQ, я до сих пор не знаю, как реализовать аналогичные интерфейсы в Java.
И я не думаю, что можно реализовать их на Java, из-за семантики стилей стилей Javas от параметров generics.
Проект Lombok содержит аннотацию @ExtensionMethod
, которая могут использоваться для достижения требуемой функциональности.
XTend language — который является супер-набором Java и компилируется в исходный код Java 1 — поддерживает это.
В Java нет такой функции. Вместо этого вы можете либо создать обычный подкласс реализации списка или создать анонимный внутренний класс:
List<String> list = new ArrayList<String>() {
public String getData() {
return ""; // add your implementation here.
}
};
Проблема заключается в вызове этого метода. Вы можете сделать это "на месте":
new ArrayList<String>() {
public String getData() {
return ""; // add your implementation here.
}
}.getData();
Другой вариант - использовать ForwardingXXX из библиотеки google-guava.
Технически расширение С# не имеет аналогов в Java. Но если вы хотите реализовать такие функции для более чистого кода и удобства сопровождения, вы должны использовать платформу Manifold.
package extensions.java.lang.String;
import manifold.ext.api.*;
@Extension
public class MyStringExtension {
public static void print(@This String thiz) {
System.out.println(thiz);
}
@Extension
public static String lineSeparator() {
return System.lineSeparator();
}
}
Коллектор предоставляет Java с С# -style методами расширения и некоторыми другими функциями. В отличие от других инструментов, Manifold не имеет ограничений и не страдает от проблем с обобщениями, лямбдами, IDE и т.д. В Manifold есть несколько других функций, таких как F # -style пользовательские типы, TypeScript -style структурные интерфейсы и Javascript -style расширенные типы.
Кроме того, IntelliJ обеспечивает всестороннюю поддержку Manifold через подключаемый модуль plugin.
Manifold - это проект с открытым исходным кодом, доступный на github.
Похоже, существует небольшая вероятность того, что Defender Methods (т.е. методы по умолчанию) могут попасть в Java 8. Однако, насколько я понимаю, они позволяют автору interface
ретроактивно расширять его, а не произвольные пользователи.
Defender Methods + Interface Injection затем сможет полностью реализовать методы расширения класса С#, но AFAICS, Interface Injection еще не существует на дорожной карте Java 8.
Бит поздно на вечеринку по этому вопросу, но в случае, если кто-либо найдет это полезным, я просто создал подкласс:
public class ArrayList2<T> extends ArrayList<T>
{
private static final long serialVersionUID = 1L;
public T getLast()
{
if (this.isEmpty())
{
return null;
}
else
{
return this.get(this.size() - 1);
}
}
}
Мы можем смоделировать реализацию методов расширения С# в Java, используя реализацию метода по умолчанию, доступную с Java 8. Мы начнем с определения интерфейса, который позволит нам получить доступ к объекту поддержки через метод base(), например, так:
public interface Extension<T> {
default T base() {
return null;
}
}
Мы возвращаем нулевое значение, поскольку интерфейсы не могут иметь состояния, но это необходимо исправить позже через прокси.
Разработчик расширений должен был бы расширить этот интерфейс новым интерфейсом, содержащим методы расширения. Допустим, мы хотим добавить потребителя forEach в интерфейс List:
public interface ListExtension<T> extends Extension<List<T>> {
default void foreach(Consumer<T> consumer) {
for (T item : base()) {
consumer.accept(item);
}
}
}
Поскольку мы расширяем интерфейс Extension, мы можем вызвать метод base() внутри нашего метода extension, чтобы получить доступ к объекту поддержки, к которому мы присоединяемся.
Интерфейс Расширения должен иметь фабричный метод, который создаст расширение данного объекта поддержки:
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;
}
}
}
Мы создаем прокси, который реализует интерфейс расширения и весь интерфейс, реализованный по типу объекта поддержки. Обработчик вызова, данный прокси-серверу, будет отправлять все вызовы к объекту поддержки, кроме "базового" метода, который должен возвращать объект поддержки, в противном случае его реализация по умолчанию возвращает нуль:
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);
}
}
Затем мы можем использовать метод Extension.create(), чтобы присоединить интерфейс, содержащий метод расширения, к объекту поддержки. Результатом является объект, который может быть приведен к интерфейсу расширения, с помощью которого мы все еще можем получить доступ к объекту поддержки, вызывающему метод base(). Приведя ссылку на интерфейс расширения, мы теперь можем безопасно вызывать методы расширения, которые могут иметь доступ к объекту поддержки, чтобы теперь мы могли присоединять новые методы к существующему объекту, но не к его определяющему типу:
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);
}
}
Таким образом, это способ, которым мы можем моделировать возможность расширения объектов в Java, добавляя к ним новые контракты, что позволяет нам вызывать дополнительные методы для указанных объектов.
Ниже вы можете найти код интерфейса расширения:
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;
}
}
}
Можно использовать объект-ориентированный шаблон дизайна декоратора. Примером этого шаблона, который используется в стандартной библиотеке Java, будет DataOutputStream.
Вот некоторый код для расширения функциональности List:
public class ListDecorator<E> implements List<E>
{
public final List<E> wrapee;
public ListDecorator(List<E> wrapee)
{
this.wrapee = wrapee;
}
// implementation of all the list methods here...
public <R> ListDecorator<R> map(Transform<E,R> transformer)
{
ArrayList<R> result = new ArrayList<R>(size());
for (E element : this)
{
R transformed = transformer.transform(element);
result.add(transformed);
}
return new ListDecorator<R>(result);
}
}
P.S. Я большой поклонник Kotlin. Он имеет методы расширения, а также работает на JVM.
Java
8 теперь поддерживает методы по умолчанию, которые похожи на методы расширения C#
.