Java entspricht C # -Erweiterungsmethoden

176

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?

Fabio Milheiro
quelle
8
Überprüfen Sie dieses: github.com/nicholas22/jpropel, Beispiel: new String [] {"james", "john", "john", "eddie"} .where (StartsWith ("j")). Different (); Es verwendet lombok-pg, das die Erweiterungsmethode Güte bietet.
NT_
6
Microsoft hat es definitiv richtig gemacht, als sie Erweiterungen erlaubten. Die Unterklasse zum Hinzufügen neuer Funktionen funktioniert nicht, wenn ich die Funktion in einer Klasse benötige, die an anderer Stelle an mich zurückgegeben wird. Wie das Hinzufügen von Methoden zu String und Datum.
tggagne
3
dh java.lang.String ist eine letzte Klasse, daher können Sie sie nicht erweitern. Die Verwendung statischer Methoden ist eine Möglichkeit, zeigt jedoch manchmal unlesbaren Code. Ich denke, C # hat ein Alter als Computer-Sprache hinterlassen. Erweiterungsmethoden,
Davut Gürbüz
7
@Roadrunner, rofl! Die beste Antwort auf ein fehlendes Sprachmerkmal ist, dass das fehlende Sprachmerkmal böse und unerwünscht ist. Das ist bekannt.
Kirk Woll
6
Erweiterungsmethoden sind nicht "böse". Sie verbessern die Lesbarkeit des Codes erheblich. Nur noch eine der vielen schlechten Designentscheidungen in Java.
Csauve

Antworten:

196

Java unterstützt keine Erweiterungsmethoden.

Stattdessen können Sie eine reguläre statische Methode erstellen oder eine eigene Klasse schreiben.

SLaks
quelle
62
Ich bin verwöhnt, nachdem ich Erweiterungsmethoden verwendet habe - aber auch statische Methoden reichen aus.
Bbqchickenrobot
31
Aber die Syntax ist so schön und macht das Programm leichter verständlich :) Ich mag auch, wie Ruby es Ihnen ermöglicht, fast dasselbe zu tun, außer dass Sie die eingebauten Klassen tatsächlich ändern und neue Methoden hinzufügen können.
bekanntasilya
18
@ Ken: Ja, und das ist der springende Punkt! Warum schreiben Sie in Java und nicht direkt in JVM-Bytecode? Ist es nicht "nur eine Frage der Syntax"?
Fjodor Soikin
30
Erweiterungsmethoden können Code im Vergleich zu zusätzlichen statischen Methoden einer anderen Klasse viel eleganter machen. Fast alle moderneren Sprachen erlauben eine bestehende Klassenerweiterung: C #, PHP, Objective-C, Javascript. Java zeigt hier sicherlich sein Alter. Stellen Sie sich vor, Sie möchten ein JSONObject auf die Festplatte schreiben. Rufen Sie jsonobj.writeToDisk () oder someunrelatedclass.writeToDisk (jsonobj) auf?
Woens
7
Gründe, Java zu hassen, wachsen weiter. Und ich habe vor ein paar Jahren aufgehört, nach ihnen zu suchen ......
John Demetriou
53

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.

user1686250
quelle
Sie ermöglichen es Ihnen auch, mehrere Verhaltensweisen zu erben (ohne Polymorphismus). Sie können mehrere Schnittstellen implementieren, und damit kommen ihre Erweiterungsmethoden. Mit ihnen können Sie auch das Verhalten implementieren, das Sie an einen Typ anhängen möchten, ohne es systemweit global mit dem Typ verknüpft zu haben.
Cleverer Neologismus
25
Diese ganze Antwort ist falsch. Erweiterungsmethoden in C # sind nur syntaktischer Zucker, den der Compiler ein wenig neu anordnet, um das Ziel des Methodenaufrufs auf das erste Argument der statischen Methode zu verschieben. Sie können vorhandene Methoden nicht überschreiben. Es ist nicht erforderlich, dass eine Erweiterungsmethode eine Monade ist. Es ist buchstäblich nur eine bequemere Möglichkeit, eine statische Methode aufzurufen, die den Eindruck erweckt, einer Klasse Instanzmethoden hinzuzufügen. Bitte lesen Sie dies, wenn Sie mit dieser Antwort einverstanden sind
Matt Klein
3
Nun, in diesem Fall definieren Sie, was Syntaxzucker ist. Ich würde einen Syntaxzucker als interne Makrosyntax bezeichnen. Für Erweiterungsmethoden muss der Compiler mindestens die statische Klasse nachschlagen, die die Erweiterungsmethode ersetzen soll. Es gibt nichts in der Antwort über die Methode sollte Monade sein, was unsinnig ist. Sie können es auch zum Überladen verwenden, es handelt sich jedoch nicht um eine Funktion für Erweiterungsmethoden. Es handelt sich um ein einfaches Überladen auf der Basis von Parametertypen, genauso wie es funktionieren würde, wenn die Methode direkt aufgerufen wird, und es funktioniert in vielen interessanten Fällen in Java nicht aufgrund von generischen Typargumenten löschen.
user1686250
1
@ user1686250 Es ist möglich, in Java zu implementieren (mit "Java" meine ich Bytecode, der auf einer JVM ausgeführt wird) ... Kotlin, das zu Bytecode kompiliert wird, hat Erweiterungen. Es ist nur syntaktischer Zucker über statische Methoden. Sie können den Dekompiler in IntelliJ verwenden, um zu sehen, wie das entsprechende Java aussieht.
Jeffrey Blattman
@ user1686250 Könnten Sie entwickeln, was Sie über Generika schreiben (oder einen Link geben), weil ich den Punkt über Generika absolut nicht verstehe. Auf welche andere als die üblichen statischen Methoden bezieht es sich?
C.Champagne
10

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();
  }
}
M. Laida
quelle
7

Die XTend- Sprache - eine Super-Menge von Java, die zu Java-Quellcode 1 kompiliert wird  - unterstützt dies.

Sam Keays
quelle
Wenn dieser Code, der nicht Java ist, zu Java kompiliert wird, haben Sie eine Erweiterungsmethode? Oder ist der Java-Code nur eine statische Methode?
Fabio Milheiro
@Bomboca Wie andere angemerkt haben, verfügt Java nicht über Erweiterungsmethoden. XTend-Code, der in Java kompiliert wurde, erstellt also keine Java-Erweiterungsmethode. Wenn Sie jedoch ausschließlich in XTend arbeiten, werden Sie es nicht bemerken oder sich darum kümmern. Um Ihre Frage zu beantworten, müssen Sie jedoch auch nicht unbedingt über eine statische Methode verfügen. Der Hauptautor von XTend hat einen Blogeintrag dazu unter blog.efftinge.de/2011/11/…
Erick G. Hagstrom
Ja, ich weiß nicht, warum ich das nicht auch gedacht habe. Vielen Dank!
Fabio Milheiro
@ Sam Danke, dass du mich mit XTend bekannt gemacht hast - ich hatte noch nie davon gehört.
Jpaugh
7

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 .

Scott
quelle
6

Eine weitere Option ist die Verwendung von ForwardingXXX- Klassen aus der Google-Guava-Bibliothek.

Aravind Yarram
quelle
5

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();
AlexR
quelle
112
Das ist völlig nutzlos.
SLaks
2
@ Slaks: Warum genau? Dies ist ein von Ihnen selbst vorgeschlagenes "Schreiben Sie Ihre eigene Klasse".
Goran Jovic
23
@Goran: Sie können lediglich eine Methode definieren und sie dann sofort einmal aufrufen .
SLaks
3
@ Slaks: Also gut, Punkt genommen. Im Vergleich zu dieser eingeschränkten Lösung wäre es besser, eine benannte Klasse zu schreiben.
Goran Jovic
Es gibt einen großen Unterschied zwischen C # -Erweiterungsmethoden und anonymen Java-Klassen. In C # ist eine Erweiterungsmethode syntaktischer Zucker für eine eigentlich nur statische Methode. Die IDE und der Compiler lassen eine Erweiterungsmethode so erscheinen, als wäre sie eine Instanzmethode der erweiterten Klasse. (Hinweis: "erweitert" bedeutet in diesem Zusammenhang nicht "geerbt", wie es normalerweise in Java der
Fall
4

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.

Jörg W Mittag
quelle
3

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);
        }
    }
}
magritte
quelle
4
Erweiterungsmethoden gelten normalerweise für Code, der nicht wie endgültige / versiegelte Klassen geändert oder vererbt werden kann. Die Hauptleistung liegt in der Erweiterung von Schnittstellen, z. B. der Erweiterung von IEnumerable <T>. Natürlich sind sie nur syntaktischer Zucker für statische Methoden. Der Zweck ist, dass der Code viel besser lesbar ist. Sauberer Code bedeutet bessere Wartbarkeit / Evolvabilität.
mbx
1
Das ist nicht nur @mbx. Erweiterungsmethoden sind auch nützlich, um die Klassenfunktionalität nicht versiegelter Klassen zu erweitern, die Sie jedoch nicht erweitern können, da Sie nicht steuern, was Instanzen zurückgibt, z. B. HttpContextBase, eine abstrakte Klasse.
Fabio Milheiro
@FabioMilheiro Ich habe in diesem Zusammenhang großzügig abstrakte Klassen als "Schnittstellen" aufgenommen. Automatisch generierte Klassen (xsd.exe) sind von der gleichen Art: Sie können, sollten sie aber nicht erweitern, indem Sie die generierten Dateien ändern. Normalerweise würden Sie sie mit "partiell" erweitern, sodass sie sich in derselben Assembly befinden müssen. Wenn dies nicht der Fall ist, sind Erweiterungsmethoden eine hübsche Alternative. Letztendlich handelt es sich nur um statische Methoden (es gibt keinen Unterschied, wenn Sie sich den generierten IL-Code ansehen).
mbx
Ja ... HttpContextBase ist eine Abstraktion, obwohl ich Ihre Großzügigkeit verstehe. Eine Schnittstelle als Abstraktion zu bezeichnen, mag etwas natürlicher erscheinen. Unabhängig davon meinte ich nicht, dass es eine Abstraktion sein musste. Ich habe nur ein Beispiel für eine Klasse gegeben, für die ich viele Erweiterungsmethoden geschrieben habe.
Fabio Milheiro
2

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;
        }
    }

}
Iulian Ilie-Némedi
quelle
1
das ist ein verdammt kludge!
Dmitry Avtonomov
1

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.

Eric
quelle
0

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;
}
GarethReid
quelle
-7

Java8 unterstützt jetzt Standardmethoden , die den C#Erweiterungsmethoden ähneln .

DarVar
quelle
8
Falsch; Das Beispiel in dieser Frage ist immer noch unmöglich.
SLaks
@SLaks Was ist der Unterschied zwischen Java- und C # -Erweiterungen?
Fabio Milheiro
3
Standardmethoden können nur innerhalb der Schnittstelle definiert werden. docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
SLaks
DarVar, dieser Kommentarthread war der einzige Ort, an dem Standardmethoden erwähnt wurden, an die ich mich verzweifelt erinnern wollte. Danke, dass du sie erwähnt hast, wenn nicht namentlich! :-) (Danke @SLaks für den Link)
jpaugh
Ich würde diese Antwort, weil das Endergebnis aus einer statischen Methode in der Schnittstelle upvote den gleichen Einsatz wie C # Erweiterungsmethoden würde aber Sie müssen noch die Schnittstelle in der Klasse im Gegensatz zu C # implementieren Sie (das) Stichwort als Parameter übergeben
zaPlayer