Szukam zaimplementowania funkcjonalności na liście obiektów, tak jak w C # przy użyciu metody rozszerzenia.
Coś takiego:
List<DataObject> list;
// ... List initialization.
list.getData(id);
Jak to zrobić w Javie?
Szukam zaimplementowania funkcjonalności na liście obiektów, tak jak w C # przy użyciu metody rozszerzenia.
Coś takiego:
List<DataObject> list;
// ... List initialization.
list.getData(id);
Jak to zrobić w Javie?
Odpowiedzi:
Java nie obsługuje metod rozszerzających.
Zamiast tego możesz utworzyć zwykłą metodę statyczną lub napisać własną klasę.
Metody rozszerzeń to nie tylko metoda statyczna, a nie tylko wygodna składnia, w rzeczywistości są one dość potężnym narzędziem. Najważniejsza jest możliwość nadpisywania różnych metod opartych na instancji różnych parametrów generycznych. Jest to podobne do klas typu Haskella i faktycznie wygląda na to, że są w C #, aby obsługiwać monady C # (tj. LINQ). Nawet porzucając składnię LINQ, nadal nie wiem, jak zaimplementować podobne interfejsy w Javie.
I nie sądzę, aby można je było zaimplementować w Javie, ze względu na semantykę parametrów generycznych w Javie.
Projekt Lombok zawiera adnotację, @ExtensionMethod
której można użyć do osiągnięcia żądanej funkcjonalności.
java.lang.String
.. Demonstracja: http://manifold.systems/images/ExtensionMethod.mp4
Technicznie rzecz biorąc, rozszerzenia C # nie mają odpowiednika w Javie. Ale jeśli chcesz zaimplementować takie funkcje, aby uzyskać bardziej przejrzysty kod i łatwość konserwacji, musisz użyć struktury 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();
}
}
Język XTend - który jest super zestawem języków Java i kompiluje się do kodu źródłowego Java 1 - obsługuje to.
Manifold udostępnia Javę z metodami rozszerzeń w stylu C # i kilkoma innymi funkcjami. W przeciwieństwie do innych narzędzi, Manifold nie ma ograniczeń i nie cierpi z powodu problemów z typami ogólnymi, lambdami, IDE itp. Manifold zapewnia kilka innych funkcji, takich jak niestandardowe typy w stylu F # , strukturalne interfejsy w stylu TypeScript i typy expando w stylu JavaScript .
Dodatkowo IntelliJ zapewnia kompleksową obsługę Manifolda poprzez wtyczkę Manifold .
Manifold to projekt open source dostępny na github .
Inną opcją jest użycie klas ForwardingXXX z biblioteki google-guava.
Java nie ma takiej funkcji. Zamiast tego możesz utworzyć zwykłą podklasę swojej implementacji listy lub utworzyć anonimową klasę wewnętrzną:
List<String> list = new ArrayList<String>() {
public String getData() {
return ""; // add your implementation here.
}
};
Problem polega na wywołaniu tej metody. Możesz to zrobić „na miejscu”:
new ArrayList<String>() {
public String getData() {
return ""; // add your implementation here.
}
}.getData();
Wygląda na to jest jakaś mała szansa, że Defender (tj Metody Metody domyślne) może uczynić go Java 8. Jednakże o ile mi ich zrozumieć, pozwalają one tylko autor od Aninterface
wstecznie przedłużyć go, a nie arbitralnych użytkowników.
Defender Methods + Interface Injection byłby wtedy w stanie w pełni zaimplementować metody rozszerzeń w stylu C #, ale AFAICS, Interface Injection nie jest jeszcze na mapie drogowej Java 8.
Trochę za późno na imprezę w tej sprawie, ale na wypadek, gdyby ktoś uznał to za przydatne, właśnie utworzyłem podklasę:
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);
}
}
}
Możemy zasymulować implementację metod rozszerzających C # w Javie, używając domyślnej implementacji metody dostępnej od Java 8. Zaczynamy od zdefiniowania interfejsu, który pozwoli nam na dostęp do obiektu wsparcia za pomocą metody base (), na przykład:
public interface Extension<T> {
default T base() {
return null;
}
}
Zwracamy wartość null, ponieważ interfejsy nie mogą mieć stanu, ale należy to później naprawić za pośrednictwem serwera proxy.
Twórca rozszerzeń musiałby rozszerzyć ten interfejs o nowy interfejs zawierający metody rozszerzające. Powiedzmy, że chcemy dodać konsumenta forEach do interfejsu List:
public interface ListExtension<T> extends Extension<List<T>> {
default void foreach(Consumer<T> consumer) {
for (T item : base()) {
consumer.accept(item);
}
}
}
Ponieważ rozszerzamy interfejs Extension, możemy wywołać metodę base () wewnątrz naszej metody rozszerzenia, aby uzyskać dostęp do obiektu wsparcia, do którego się podłączamy.
Interfejs Extension musi posiadać metodę fabryczną, która utworzy rozszerzenie danego obiektu wsparcia:
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;
}
}
}
Tworzymy proxy, które implementuje interfejs rozszerzenia i cały interfejs zaimplementowany przez typ obiektu wsparcia. Procedura obsługi wywołań przekazana proxy mogłaby wysłać wszystkie wywołania do obiektu wsparcia, z wyjątkiem metody „podstawowej”, która musi zwrócić obiekt wsparcia, w przeciwnym razie jej domyślna implementacja zwraca wartość 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);
}
}
Następnie możemy użyć metody Extension.create (), aby dołączyć interfejs zawierający metodę rozszerzenia do obiektu pomocniczego. Rezultatem jest obiekt, który można rzutować na interfejs rozszerzenia, za pomocą którego nadal możemy uzyskać dostęp do obiektu pomocniczego wywołującego metodę base (). Mając referencję rzuconą na interfejs rozszerzenia, możemy teraz bezpiecznie wywołać metody rozszerzające, które mogą mieć dostęp do obiektu wsparcia, dzięki czemu możemy teraz dołączyć nowe metody do istniejącego obiektu, ale nie do jego definiującego typu:
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);
}
}
Jest to więc sposób, w jaki możemy zasymulować możliwość rozszerzania obiektów w Javie poprzez dodawanie do nich nowych kontraktów, które pozwalają nam wywoływać dodatkowe metody na danych obiektach.
Poniżej znajduje się kod interfejsu rozszerzenia:
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;
}
}
}
Można by wykorzystać wzorzec projektowania obiektowego dekoratora . Przykładem tego wzorca używanego w standardowej bibliotece Javy byłby DataOutputStream .
Oto kod rozszerzający funkcjonalność listy:
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 Jestem wielkim fanem Kotlina . Ma metody rozszerzające, a także działa na JVM.
Możesz stworzyć metodę rozszerzenia / pomocnika podobną do C #, implementując (RE) interfejs Collections i dodając - przykład dla 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 obsługuje teraz metody domyślne , które są podobne do C#
metod rozszerzających.