Java equivalente ai metodi di estensione C #


176

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?


8
Dai un'occhiata a questo: github.com/nicholas22/jpropel, esempio: new String [] {"james", "john", "john", "eddie"} .where (startWith ("j")). Distinto (); Usa lombok-pg che fornisce la bontà del metodo di estensione.
NT_

6
Microsoft ha sicuramente capito bene quando hanno permesso le estensioni. La sottoclasse per aggiungere nuove funzionalità non funziona se ho bisogno della funzione in una classe restituita altrove. Come aggiungere metodi a String e Date.
Tggagne,

3
cioè java.lang.String è una classe finale, quindi non puoi estenderla. L'utilizzo di metodi statici è un modo, ma a volte mostra codice illeggibile. Penso che C # abbia lasciato un'età come linguaggio del computer. Metodi di estensione, classi parziali, LINQ e così via ..
Davut Gürbüz,

7
@Roadrunner, rofl! La migliore risposta a una caratteristica della lingua mancante è che la caratteristica della lingua mancante è malvagia e indesiderata. Questo è noto
Kirk Woll,

6
I metodi di estensione non sono "malvagi". Migliorano notevolmente la leggibilità del codice. Solo un'altra delle molte cattive decisioni di progettazione in Java.
csauve,

Risposte:


196

Java non supporta i metodi di estensione.

Invece, puoi creare un metodo statico regolare o scrivere la tua classe.


63
Sono viziato dopo aver usato i metodi di estensione, ma anche i metodi statici faranno il trucco.
bbqchickenrobot,

31
Ma la sintassi è così bella e rende il programma più facile da capire :) Mi piace anche come Ruby ti permetta di fare quasi la stessa cosa, tranne che puoi effettivamente modificare le classi integrate e aggiungere nuovi metodi.
knownasilya,

18
@Ken: Sì, e questo è il punto! Perché scrivi in ​​Java e non direttamente nel codice byte JVM? Non è "solo una questione di sintassi"?
Fyodor Soikin,

30
I metodi di estensione possono rendere il codice molto più elegante rispetto ai metodi statici extra di qualche altra classe. Quasi tutte le lingue più moderne consentono un certo tipo di estensione di classe esistente: C #, php, goal-c, javascript. Java sicuramente mostra la sua età qui. Immagina di voler scrivere un JSONObject su disco. Chiami jsonobj.writeToDisk () o someunrelatedclass.writeToDisk (jsonobj)?
male il

9
I motivi per odiare Java continuano a crescere. E ho smesso di cercarli un paio di anni fa ......
John Demetriou,

54

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.


Consentono inoltre di ereditare comportamenti multipli (senza polimorfismo). Puoi implementare più interfacce e con ciò arrivano i loro metodi di estensione. Consentono inoltre di implementare il comportamento che si desidera associare a un tipo senza averlo associato globalmente al tipo a livello di sistema.
Neologismo intelligente

25
Tutta questa risposta è sbagliata. I metodi di estensione in C # sono solo zucchero sintattico che il compilatore riorganizza leggermente per spostare l'obiettivo della chiamata al metodo sul primo argomento del metodo statico. Non è possibile ignorare i metodi esistenti. Non è necessario che un metodo di estensione sia una monade. È letteralmente solo un modo più conveniente per chiamare un metodo statico, che dà l'impressione di aggiungere metodi di istanza a una classe. Si prega di leggere questo se si è d'accordo con questa risposta
Matt Klein

3
bene, in questo caso, definire cos'è lo zucchero di sintassi, definirei lo zucchero di sintassi come sintassi di macro interna, perché il compilatore dei metodi di estensione deve almeno cercare la classe statica che il metodo di estensione si trova da sostituire. Non c'è nulla nella risposta sul metodo che dovrebbe essere la monade che non ha senso. Inoltre, puoi usarlo per il sovraccarico, ma non è una funzione dei metodi di estensione, è un semplice sovraccarico basato su un tipo di parametro, allo stesso modo in cui funzionerebbe se il metodo viene chiamato direttamente e non funzionerà in molti casi interessanti in Java a causa della cancellazione di argomenti di tipo generici.
user1686250,

1
@ user1686250 È possibile implementare in Java (per "Java" suppongo che intendi bytecode che gira su una JVM) ... Kotlin, che si compila in bytecode ha estensioni. È solo zucchero sintattico su metodi statici. È possibile utilizzare il decompilatore in IntelliJ per vedere l'aspetto di Java equivalente.
Jeffrey Blattman,

@ user1686250 Potresti sviluppare ciò che stai scrivendo sui generici (o fornire un link) perché non capisco assolutamente il punto sui generici. In che modo è diverso dai soliti metodi statici?
C.Champagne il


10

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

7

Il linguaggio XTend - che è un super-set di Java e si compila nel codice sorgente Java 1  - supporta questo.


Quando quel codice che non è Java viene compilato in Java, hai un metodo di estensione? O il codice Java è solo un metodo statico?
Fabio Milheiro,

@Bomboca Come altri hanno già notato, Java non ha metodi di estensione. Quindi il codice XTend, compilato su Java, non crea in qualche modo un metodo di estensione Java. Ma se lavori esclusivamente su XTend non te ne accorgerai o non ti preoccuperai. Ma, per rispondere alla tua domanda, non hai necessariamente un metodo statico. L'autore principale di XTend ha un post sul blog su blog.efftinge.de/2011/11/…
Erick G. Hagstrom,

Sì, non so perché non lo pensassi anch'io. Grazie!
Fabio Milheiro,

@Sam Grazie per avermi fatto conoscere XTend - non ne avevo mai sentito parlare.
jpaugh

7

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 .



5

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

112
È assolutamente inutile.
SLaks

2
@Slaks: perché esattamente? Questo è un "scrivi la tua lezione" suggerito da te.
Goran Jovic,

23
@Goran: tutto ciò che ti permette di fare è definire un metodo, quindi chiamarlo immediatamente, una volta .
SLaks,

3
@Slaks: Va bene, punto preso. Rispetto a quella soluzione limitata, sarebbe meglio scrivere una classe con nome.
Goran Jovic,

C'è una grande differenza tra i metodi di estensione C # e le classi anonime Java. In C # un metodo di estensione è lo zucchero sintattico per quello che in realtà è solo un metodo statico. L'IDE e il compilatore fanno apparire un metodo di estensione come se fosse un metodo di istanza della classe estesa. (Nota: "esteso" in questo contesto non significa "ereditato" come normalmente in Java.)
HairOfTheDog

4

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.


3

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

4
I metodi di estensione sono in genere per il codice che non può essere modificato o ereditato come le classi finali / sigillate e il suo potere principale è l'estensione delle interfacce, ad esempio estendendo IEnumerable <T>. Naturalmente, sono solo zucchero sintattico per metodi statici. Lo scopo è che il codice sia molto più leggibile. Un codice più pulito significa migliore manutenibilità / evolvibilità.
mbx,

1
Questo non è solo @mbx. I metodi di estensione sono utili anche per estendere la funzionalità di classe di classi non sigillate ma che non è possibile estendere perché non si controlla ciò che restituisce istanze, ad esempio HttpContextBase che è una classe astratta.
Fabio Milheiro,

@FabioMilheiro Ho generosamente incluso le classi astratte come "interfacce" in quel contesto. Le classi generate automaticamente (xsd.exe) sono dello stesso tipo: potresti ma non dovresti estenderle modificando i file generati. Normalmente li estenderesti usando "parziale" che richiede loro di risiedere nello stesso assieme. In caso contrario, i metodi di estensione sono un'alternativa piuttosto carina. In definitiva, sono solo metodi statici (non c'è differenza se si osserva il codice IL generato).
mbx,

Sì ... HttpContextBase è un'astrazione anche se capisco la tua generosità. Chiamare un'interfaccia come un'astrazione potrebbe essere sembrato in qualche modo più naturale. Indipendentemente da ciò, non intendevo che doveva essere un'astrazione. Ho appena dato un esempio di una classe per la quale ho scritto molti metodi di estensione.
Fabio Milheiro,

2

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

}

1
questo è un inferno di un kludge!
Dmitry Avtonomov

1

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.


0

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

-7

Java8 ora supporta i metodi predefiniti , che sono simili ai C#metodi di estensione.


9
Sbagliato; l'esempio in questa domanda è ancora impossibile.
SLaks

@SLaks qual è la differenza tra le estensioni Java e C #?
Fabio Milheiro,

3
I metodi predefiniti possono essere definiti solo nell'interfaccia. docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
SLaks

DarVar, Questo thread di commenti era l'unico posto in cui venivano menzionati i metodi predefiniti , che stavo disperatamente cercando di ricordare. Grazie per averli citati, se non per nome! :-) (Grazie @SLaks per il link)
jpaugh

Voterei questa risposta, perché il risultato finale di un metodo statico nell'interfaccia fornirebbe lo stesso uso dei metodi di estensione C #, tuttavia, è ancora necessario implementare l'interfaccia nella tua classe a differenza di C # che passi (questa) parola chiave come parametro
zaPlayer il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.