Ich möchte eine Funktionalität in einer Objektliste wie in C # mithilfe einer Erweiterungsmethode implementieren.
Etwas wie das:
List<DataObject> list;
// ... List initialization.
list.getData(id);
Wie mache ich das in Java?
Ich möchte eine Funktionalität in einer Objektliste wie in C # mithilfe einer Erweiterungsmethode implementieren.
Etwas wie das:
List<DataObject> list;
// ... List initialization.
list.getData(id);
Wie mache ich das in Java?
Antworten:
Java unterstützt keine Erweiterungsmethoden.
Stattdessen können Sie eine reguläre statische Methode erstellen oder eine eigene Klasse schreiben.
Erweiterungsmethoden sind nicht nur statische Methoden und nicht nur Convenience-Syntaxzucker, sondern auch ein recht leistungsfähiges Werkzeug. Die Hauptsache ist die Möglichkeit, verschiedene Methoden basierend auf der Instanziierung der Parameter verschiedener Generika zu überschreiben. Dies ähnelt den Typklassen von Haskell, und es sieht tatsächlich so aus, als wären sie in C #, um C # -Monaden (dh LINQ) zu unterstützen. Selbst wenn ich die LINQ-Syntax fallen lasse, weiß ich immer noch nicht, wie ich ähnliche Schnittstellen in Java implementieren kann.
Und ich denke nicht, dass es möglich ist, sie in Java zu implementieren, da Java die Semantik der Löschung von generischen Parametern löscht.
Project Lombok bietet eine Anmerkung @ExtensionMethod
, mit der Sie die von Ihnen gewünschte Funktionalität erreichen können.
java.lang.String
. Demonstration: http://manifold.systems/images/ExtensionMethod.mp4
Technisch gesehen hat die C # -Erweiterung in Java keine Entsprechung. Wenn Sie solche Funktionen jedoch für einen saubereren Code und eine sauberere Wartbarkeit implementieren möchten, müssen Sie das Manifold-Framework verwenden.
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();
}
}
Die XTend- Sprache - eine Super-Menge von Java, die zu Java-Quellcode 1 kompiliert wird - unterstützt dies.
Manifold bietet Java Erweiterungsmethoden im C # -Stil und verschiedene andere Funktionen. Im Gegensatz zu anderen Tools, hat Manifold keine Einschränkungen und nicht leiden von Problemen mit Generika, Lambda - Ausdrücke, IDE usw. Manifold bietet verschiedene andere Funktionen wie F # -Stil benutzerdefinierte Typen , Typoskript-Stil strukturelle Schnittstellen und Javascript-Stil expando Typen .
Darüber hinaus bietet IntelliJ umfassende Unterstützung für Manifold über das Manifold- Plugin .
Manifold ist ein Open Source-Projekt, das auf github verfügbar ist .
Eine weitere Option ist die Verwendung von ForwardingXXX- Klassen aus der Google-Guava-Bibliothek.
Java hat keine solche Funktion. Stattdessen können Sie entweder eine reguläre Unterklasse Ihrer Listenimplementierung erstellen oder eine anonyme innere Klasse erstellen:
List<String> list = new ArrayList<String>() {
public String getData() {
return ""; // add your implementation here.
}
};
Das Problem besteht darin, diese Methode aufzurufen. Sie können es "an Ort und Stelle" tun:
new ArrayList<String>() {
public String getData() {
return ""; // add your implementation here.
}
}.getData();
Es sieht so aus, als ob es eine kleine Chance gibt, dass Defender-Methoden (dh Standardmethoden) es in Java 8 schaffen. Soweit ich sie verstehe, erlauben sie jedoch nur den Autor einesinterface
, es rückwirkend zu erweitern, nicht willkürlichen Benutzern.
Defender Methods + Interface Injection könnten dann Erweiterungsmethoden im C # -Stil vollständig implementieren, aber AFAICS, Interface Injection ist noch nicht einmal auf der Java 8-Roadmap.
Etwas spät zur Party in dieser Frage, aber falls jemand es nützlich findet, habe ich gerade eine Unterklasse erstellt:
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);
}
}
}
Wir können die Implementierung von C # -Erweiterungsmethoden in Java mithilfe der seit Java 8 verfügbaren Standardmethodenimplementierung simulieren. Zunächst definieren wir eine Schnittstelle, über die wir über eine base () -Methode auf das Support-Objekt zugreifen können:
public interface Extension<T> {
default T base() {
return null;
}
}
Wir geben null zurück, da Schnittstellen keinen Status haben können, dies muss jedoch später über einen Proxy behoben werden.
Der Entwickler von Erweiterungen müsste diese Schnittstelle um eine neue Schnittstelle erweitern, die Erweiterungsmethoden enthält. Angenommen, wir möchten einen forEach-Consumer auf der List-Oberfläche hinzufügen:
public interface ListExtension<T> extends Extension<List<T>> {
default void foreach(Consumer<T> consumer) {
for (T item : base()) {
consumer.accept(item);
}
}
}
Da wir die Erweiterungsschnittstelle erweitern, können wir die base () -Methode innerhalb unserer Erweiterungsmethode aufrufen, um auf das Support-Objekt zuzugreifen, an das wir uns anhängen.
Die Erweiterungsschnittstelle muss über eine Factory-Methode verfügen, mit der eine Erweiterung eines bestimmten Unterstützungsobjekts erstellt wird:
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;
}
}
}
Wir erstellen einen Proxy, der die Erweiterungsschnittstelle und die gesamte vom Typ des Unterstützungsobjekts implementierte Schnittstelle implementiert. Der dem Proxy übergebene Aufrufhandler würde alle Aufrufe an das Support-Objekt senden, mit Ausnahme der "base" -Methode, die das Support-Objekt zurückgeben muss, andernfalls gibt die Standardimplementierung null zurück:
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);
}
}
Anschließend können wir die Extension.create () -Methode verwenden, um die Schnittstelle mit der Erweiterungsmethode an das Support-Objekt anzuhängen. Das Ergebnis ist ein Objekt, das in die Erweiterungsschnittstelle umgewandelt werden kann, über die wir weiterhin auf das Unterstützungsobjekt zugreifen können, das die base () -Methode aufruft. Nachdem die Referenz in die Erweiterungsschnittstelle umgewandelt wurde, können wir jetzt sicher die Erweiterungsmethoden aufrufen, die Zugriff auf das Unterstützungsobjekt haben können, sodass wir jetzt neue Methoden an das vorhandene Objekt anhängen können, jedoch nicht an den definierenden Typ:
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);
}
}
Auf diese Weise können wir die Möglichkeit simulieren, Objekte in Java zu erweitern, indem wir ihnen neue Verträge hinzufügen, mit denen wir zusätzliche Methoden für die angegebenen Objekte aufrufen können.
Unten finden Sie den Code der Erweiterungsschnittstelle:
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;
}
}
}
Man könnte das objektorientierte Designmuster des Dekorateurs verwenden . Ein Beispiel für dieses Muster, das in der Standardbibliothek von Java verwendet wird, ist der DataOutputStream .
Hier ist ein Code zum Erweitern der Funktionalität einer Liste:
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 Ich bin ein großer Fan von Kotlin . Es verfügt über Erweiterungsmethoden und läuft auch auf der JVM.
Sie können eine C # -ähnliche Erweiterungs- / Hilfsmethode erstellen, indem Sie (RE) die Collections-Schnittstelle implementieren und ein Beispiel für Java Collection hinzufügen:
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 unterstützt jetzt Standardmethoden , die den C#
Erweiterungsmethoden ähneln .