Я шукаю реалізувати функціональність у списку об’єктів, як я б в C #, використовуючи метод розширення.
Щось на зразок цього:
List<DataObject> list;
// ... List initialization.
list.getData(id);
Як це зробити на Java?
Я шукаю реалізувати функціональність у списку об’єктів, як я б в C #, використовуючи метод розширення.
Щось на зразок цього:
List<DataObject> list;
// ... List initialization.
list.getData(id);
Як це зробити на Java?
Відповіді:
Java не підтримує методи розширення.
Натомість ви можете зробити звичайний статичний метод або написати власний клас.
Методи розширення - це не просто статичний метод і не просто зручність синтаксичного цукру, адже вони є досить потужним інструментом. Головне - це можливість переосмислювати різні методи, що базуються на інстанціонуванні різних загальних параметрів. Це схоже на типи класів Haskell, і насправді, схоже, вони перебувають у C # для підтримки C # 's Monads (тобто LINQ). Навіть скидаючи синтаксис LINQ, я все ще не знаю жодного способу реалізації подібних інтерфейсів у Java.
І я не думаю, що це можливо реалізувати в Java через семантику стирання стилю Java для генеричних параметрів.
Project Lombok надає примітку, @ExtensionMethod
яка може бути використана для досягнення потрібної вам функції.
java.lang.String
. Демонстрація: http://manifold.systems/images/ExtensionMethod.mp4
Технічно розширення C # не мають еквівалента на 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();
}
}
Мова XTend - це супернабір Java та компілюється у вихідний код Java 1 - це підтримує.
Manifold надає Java способи розширення C # -style та кілька інших функцій. В відміну від інших інструментів, Колектор не має ніяких обмежень і не страждає від проблем з дженерик, лямбда, IDE і т.д. Колектор надає ряд інших функцій , такі як F # -Style призначених для користувача типів , машинопис стилю структурних інтерфейсів і Javascript-стиль типів EXPANDO .
Крім того, IntelliJ забезпечує всебічну підтримку Manifold через плагін Manifold .
Колектор - це проект з відкритим кодом, доступний на github .
Інший варіант - використовувати класи ForwardingXXX з бібліотеки google-guava.
У 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();
Схоже , є деякі невеликі шанси , що Захисник методу (тобто методу по замовчуванням) може зробити це в Java 8. Тим НЕ менше, наскільки я їх розумію, вони тільки дозволяють автор про interface
задньому числі продовжити його, а не довільно користувач.
Методи Defender + Ін'єкція інтерфейсу зможе повністю реалізувати методи розширення C # -style, але AFAICS, Інжекція інтерфейсу ще не існує на дорожній карті 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);
}
}
}
Ми можемо імітувати реалізацію методів розширення C # у Java, використовуючи реалізацію методу за замовчуванням, доступну з Java 8. Почнемо з визначення інтерфейсу, який дозволить нам отримати доступ до об’єкта підтримки за допомогою методу base (), наприклад:
public interface Extension<T> {
default T base() {
return null;
}
}
Ми повертаємо null, оскільки інтерфейси не можуть мати стан, але це потрібно виправити пізніше через проксі.
Розробник розширень повинен був би розширити цей інтерфейс новим інтерфейсом, що містить методи розширення. Скажімо, ми хочемо додати ForEach споживача до інтерфейсу List:
public interface ListExtension<T> extends Extension<List<T>> {
default void foreach(Consumer<T> consumer) {
for (T item : base()) {
consumer.accept(item);
}
}
}
Оскільки ми розширюємо інтерфейс розширення, ми можемо викликати метод base () всередині нашого методу розширення для доступу до об'єкта підтримки, до якого ми приєднуємося.
Інтерфейс розширення повинен мати заводський метод, який створить розширення заданого об’єкта підтримки:
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;
}
}
}
Ми створюємо проксі-сервер, який реалізує інтерфейс розширення та весь інтерфейс, реалізований за типом об'єкта підтримки. Обробник виклику, наданий проксі, відправив би всі виклики до об'єкта підтримки, за винятком методу "base", який повинен повернути об'єкт підтримки, інакше його реалізація за замовчуванням повертає нуль:
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 .
Ось код для розширення функціональності списку:
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's 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);
}
}
PS Я великий фанат Котліна . Він має методи розширення, а також працює на JVM.
Ви можете створити розширення / допоміжний метод типу C #, використовуючи (RE) реалізацію інтерфейсу Collections та додаючи приклад для Java Collection:
public class RockCollection<T extends Comparable<T>> implements Collection<T> {
private Collection<T> _list = new ArrayList<T>();
//###########Custom extension methods###########
public T doSomething() {
//do some stuff
return _list
}
//proper examples
public T find(Predicate<T> predicate) {
return _list.stream()
.filter(predicate)
.findFirst()
.get();
}
public List<T> findAll(Predicate<T> predicate) {
return _list.stream()
.filter(predicate)
.collect(Collectors.<T>toList());
}
public String join(String joiner) {
StringBuilder aggregate = new StringBuilder("");
_list.forEach( item ->
aggregate.append(item.toString() + joiner)
);
return aggregate.toString().substring(0, aggregate.length() - 1);
}
public List<T> reverse() {
List<T> listToReverse = (List<T>)_list;
Collections.reverse(listToReverse);
return listToReverse;
}
public List<T> sort(Comparator<T> sortComparer) {
List<T> listToReverse = (List<T>)_list;
Collections.sort(listToReverse, sortComparer);
return listToReverse;
}
public int sum() {
List<T> list = (List<T>)_list;
int total = 0;
for (T aList : list) {
total += Integer.parseInt(aList.toString());
}
return total;
}
public List<T> minus(RockCollection<T> listToMinus) {
List<T> list = (List<T>)_list;
int total = 0;
listToMinus.forEach(list::remove);
return list;
}
public Double average() {
List<T> list = (List<T>)_list;
Double total = 0.0;
for (T aList : list) {
total += Double.parseDouble(aList.toString());
}
return total / list.size();
}
public T first() {
return _list.stream().findFirst().get();
//.collect(Collectors.<T>toList());
}
public T last() {
List<T> list = (List<T>)_list;
return list.get(_list.size() - 1);
}
//##############################################
//Re-implement existing methods
@Override
public int size() {
return _list.size();
}
@Override
public boolean isEmpty() {
return _list == null || _list.size() == 0;
}
Java
8 тепер підтримує методи за замовчуванням , подібні до C#
методів розширення.