Java équivalent aux méthodes d'extension C #

176

Je cherche à implémenter une fonctionnalité dans une liste d'objets comme je le ferais en C # en utilisant une méthode d'extension.

Quelque chose comme ça:

List<DataObject> list;
// ... List initialization.
list.getData(id);

Comment faire cela en Java?

Fabio Milheiro
la source
8
Vérifiez celui-ci: github.com/nicholas22/jpropel, exemple: new String [] {"james", "john", "john", "eddie"} .where (startsWith ("j")). Distinct (); Il utilise lombok-pg qui fournit la qualité de la méthode d'extension.
NT_
6
Microsoft a définitivement raison quand ils ont autorisé les extensions. Le sous-classement pour ajouter de nouvelles fonctionnalités ne fonctionne pas si j'ai besoin que la fonction dans une classe me soit renvoyée ailleurs. Comme ajouter des méthodes à String et Date.
tggagne
3
c'est-à-dire que java.lang.String est une classe finale, vous ne pouvez donc pas l'étendre. Utiliser des méthodes statiques est un moyen, mais cela montre parfois du code illisible. Je pense que C # a laissé un âge en tant que langage informatique. Méthodes d'extension, classes partielles, LINQ et ainsi de suite ..
Davut Gürbüz
7
@Roadrunner, rofl! La meilleure réponse à une fonctionnalité de langue manquante est que ladite fonctionnalité de langue manquante est mauvaise et indésirable. Ceci est connu.
Kirk Woll
6
Les méthodes d'extension ne sont pas «mauvaises». Ils améliorent considérablement la lisibilité du code. Encore une des nombreuses mauvaises décisions de conception en Java.
csauve

Réponses:

196

Java ne prend pas en charge les méthodes d'extension.

Au lieu de cela, vous pouvez créer une méthode statique régulière ou écrire votre propre classe.

SLaks
la source
63
Je suis gâté après avoir utilisé des méthodes d'extension - mais les méthodes statiques feront également l'affaire.
bbqchickenrobot
31
Mais la syntaxe est tellement agréable et rend le programme plus facile à comprendre :) J'aime aussi la façon dont Ruby vous permet de faire presque la même chose, sauf que vous pouvez en fait modifier les classes intégrées et ajouter de nouvelles méthodes.
knownasilya
18
@Ken: Oui, et c'est tout le problème! Pourquoi écrivez-vous en Java et pas directement en byte code JVM? N'est-ce pas "juste une question de syntaxe"?
Fyodor Soikin
30
Les méthodes d'extension peuvent rendre le code beaucoup plus élégant par rapport aux méthodes statiques supplémentaires d'une autre classe. Presque tous les langages plus modernes permettent une extension de classe existante: C #, php, objective-c, javascript. Java montre sûrement son âge ici. Imaginez que vous vouliez écrire un JSONObject sur le disque. Appelez-vous jsonobj.writeToDisk () ou someunrelatedclass.writeToDisk (jsonobj)?
woens
9
Les raisons de détester Java ne cessent de croître. Et j'ai arrêté de les chercher il y a quelques années ......
John Demetriou
54

Les méthodes d'extension ne sont pas seulement des méthodes statiques et pas seulement du sucre de syntaxe pratique, en fait, elles sont des outils assez puissants. L'essentiel est de pouvoir remplacer différentes méthodes basées sur l'instanciation de différents paramètres génériques. Ceci est similaire aux classes de types de Haskell, et en fait, il semble qu'elles soient en C # pour supporter les monades de C # (c'est-à-dire LINQ). Même en abandonnant la syntaxe LINQ, je ne connais toujours aucun moyen d'implémenter des interfaces similaires en Java.

Et je ne pense pas qu'il soit possible de les implémenter en Java, à cause de la sémantique d'effacement de type Java des paramètres génériques.

user1686250
la source
Ils vous permettent également d'hériter de plusieurs comportements (sans polymorphisme). Vous pouvez implémenter plusieurs interfaces, et avec cela vient leurs méthodes d'extension. Ils vous permettent également d'implémenter le comportement que vous souhaitez attacher à un type sans qu'il soit globalement associé au type à l'échelle du système.
Clever Neologism
25
Toute cette réponse est fausse. Les méthodes d'extension en C # ne sont que du sucre syntaxique que le compilateur réorganise un peu pour déplacer la cible de l'appel de méthode vers le premier argument de la méthode statique. Vous ne pouvez pas remplacer les méthodes existantes. Il n'est pas nécessaire qu'une méthode d'extension soit une monade. C'est littéralement un moyen plus pratique d'appeler une méthode statique, ce qui donne l'apparence d'ajouter des méthodes d'instance à une classe. Veuillez lire ceci si vous êtes d'accord avec cette réponse
Matt Klein
3
eh bien, dans ce cas, définissez ce qu'est le sucre de syntaxe, j'appellerais un sucre de syntaxe une macro syntaxe interne, pour les méthodes d'extension, le compilateur doit au moins rechercher la classe statique que la méthode d'extension est située à remplacer. Il n'y a rien dans la réponse à propos de la méthode devrait être monade qui n'a pas de sens. En outre, vous pouvez l'utiliser pour la surcharge, mais ce n'est pas une fonctionnalité de méthodes d'extension, c'est une surcharge basée sur un type de paramètre simple, de la même manière que cela fonctionnerait si la méthode est appelée directement, et cela ne fonctionnera pas dans de nombreux cas intéressants en Java en raison de l'effacement des arguments de type générique.
user1686250
1
@ user1686250 Il est possible d'implémenter en Java (par "Java" je suppose que vous entendez le bytecode qui fonctionne sur une JVM) ... Kotlin, qui compile en bytecode a des extensions. C'est juste du sucre syntaxique par rapport aux méthodes statiques. Vous pouvez utiliser le décompilateur dans IntelliJ pour voir à quoi ressemble l'équivalent Java.
Jeffrey Blattman
@ user1686250 Pourriez-vous développer ce que vous écrivez sur les génériques (ou donner un lien) parce que je ne comprends absolument pas les génériques. De quelle manière est-il lié aux méthodes statiques habituelles?
C.Champagne
10

Techniquement, l'extension C # n'a pas d'équivalent en Java. Mais si vous souhaitez implémenter de telles fonctions pour un code plus propre et une maintenabilité, vous devez utiliser le 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();
  }
}
M. Laida
la source
7

Le langage XTend - qui est un super-ensemble de Java, et se compile en code source Java 1  - le prend en charge.

Sam Keays
la source
Lorsque ce code qui n'est pas Java est compilé en Java, avez-vous une méthode d'extension? Ou le code Java n'est-il qu'une méthode statique?
Fabio Milheiro
@Bomboca Comme d'autres l'ont noté, Java n'a pas de méthodes d'extension. Ainsi, le code XTend, compilé en Java, ne crée pas en quelque sorte une méthode d'extension Java. Mais si vous travaillez exclusivement dans XTend, vous ne le remarquerez pas et ne vous en soucierez pas. Mais, pour répondre à votre question, vous n'avez pas forcément non plus de méthode statique. L'auteur principal de XTend a une entrée de blog à ce sujet sur blog.efftinge.de/2011/11/…
Erick G. Hagstrom
Oui, je ne sais pas pourquoi je ne pensais pas ça aussi. Merci!
Fabio Milheiro
@Sam Merci de m'avoir présenté XTend - je n'en avais jamais entendu parler.
jpaugh
7

Manifold fournit à Java des méthodes d'extension de style C # et plusieurs autres fonctionnalités. Contrairement à d' autres outils, collecteur n'a pas de limites et ne pas souffrir de problèmes avec les génériques, lambdas, etc. IDE fournit plusieurs Manifold d' autres fonctions telles que F # -style types personnalisés , tapuscrit de style interfaces de structure et de style Javascript types expando .

De plus, IntelliJ fournit une prise en charge complète de Manifold via le plugin Manifold .

Manifold est un projet open source disponible sur github .

Scott
la source
6

Une autre option consiste à utiliser les classes ForwardingXXX de la bibliothèque google-guava.

Aravind Yarram
la source
5

Java n'a pas une telle fonctionnalité. Au lieu de cela, vous pouvez créer une sous-classe régulière de votre implémentation de liste ou créer une classe interne anonyme:

List<String> list = new ArrayList<String>() {
   public String getData() {
       return ""; // add your implementation here. 
   }
};

Le problème est d'appeler cette méthode. Vous pouvez le faire "sur place":

new ArrayList<String>() {
   public String getData() {
       return ""; // add your implementation here. 
   }
}.getData();
AlexR
la source
112
Cela est totalement inutile.
SLaks
2
@Slaks: Pourquoi exactement? Il s'agit d'un "écrivez votre propre cours" que vous avez suggéré.
Goran Jovic
23
@Goran: Tout cela vous permet de définir une méthode, puis de l'appeler immédiatement, une fois .
SLaks
3
@Slaks: D'accord, point pris. Par rapport à cette solution limitée, écrire une classe nommée serait mieux.
Goran Jovic
Il existe une grande différence entre les méthodes d'extension C # et les classes anonymes Java. En C #, une méthode d'extension est du sucre syntaxique pour ce qui n'est en réalité qu'une méthode statique. L'EDI et le compilateur font apparaître une méthode d'extension comme s'il s'agissait d'une méthode d'instance de la classe étendue. (Remarque: "étendu" dans ce contexte ne signifie pas "hérité" comme il le ferait normalement en Java.)
HairOfTheDog
4

Il semble qu'il y a peu de chances que les méthodes Defender (c'est-à-dire les méthodes par défaut) soient intégrées à Java 8. Cependant, pour autant que je les comprends, elles permettent uniquement à l' auteur d'un interfacede l'étendre rétroactivement, pas aux utilisateurs arbitraires.

Defender Methods + Interface Injection serait alors capable d'implémenter pleinement les méthodes d'extension de style C #, mais AFAICS, Interface Injection n'est même pas encore sur la feuille de route de Java 8.

Jörg W Mittag
la source
3

Un peu tard à la fête sur cette question, mais au cas où quelqu'un le trouverait utile, je viens de créer une sous-classe:

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
la source
4
Les méthodes d'extension sont généralement destinées au code qui ne peut pas être modifié ou hérité comme les classes finales / scellées et son principal pouvoir est l'extension des interfaces, par exemple l'extension de IEnumerable <T>. Bien sûr, ce ne sont que du sucre syntaxique pour les méthodes statiques. Le but est que le code soit beaucoup plus lisible. Un code plus propre signifie une meilleure maintenabilité / évolutivité.
mbx
1
Ce n'est pas seulement ça @mbx. Les méthodes d'extension sont également utiles pour étendre la fonctionnalité de classe des classes non scellées mais que vous ne pouvez pas étendre car vous ne contrôlez pas ce qui renvoie des instances, par exemple HttpContextBase qui est une classe abstraite.
Fabio Milheiro
@FabioMilheiro J'ai généreusement inclus des classes abstraites comme "interfaces" dans ce contexte. Les classes générées automatiquement (xsd.exe) sont du même type: vous pouvez mais ne devez pas les étendre en modifiant les fichiers générés. Vous les étendez normalement en utilisant «partial», ce qui les oblige à résider dans le même assemblage. Si ce n'est pas le cas, les méthodes d'extension sont une jolie alternative. En fin de compte, ce ne sont que des méthodes statiques (il n'y a aucune différence si vous regardez le code IL généré).
mbx
Oui ... HttpContextBase est une abstraction bien que je comprenne votre générosité. Appeler une interface une abstraction aurait pu sembler un peu plus naturel. Indépendamment de cela, je ne voulais pas dire que ce devait être une abstraction. Je viens de donner un exemple de classe pour laquelle j'ai écrit de nombreuses méthodes d'extension.
Fabio Milheiro
2

Nous pouvons simuler l'implémentation des méthodes d'extension C # en Java en utilisant l'implémentation de méthode par défaut disponible depuis Java 8. Nous commençons par définir une interface qui nous permettra d'accéder à l'objet support via une méthode base (), comme ceci:

public interface Extension<T> {

    default T base() {
        return null;
    }
}

Nous retournons null car les interfaces ne peuvent pas avoir d'état, mais cela doit être corrigé ultérieurement via un proxy.

Le développeur d'extensions devrait étendre cette interface par une nouvelle interface contenant des méthodes d'extension. Disons que nous voulons ajouter une interface forEach consumer sur List:

public interface ListExtension<T> extends Extension<List<T>> {

    default void foreach(Consumer<T> consumer) {
        for (T item : base()) {
            consumer.accept(item);
        }
    }

}

Comme nous étendons l'interface d'extension, nous pouvons appeler la méthode base () dans notre méthode d'extension pour accéder à l'objet de support auquel nous nous attachons.

L'interface d'extension doit avoir une méthode de fabrique qui créera une extension d'un objet de support donné:

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

Nous créons un proxy qui implémente l'interface d'extension et toute l'interface implémentée par le type de l'objet de support. Le gestionnaire d'appel donné au proxy distribuerait tous les appels à l'objet de support, à l'exception de la méthode "base", qui doit renvoyer l'objet de support, sinon son implémentation par défaut renvoie 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);
    }

}

Ensuite, nous pouvons utiliser la méthode Extension.create () pour attacher l'interface contenant la méthode d'extension à l'objet de support. Le résultat est un objet qui peut être transtypé vers l'interface d'extension par laquelle nous pouvons toujours accéder à l'objet de support appelant la méthode base (). Une fois la référence castée vers l'interface d'extension, nous pouvons maintenant appeler en toute sécurité les méthodes d'extension qui peuvent avoir accès à l'objet de support, de sorte que nous pouvons maintenant attacher de nouvelles méthodes à l'objet existant, mais pas à son type de définition:

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

}

C'est donc une façon de simuler la capacité d'étendre des objets en Java en leur ajoutant de nouveaux contrats, ce qui nous permet d'appeler des méthodes supplémentaires sur les objets donnés.

Vous trouverez ci-dessous le code de l'interface d'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;
        }
    }

}
Iulian Ilie-Némedi
la source
1
c'est un sacré kludge!
Dmitry Avtonomov le
1

On pourrait utiliser le modèle de conception orienté objet du décorateur . Un exemple de ce modèle utilisé dans la bibliothèque standard de Java serait le DataOutputStream .

Voici du code pour augmenter les fonctionnalités d'une 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 Je suis un grand fan de Kotlin . Il a des méthodes d'extension et fonctionne également sur la JVM.

Eric
la source
0

Vous pouvez créer une méthode d'extension / d'assistance de type C # en (RE) implémentant l'interface Collections et en ajoutant un exemple pour 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;
}
GarethReid
la source
-7

Java8 prend désormais en charge les méthodes par défaut , qui sont similaires aux C#méthodes d'extension de.

DarVar
la source
9
Faux; l'exemple de cette question est encore impossible.
SLaks
@SLaks Quelle est la différence entre les extensions Java et C #?
Fabio Milheiro
3
Les méthodes par défaut ne peuvent être définies que dans l'interface. docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
SLaks
DarVar, Ce fil de commentaires était le seul endroit où les méthodes par défaut étaient mentionnées, ce dont j'essayais désespérément de me souvenir. Merci de les mentionner, sinon par leur nom! :-) (Merci @SLaks pour le lien)
jpaugh
Je voterais pour cette réponse, car le résultat final d'une méthode statique dans l'interface fournirait la même utilisation que les méthodes d'extension C #, cependant, vous devez toujours implémenter l'interface dans votre classe contrairement à C # vous passez (ce) mot-clé en tant que paramètre
zaPlayer