Sto cercando di implementare una funzionalità in un elenco di oggetti come farei in C # usando un metodo di estensione.
Qualcosa come questo:
List<DataObject> list;
// ... List initialization.
list.getData(id);
Come posso farlo in Java?
Sto cercando di implementare una funzionalità in un elenco di oggetti come farei in C # usando un metodo di estensione.
Qualcosa come questo:
List<DataObject> list;
// ... List initialization.
list.getData(id);
Come posso farlo in Java?
Risposte:
Java non supporta i metodi di estensione.
Invece, puoi creare un metodo statico regolare o scrivere la tua classe.
I metodi di estensione non sono solo metodi statici e non solo zucchero di sintassi per praticità, in realtà sono strumenti abbastanza potenti. La cosa principale è la capacità di scavalcare metodi diversi basati sull'istanza di parametri di diversi generici. Questo è simile alle classi di tipi di Haskell, e in effetti sembra che siano in C # per supportare le Monadi di C # (cioè LINQ). Anche lasciando cadere la sintassi LINQ, non conosco ancora alcun modo per implementare interfacce simili in Java.
E non penso che sia possibile implementarli in Java, a causa della semantica di cancellazione dei parametri generici del tipo Java.
Project Lombok fornisce un'annotazione @ExtensionMethod
che può essere utilizzata per ottenere la funzionalità richiesta.
java.lang.String
. Dimostrazione: http://manifold.systems/images/ExtensionMethod.mp4
Tecnicamente l'estensione C # non ha equivalenti in Java. Ma se si desidera implementare tali funzioni per un codice più pulito e manutenibilità, è necessario utilizzare il framework 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();
}
}
Il linguaggio XTend - che è un super-set di Java e si compila nel codice sorgente Java 1 - supporta questo.
Manifold fornisce a Java metodi di estensione in stile C # e diverse altre funzionalità. A differenza di altri strumenti, Manifold non ha limiti e non soffre di problemi con generici, lambda, IDE ecc. Manifold offre molte altre funzionalità come tipi personalizzati in stile F # , interfacce strutturali in stile TypeScript e tipi expando in stile Javascript .
Inoltre, IntelliJ fornisce un supporto completo per Manifold tramite il plug-in Manifold .
Manifold è un progetto open source disponibile su github .
Un'altra opzione è quella di utilizzare le classi ForwardingXXX dalla libreria google-guava.
Java non ha questa funzionalità. Puoi invece creare una sottoclasse regolare dell'implementazione dell'elenco o creare una classe interna anonima:
List<String> list = new ArrayList<String>() {
public String getData() {
return ""; // add your implementation here.
}
};
Il problema è chiamare questo metodo. Puoi farlo "sul posto":
new ArrayList<String>() {
public String getData() {
return ""; // add your implementation here.
}
}.getData();
Sembra che ci siano alcune piccole possibilità che Defender Methods (ovvero i metodi predefiniti) possano farcela in Java 8. Tuttavia, per quanto li capisco, consentono solo all'autore di uninterface
estenderlo retroattivamente, non utenti arbitrari.
Defender Methods + Interface Injection sarebbe quindi in grado di implementare completamente i metodi di estensione in stile C #, ma AFAICS, Interface Injection non è ancora sulla road map di Java 8.
Un po 'tardi alla festa su questa domanda, ma nel caso qualcuno lo trovi utile ho appena creato una sottoclasse:
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);
}
}
}
Possiamo simulare l'implementazione dei metodi di estensione C # in Java utilizzando l'implementazione del metodo predefinito disponibile da Java 8. Iniziamo definendo un'interfaccia che ci permetterà di accedere all'oggetto di supporto tramite un metodo base (), in questo modo:
public interface Extension<T> {
default T base() {
return null;
}
}
Restituiamo null poiché le interfacce non possono avere stato, ma questo deve essere corretto in seguito tramite un proxy.
Lo sviluppatore di estensioni dovrebbe estendere questa interfaccia con una nuova interfaccia contenente metodi di estensione. Diciamo che vogliamo aggiungere un utente forEach sull'interfaccia List:
public interface ListExtension<T> extends Extension<List<T>> {
default void foreach(Consumer<T> consumer) {
for (T item : base()) {
consumer.accept(item);
}
}
}
Poiché estendiamo l'interfaccia di estensione, possiamo chiamare il metodo base () all'interno del nostro metodo di estensione per accedere all'oggetto di supporto a cui ci colleghiamo.
L'interfaccia Extension deve avere un metodo factory che creerà un'estensione di un determinato oggetto di supporto:
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;
}
}
}
Creiamo un proxy che implementa l'interfaccia di estensione e tutta l'interfaccia implementata dal tipo di oggetto di supporto. Il gestore di invocazione fornito al proxy invierebbe tutte le chiamate all'oggetto di supporto, ad eccezione del metodo "base", che deve restituire l'oggetto di supporto, altrimenti la sua implementazione predefinita restituisce 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);
}
}
Quindi, possiamo usare il metodo Extension.create () per collegare l'interfaccia contenente il metodo di estensione all'oggetto di supporto. Il risultato è un oggetto che può essere trasmesso all'interfaccia di estensione tramite la quale possiamo ancora accedere all'oggetto di supporto chiamando il metodo base (). Dopo aver eseguito il cast del riferimento all'interfaccia di estensione, ora possiamo tranquillamente chiamare i metodi di estensione che possono avere accesso all'oggetto di supporto, in modo che ora possiamo collegare nuovi metodi all'oggetto esistente, ma non al suo tipo di definizione:
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);
}
}
Quindi, questo è un modo in cui possiamo simulare la possibilità di estendere oggetti in Java aggiungendo nuovi contratti ad essi, che ci consentono di chiamare metodi aggiuntivi su oggetti dati.
Di seguito puoi trovare il codice dell'interfaccia Extension:
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;
}
}
}
Si potrebbe usare il modello di design orientato agli oggetti del decoratore . Un esempio di questo modello utilizzato nella libreria standard di Java sarebbe DataOutputStream .
Ecco un po 'di codice per aumentare la funzionalità di un elenco:
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 Sono un grande fan di Kotlin . Ha metodi di estensione e funziona anche su JVM.
È possibile creare un metodo di estensione / helper simile a C # implementando (RE) l'interfaccia di Collections e aggiungendo un esempio per 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 ora supporta i metodi predefiniti , che sono simili ai C#
metodi di estensione.