Java эквивалентен методам расширения C #

176

Я ищу для реализации функциональности в списке объектов, как я бы в C #, используя метод расширения.

Что-то вроде этого:

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

Как мне это сделать на Java?

Фабио Мильейру
источник
8
Проверьте это: github.com/nicholas22/jpropel, пример: new String [] {"james", "john", "john", "eddie"} .where (startWith ("j")). Diver (); Он использует lombok-pg, который обеспечивает совершенство метода расширения.
NT_
6
Microsoft определенно поняла это правильно, когда они разрешили расширения. Подклассы для добавления новых функций не работают, если мне нужна функция в классе, возвращенном мне в другом месте. Как добавление методов в String и Date.
Tggagne
3
то есть java.lang.String - последний класс, поэтому вы не можете его расширять. Использование статических методов - это один из способов, но иногда он показывает нечитаемый код. Я думаю, что C # оставил эпоху компьютерного языка. Методы расширения, частичные классы, LINQ и т. Д.
Davut Gürbüz
7
@Roadrunner, rofl! Лучший ответ на отсутствующую языковую функцию состоит в том, что упомянутая отсутствующая языковая функция является злой и нежелательной. Это известно.
Кирк Уолл
6
Методы расширения не являются «злыми». Они значительно улучшают читабельность кода. Еще одно из многих плохих дизайнерских решений в Java.
csauve

Ответы:

196

Java не поддерживает методы расширения.

Вместо этого вы можете создать обычный статический метод или написать свой собственный класс.

SLaks
источник
63
Я испорчен после использования методов расширения - но статические методы также помогут.
bbqchickenrobot
31
Но синтаксис очень приятный и делает программу более понятной :) Мне также нравится, как Ruby позволяет вам делать почти то же самое, за исключением того, что вы можете на самом деле изменять встроенные классы и добавлять новые методы.
известноасиля
18
@Ken: Да, и в этом весь смысл! Почему вы пишете на Java, а не прямо в байт-коде JVM? Разве это не просто вопрос синтаксиса?
Федор Сойкин
30
Методы расширения могут сделать код намного более элегантным по сравнению с дополнительными статическими методами из другого класса. Почти все более современные языки допускают какое-то существующее расширение класса: C #, php, target-c, javascript. Ява, безусловно, показывает свой возраст здесь. Представьте, что вы хотите записать JSONObject на диск. Вы вызываете jsonobj.writeToDisk () или someunrelatedclass.writeToDisk (jsonobj)?
woens
9
Причины ненавидеть Java продолжают расти. И я перестал их искать пару лет назад ......
Джон Деметриу
54

Методы расширения - это не просто статический метод и не просто удобство синтаксиса, на самом деле это довольно мощный инструмент. Главное, что есть возможность переопределять разные методы, основанные на создании экземпляров различных родовых параметров. Это похоже на классы типов в Haskell, и, на самом деле, похоже, что они находятся в C # для поддержки монад C # (т.е. LINQ). Даже отбрасывая синтаксис LINQ, я до сих пор не знаю способа реализации подобных интерфейсов в Java.

И я не думаю, что возможно реализовать их в Java, из-за семантики стирания типов Java обобщенных параметров.

user1686250
источник
Они также позволяют вам наследовать несколько типов поведения (без полиморфизма). Вы можете реализовать несколько интерфейсов, а вместе с ними и их методы расширения. Они также позволяют вам реализовать поведение, которое вы хотите присоединить к типу, не связывая его глобально с типом в масштабе всей системы.
Умный неологизм
25
Весь этот ответ неверен. Методы расширения в C # являются просто синтаксическим сахаром, который компилятор немного переставляет, чтобы переместить цель вызова метода в первый аргумент статического метода. Вы не можете переопределить существующие методы. Не требуется, чтобы метод расширения был монадой. Это буквально просто более удобный способ вызова статического метода, который создает видимость добавления методов экземпляра в класс. Пожалуйста, прочитайте это, если вы согласны с этим ответом
Мэтт Кляйн
3
ну, в этом случае, определите, что такое синтаксический сахар, я бы назвал синтаксический сахар внутренним макросинтаксисом, так как компилятор методов расширения должен хотя бы найти статический класс, который метод расширения находится для замены. Нет ничего в ответе о том, что метод должен быть монадой, которая не имеет смысла. Кроме того, вы можете использовать его для перегрузки, но это не особенность методов расширения, это перегрузка на основе простого параметра, аналогично тому, как он будет работать, если метод вызывается напрямую, и он не будет работать во многих интересных случаях в Java. из-за стирания аргументов типа дженериков.
user1686250
1
@ user1686250 Это возможно реализовать в Java (под «Java» я предполагаю, что вы имеете в виду байт-код, который выполняется на JVM) ... Kotlin, который компилируется в байт-код, имеет расширения. Это просто синтаксический сахар по сравнению со статическими методами. Вы можете использовать декомпилятор в IntelliJ, чтобы увидеть, как выглядит эквивалентная Java.
Джеффри Блатман
@ user1686250 Не могли бы вы разработать то, что вы пишете о дженериках (или дать ссылку), потому что я абсолютно не понимаю, о дженериках. Каким образом кроме обычных статических методов это связано?
C.Champagne
17

Проект Ломбок предоставляет аннотацию, @ExtensionMethodкоторую можно использовать для достижения требуемой функциональности.

Томас Эйзингер
источник
3
Manifold обеспечивает полную, бесшовную поддержку методов расширения в стиле C # для Java , позволяя вам добавлять методы в классы, которые вы не контролируете, например java.lang.String. Демонстрация: http://manifold.systems/images/ExtensionMethod.mp4
Скотт,
10

Технически расширение C # не имеет аналогов в Java. Но если вы хотите реализовать такие функции для более чистого кода и удобства сопровождения, вы должны использовать платформу 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
источник
7

Язык XTend - который является супернабором Java и компилируется в исходный код Java 1  - поддерживает это.

Сэм Кис
источник
Когда тот код, который не является Java, компилируется в Java, у вас есть метод расширения? Или код Java - это просто статический метод?
Фабио Мильейру
@Bomboca Как уже отмечалось, у Java нет методов расширения. Таким образом, код XTend, скомпилированный в Java, каким-то образом не создает метод расширения Java. Но если вы работаете исключительно в XTend, вы не заметите и не позаботитесь. Но, чтобы ответить на ваш вопрос, у вас тоже не обязательно есть статический метод. Основной автор XTend имеет запись в блоге об этом на blog.efftinge.de/2011/11/…
Эрик Дж. Хагстром
Да, не знаю, почему я тоже так не думаю. Спасибо!
Фабио Мильейру,
@Sam Спасибо за представление меня в XTend - я никогда не слышал об этом.
jpaugh
7

Коллектор предоставляет Java методы расширения в стиле C # и ряд других функций. В отличие от других инструментов, Manifold не имеет ограничений и не страдает от проблем с обобщениями, лямбдами, IDE и т. Д. Manifold предоставляет несколько других функций, таких как настраиваемые типы в стиле F # , структурные интерфейсы в стиле TypeScript и расширяемые типы в стиле Javascript .

Кроме того, IntelliJ обеспечивает всестороннюю поддержку Manifold через плагин Manifold .

Manifold - это проект с открытым исходным кодом, доступный на github .

Скотт
источник
6

Другой вариант - использовать классы ForwardingXXX из библиотеки google-guava.

Аравинд Яррам
источник
5

У Java нет такой возможности. Вместо этого вы можете либо создать обычный подкласс вашей реализации списка, либо создать анонимный внутренний класс:

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

Проблема в том, чтобы вызвать этот метод. Вы можете сделать это "на месте":

new ArrayList<String>() {
   public String getData() {
       return ""; // add your implementation here. 
   }
}.getData();
AlexR
источник
112
Это совершенно бесполезно.
SLaks
2
@ Слакс: Почему именно? Это «написать свой собственный класс», предложенный вами.
Горан Йович
23
@Goran: Все это позволяет вам определить метод, а затем вызвать его немедленно, один раз .
SLaks
3
@ Слакс: Хорошо, точка взята. По сравнению с этим ограниченным решением написание именованного класса было бы лучше.
Горан Йович
Существует большая разница между методами расширения C # и анонимными классами Java. В C # метод расширения является синтаксическим сахаром для того, что действительно является статическим методом. В среде IDE и компиляторе метод расширения выглядит так, как будто это метод экземпляра расширенного класса. (Примечание: «расширенный» в этом контексте не означает «унаследованный», как это обычно
бывает
4

Похоже , есть некоторые небольшие шансы , что Защитник метода (т.е. метода по умолчанию) может сделать это в Java 8. Однако, насколько я понимаю их, они лишь позволяют автор вinterface заднем числе продлить его, а не произвольные пользователь.

Методы Defender + Interface Injection смогут полностью реализовать методы расширения в стиле C #, но AFAICS, Interface Injection даже не входит в план развития Java 8.

Йорг Миттаг
источник
3

Немного опоздал на вечеринку по этому вопросу, но на тот случай, если кто-то сочтет это полезным, я просто создал подкласс

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
Методы расширения обычно предназначены для кода, который не может быть изменен или унаследован, как конечные / запечатанные классы, и его основной силой является расширение интерфейсов, например расширение IEnumerable <T>. Конечно, они являются только синтаксическим сахаром для статических методов. Цель состоит в том, чтобы код был намного более читабельным. Более чистый код означает лучшую возможность обслуживания / развития.
mbx
1
Это не просто @mbx. Методы расширения также полезны для расширения функциональных возможностей классов незапечатанных классов, но вы не можете расширять их, потому что вы не управляете тем, что возвращает экземпляры, например HttpContextBase, который является абстрактным классом.
Фабио Мильейру
@FabioMilheiro Я щедро включил абстрактные классы в качестве «интерфейсов» в этом контексте. Автоматически сгенерированные классы (xsd.exe) относятся к тому же типу: вы можете, но не должны расширять их, изменяя сгенерированные файлы. Обычно вы расширяете их с помощью «частичного», что требует их размещения в одной сборке. Если это не так, методы расширения являются довольно привлекательной альтернативой. В конечном счете, это только статические методы (нет разницы, если вы посмотрите на сгенерированный код IL).
mbx
Да ... HttpContextBase - это абстракция, хотя я понимаю вашу щедрость. Называть интерфейс абстракцией могло бы показаться более естественным. Несмотря на это, я не имел в виду, что это должна быть абстракция. Я просто привел пример класса, для которого я написал много методов расширения.
Фабио Мильейру
2

Мы можем смоделировать реализацию методов расширения C # в Java, используя реализацию метода по умолчанию, доступную начиная с Java 8. Мы начнем с определения интерфейса, который позволит нам получить доступ к объекту поддержки через метод base (), например, так:

public interface Extension<T> {

    default T base() {
        return null;
    }
}

Мы возвращаем null, так как интерфейсы не могут иметь состояния, но это должно быть исправлено позже через прокси.

Разработчик расширений должен был бы расширить этот интерфейс новым интерфейсом, содержащим методы расширения. Допустим, мы хотим добавить потребителя forEach в интерфейс List:

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

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

}

Поскольку мы расширяем интерфейс Extension, мы можем вызвать метод base () внутри нашего метода extension, чтобы получить доступ к объекту поддержки, к которому мы присоединяемся.

Интерфейс Extension должен иметь фабричный метод, который создаст расширение данного объекта поддержки:

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

Мы создаем прокси, который реализует интерфейс расширения и весь интерфейс, реализованный по типу объекта поддержки. Обработчик вызова, данный прокси, будет отправлять все вызовы к объекту поддержки, за исключением «базового» метода, который должен возвращать объект поддержки, в противном случае его реализация по умолчанию возвращает нуль:

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

}

Затем мы можем использовать метод Extension.create (), чтобы присоединить интерфейс, содержащий метод расширения, к объекту поддержки. Результатом является объект, который может быть приведен к интерфейсу расширения, с помощью которого мы все еще можем получить доступ к объекту поддержки, вызывающему метод base (). Получив ссылку на интерфейс расширения, мы теперь можем безопасно вызывать методы расширения, которые могут иметь доступ к объекту поддержки, чтобы теперь мы могли присоединять новые методы к существующему объекту, но не к его определяющему типу:

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

}

Таким образом, это способ, которым мы можем имитировать способность расширять объекты в Java, добавляя к ним новые контракты, что позволяет нам вызывать дополнительные методы для указанных объектов.

Ниже вы можете найти код интерфейса расширения:

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
это адский клудж!
Дмитрий Автономов
1

Можно было бы использовать декоратор объектно-ориентированного шаблона проектирования . Примером этого шаблона, используемого в стандартной библиотеке Java, может быть DataOutputStream .

Вот некоторый код для расширения функциональности списка:

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 Я большой поклонник Kotlin . У него есть методы расширения, а также он работает на JVM.

Эрик
источник
0

Вы можете создать C # -подобный метод расширения / помощника путем (RE) реализации интерфейса Collections и добавления-примера для 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
источник
-7

Java8 теперь поддерживает методы по умолчанию , которые похожи на C#методы расширения.

DarVar
источник
9
Неправильно; пример в этом вопросе все еще невозможен.
SLaks
@SLaks, в чем разница между расширениями Java и C #?
Фабио Мильейру
3
Методы по умолчанию могут быть определены только внутри интерфейса. docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
SLaks
DarVar, эта ветка комментариев была единственным местом, где упоминались стандартные методы , которые я отчаянно пытался запомнить. Спасибо за упоминание их, если не по имени! :-) (Спасибо @SLaks за ссылку)
jpaugh
Я бы приветствовал этот ответ, потому что конечный результат от статического метода в интерфейсе обеспечил бы то же использование, что и методы расширения C #, однако вам все равно нужно реализовать интерфейс в вашем классе, в отличие от C #, вы передаете ключевое слово (this) в качестве параметра
zaPlayer