Java equivalente aos métodos de extensão C #

176

Eu estou olhando para implementar uma funcionalidade em uma lista de objetos como faria em C # usando um método de extensão.

Algo assim:

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

Como faço isso em Java?

Fabio Milheiro
fonte
8
Marque esta: github.com/nicholas22/jpropel, exemplo: new String [] {"james", "john", "john", "eddie"} .where (beginWith ("j")). Distinct (); Ele usa o lombok-pg, que fornece a bondade do método de extensão.
NT_ 07/10/11
6
A Microsoft definitivamente acertou quando permitiu extensões. Subclassificar para adicionar nova funcionalidade não funcionará se eu precisar da função em uma classe retornada para mim em outro lugar. Como adicionar métodos a String e Data.
tggagne
3
java.lang.String é uma classe final, portanto você não pode estendê-la. Usar métodos estáticos é uma maneira, mas às vezes mostra código ilegível. Acho que o C # deixou uma era como um computador. Os métodos de extensão, as classes parciais, LINQ e assim por diante ..
Davut Gürbüz
7
@Roadrunner, rofl! A melhor resposta para um recurso de idioma ausente é que o recurso de idioma ausente é ruim e indesejado. Isso é conhecido.
precisa saber é o seguinte
6
Os métodos de extensão não são "maus". Eles melhoram bastante a legibilidade do código. Apenas mais uma das muitas decisões ruins de design em Java.
Csauve

Respostas:

196

Java não suporta métodos de extensão.

Em vez disso, você pode criar um método estático regular ou escrever sua própria classe.

SLaks
fonte
63
Estou estragado depois de usar métodos de extensão - mas métodos estáticos também farão o truque.
Bbqchickenrobot
31
Mas a sintaxe é muito agradável e facilita a compreensão do programa :) Também gosto de como o Ruby permite que você faça quase a mesma coisa, exceto que você pode realmente modificar as classes incorporadas e adicionar novos métodos.
knownasilya
18
@ Ken: Sim, e esse é o ponto! Por que você escreve em Java e não diretamente no código de bytes da JVM? Não é "apenas uma questão de sintaxe"?
Fyodor Soikin
30
Os métodos de extensão podem tornar o código muito mais elegante em comparação com os métodos estáticos extras de alguma outra classe. Quase todas as linguagens mais modernas permitem algum tipo de extensão de classe existente: C #, php, objetivo-c, javascript. Java certamente mostra sua idade aqui. Imagine que você deseja gravar um JSONObject no disco. Você chama jsonobj.writeToDisk () ou someunrelatedclass.writeToDisk (jsonobj)?
Woens
8
Os motivos para odiar o Java continuam crescendo. E eu parei de olhar para eles um par de anos atrás ......
John Demetriou
54

Os métodos de extensão não são apenas métodos estáticos e não apenas açúcar de sintaxe de conveniência, na verdade eles são uma ferramenta bastante poderosa. O principal é a capacidade de substituir métodos diferentes com base na instanciação de parâmetros de genéricos diferentes. Isso é semelhante às classes de tipo de Haskell e, de fato, parece que elas estão em C # para suportar as mônadas de C # (ou seja, LINQ). Mesmo eliminando a sintaxe do LINQ, ainda não conheço nenhuma maneira de implementar interfaces semelhantes em Java.

E não creio que seja possível implementá-los em Java, devido à semântica de apagamento de tipo de Java dos parâmetros genéricos.

user1686250
fonte
Eles também permitem que você herde vários comportamentos (sem polimorfismo). Você pode implementar várias interfaces e, com isso, seus métodos de extensão. Eles também permitem implementar o comportamento que você deseja anexar a um tipo sem que ele seja associado globalmente ao tipo em todo o sistema.
inteligente Neologismo
25
Toda essa resposta está errada. Os métodos de extensão em C # são apenas açúcar sintático que o compilador reorganiza um pouco para mover o destino da chamada de método para o primeiro argumento do método estático. Você não pode substituir os métodos existentes. Não há exigência de que um método de extensão seja uma mônada. É literalmente apenas uma maneira mais conveniente de chamar um método estático, o que dá a aparência de adicionar métodos de instância a uma classe. Por favor, leia este se você concorda com esta resposta
Matt Klein
3
bem, neste caso, defina o que é açúcar de sintaxe, eu chamaria um açúcar de sintaxe de macro interna, pois o compilador de métodos de extensão deve pelo menos procurar a classe estática que o método de extensão está localizado para substituir. Não há nada na resposta sobre o método que deva ser mônada, o que não faz sentido. Além disso, você pode usá-lo para sobrecarregar, mas não é um recurso de métodos de extensão, é um sobrecarga baseada em tipo de parâmetro simples, da mesma maneira que funcionaria se o método fosse chamado diretamente e não funcionará em muitos casos interessantes em Java por causa dos argumentos de tipo genérico apagados.
precisa saber é o seguinte
1
@ user1686250 É possível implementar em Java (por "Java", presumo que você queira dizer bytecode que é executado em uma JVM) ... Kotlin, que é compilado no bytecode, possui extensões. É apenas açúcar sintático sobre métodos estáticos. Você pode usar o descompilador no IntelliJ para ver como é o Java equivalente.
Jeffrey Blattman
@ user1686250 Você poderia desenvolver o que está escrevendo sobre genéricos (ou fornecer um link) porque eu absolutamente não entendo os genéricos. De que maneira, além dos métodos estáticos usuais, está relacionado?
C.Champagne
10

Tecnicamente, a extensão C # não tem equivalente em Java. Mas se você deseja implementar essas funções para obter um código e uma manutenção mais limpos, use a estrutura do 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
fonte
7

A linguagem XTend - que é um superconjunto de Java e compila no código-fonte 1 do Java  - suporta isso.

Sam Keays
fonte
Quando esse código que não é Java é compilado para Java, você tem um método de extensão? Ou o código Java é apenas um método estático?
Fabio Milheiro
@Bomboca Como outros observaram, o Java não possui métodos de extensão. Portanto, o código XTend, compilado em Java, de alguma forma não cria um método de extensão Java. Mas se você trabalha exclusivamente no XTend, não notará nem se importará. Mas, para responder sua pergunta, você também não tem necessariamente um método estático. O autor principal do XTend tem uma entrada de blog sobre isso em blog.efftinge.de/2011/11/…
Erick G. Hagstrom
Sim, não sei por que não pensei nisso também. Obrigado!
Fabio Milheiro
@ Sam Obrigado por me apresentar ao XTend - eu nunca tinha ouvido falar disso.
jpaugh
7

O Manifold fornece ao Java métodos de extensão no estilo C # e vários outros recursos. Diferentemente de outras ferramentas, o Manifold não tem limitações e não sofre problemas com genéricos, lambdas, IDE etc. O Manifold fornece vários outros recursos, como tipos personalizados no estilo F # , interfaces estruturais no estilo TypeScript e tipos expando no estilo Javascript .

Além disso, o IntelliJ fornece suporte abrangente ao Manifold por meio do plug-in do Manifold .

Manifold é um projeto de código aberto disponível no github .

Scott
fonte
6

Outra opção é usar as classes ForwardingXXX da biblioteca do google-goiaba.

Aravind Yarram
fonte
5

Java não possui esse recurso. Em vez disso, você pode criar subclasses regulares da implementação da sua lista ou criar uma classe interna anônima:

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

O problema é chamar esse método. Você pode fazer isso "no lugar":

new ArrayList<String>() {
   public String getData() {
       return ""; // add your implementation here. 
   }
}.getData();
AlexR
fonte
112
Isso é totalmente inútil.
SLaks
2
@ Slicks: Por que exatamente? Esta é uma "escreva sua própria turma" sugerida por você.
Goran Jovic
23
@ Koran: Tudo isso permite que você defina um método e chame-o imediatamente, uma vez .
Slaks
3
@ Slicks: Tudo bem, ponto levado. Comparado com essa solução limitada, escrever uma classe nomeada seria melhor.
Goran Jovic
Há uma grande diferença entre os métodos de extensão C # e as classes anônimas Java. No C #, um método de extensão é um açúcar sintático para o que é realmente apenas um método estático. O IDE e o compilador fazem com que um método de extensão apareça como se fosse um método de instância da classe estendida. (Nota: "estendido" neste contexto não significa que "herdou" como normalmente faria em Java.)
HairOfTheDog
4

Parece que há uma pequena chance de que os Métodos Defender (ou seja, métodos padrão) possam entrar no Java 8. No entanto, até onde eu os entendo, eles permitem apenas que o autor de um interfaceestenda-o retroativamente, e não usuários arbitrários.

O Defender Methods + Interface Injection seria capaz de implementar completamente os métodos de extensão no estilo C #, mas o AFAICS, Interface Injection ainda não está no roteiro do Java 8.

Jörg W Mittag
fonte
3

Um pouco atrasado para a parte sobre esta questão, mas, caso alguém ache útil, acabei de criar uma subclasse:

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
fonte
4
Os métodos de extensão são geralmente para código que não pode ser modificado ou herdado como classes finais / seladas e seu poder principal é a extensão de interfaces, por exemplo, estendendo IEnumerable <T>. Obviamente, eles são apenas açúcar sintático para métodos estáticos. O objetivo é que o código seja muito mais legível. Código mais limpo significa melhor manutenção / capacidade de evolução.
mbx
1
Não é só isso @mbx. Os métodos de extensão também são úteis para estender a funcionalidade de classe de classes não seladas, mas que você não pode estender porque não controla o que está retornando instâncias, por exemplo, HttpContextBase, que é uma classe abstrata.
Fabio Milheiro
@FabioMilheiro Eu incluí generosamente classes abstratas como "interfaces" nesse contexto. As classes geradas automaticamente (xsd.exe) são do mesmo tipo: você pode, mas não deve estendê-las, modificando os arquivos gerados. Você normalmente os estenderia usando "parcial", o que exige que eles residam no mesmo assembly. Caso contrário, os métodos de extensão são uma alternativa bastante atraente. Por fim, são apenas métodos estáticos (não há diferença se você observar o código IL gerado).
precisa
Sim ... HttpContextBase é uma abstração, embora eu compreenda sua generosidade. Chamar uma interface de abstração pode parecer um pouco mais natural. Independentemente disso, eu não quis dizer que tinha que ser uma abstração. Acabei de dar um exemplo de classe para a qual escrevi muitos métodos de extensão.
Fabio Milheiro
2

Podemos simular a implementação dos métodos de extensão C # em Java usando a implementação padrão disponível desde o Java 8. Começamos definindo uma interface que nos permitirá acessar o objeto de suporte por meio de um método base (), como:

public interface Extension<T> {

    default T base() {
        return null;
    }
}

Retornamos nulos, pois as interfaces não podem ter estado, mas isso deve ser corrigido posteriormente por meio de um proxy.

O desenvolvedor de extensões teria que estender essa interface por uma nova interface contendo métodos de extensão. Digamos que queremos adicionar um consumidor forEach na interface List:

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

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

}

Como estendemos a interface Extension, podemos chamar o método base () dentro do nosso método de extensão para acessar o objeto de suporte ao qual anexamos.

A interface Extension deve ter um método de fábrica que criará uma extensão de um determinado objeto de suporte:

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

Criamos um proxy que implementa a interface de extensão e toda a interface implementada pelo tipo do objeto de suporte. O manipulador de chamada fornecido ao proxy enviaria todas as chamadas para o objeto de suporte, exceto o método "base", que deve retornar o objeto de suporte, caso contrário, sua implementação padrão retornará nulo:

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

}

Em seguida, podemos usar o método Extension.create () para anexar a interface que contém o método de extensão ao objeto de suporte. O resultado é um objeto que pode ser convertido para a interface de extensão pela qual ainda podemos acessar o objeto de suporte que chama o método base (). Tendo a referência convertida para a interface de extensão, agora podemos chamar com segurança os métodos de extensão que podem ter acesso ao objeto de suporte, para que agora possamos anexar novos métodos ao objeto existente, mas não ao seu tipo de definição:

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

}

Portanto, é uma maneira de simular a capacidade de estender objetos em Java adicionando novos contratos a eles, o que nos permite chamar métodos adicionais para os objetos fornecidos.

Abaixo, você pode encontrar o código da interface 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;
        }
    }

}
Ilie-Némedi, Iulian
fonte
1
isso é um inferno de uma confusão!
Dmitry Avtonomov
1

Pode-se usar o padrão de design orientado a objetos do decorador . Um exemplo desse padrão usado na biblioteca padrão de Java seria o DataOutputStream .

Aqui está um código para aumentar a funcionalidade de uma lista:

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 Eu sou um grande fã de Kotlin . Possui métodos de extensão e também é executado na JVM.

Eric
fonte
0

Você pode criar um método de extensão / auxiliar do tipo C # implementando a interface Collections e adicionando um exemplo para o 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
fonte
-7

Java8 agora suporta métodos padrão , que são semelhantes aos C#métodos de extensão.

DarVar
fonte
9
Errado; o exemplo nesta pergunta ainda é impossível.
SLaks
@ Slaks, qual é a diferença entre as extensões Java e C #?
Fabio Milheiro
3
Os métodos padrão podem ser definidos apenas dentro da interface. docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
SLaks
DarVar, esse tópico de comentários foi o único lugar em que os métodos padrão foram mencionados, dos quais eu tentava desesperadamente lembrar. Obrigado por mencioná-los, se não pelo nome! :-) (Obrigado @SLaks pelo link)
jpaugh
Gostaria de aprovar essa resposta, porque o resultado final de um método estático na interface forneceria o mesmo uso que os métodos de extensão C #, no entanto, você ainda precisará implementar a interface na sua classe, diferente do C # que você passa (this) palavra-chave como parâmetro
zaPlayer