C # 확장 메소드와 동등한 Java


176

확장 방법을 사용하여 C #에서와 같이 객체 목록에 기능을 구현하려고합니다.

이 같은:

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

Java로 어떻게합니까?


8
github.com/nicholas22/jpropel, example : new String [] { "james", "john", "john", "eddie"} .where (startsWith ( "j")). distinct ();를 확인하십시오. 확장 방법이 좋은 롬복 -pg를 사용합니다.
NT_

6
Microsoft는 확장 기능을 허용했을 때 제대로 맞았습니다. 다른 곳으로 돌아온 클래스의 함수가 필요한 경우 새로운 기능을 추가하기위한 서브 클래 싱이 작동하지 않습니다. 문자열과 날짜에 메소드를 추가하는 것과 같습니다.
tggagne

3
즉, java.lang.String은 최종 클래스이므로 확장 할 수 없습니다. 정적 메서드를 사용하는 방법이지만 코드를 읽을 수없는 경우가 종종 있습니다 .C #은 컴퓨터 언어로 시대를 떠났다고 생각합니다. 확장 방법, 부분 클래스, LINQ 등.
Davut Gürbüz

7
@Roadrunner, rofl! 누락 된 언어 기능에 대한 가장 좋은 레토르트는 누락 된 언어 기능이 사악하고 원치 않는 것입니다. 이것은 알려져 있습니다.
Kirk Woll

6
확장 방법은 "악"이 아닙니다. 코드 가독성을 크게 향상시킵니다. Java의 많은 잘못된 디자인 결정 중 하나 이상.
csauve

답변:


196

Java는 확장 메소드를 지원하지 않습니다.

대신 일반 정적 메서드를 만들거나 자신의 클래스를 작성할 수 있습니다.


63
확장 메서드를 사용한 후 손상되었지만 정적 메서드도 트릭을 수행합니다.
bbqchickenrobot 2019

31
그러나 구문이 너무 좋으며 프로그램을 더 쉽게 이해할 수 있습니다.
knownasilya

18
@ 켄 : 예, 그게 요점입니다! JVM 바이트 코드에서 직접 작성하지 않고 Java로 작성하는 이유는 무엇입니까? "구문의 문제"가 아닌가?
Fyodor Soikin

30
확장 메소드는 다른 클래스의 추가 정적 메소드와 비교하여 코드를 훨씬 더 우아하게 만들 수 있습니다. 거의 모든 현대 언어는 C #, php, objective-c, javascript와 같은 기존 클래스 확장을 허용합니다. 자바는 분명히 나이를 보여줍니다. 디스크에 JSONObject를 작성한다고 가정하십시오. jsonobj.writeToDisk () 또는 someunrelatedclass.writeToDisk (jsonobj)를 호출합니까?
받음

8
Java를 싫어하는 이유는 계속 커지고 있습니다. 그리고 나는 2 년 전에 그것들을 찾는 것을 그만 두었습니다.
John Demetriou

54

확장 메소드는 정적 메소드 일뿐 아니라 편의 구문 설탕 만이 아니라 실제로는 매우 강력한 도구입니다. 가장 중요한 것은 다른 제네릭의 매개 변수 인스턴스화에 따라 다른 방법을 재정의하는 기능입니다. 이것은 Haskell의 형식 클래스와 유사하며 실제로 C #의 Monads (LINQ)를 지원하기 위해 C #에있는 것처럼 보입니다. LINQ 구문을 삭제하더라도 Java에서 유사한 인터페이스를 구현하는 방법을 아직 모릅니다.

그리고 제네릭 매개 변수의 Java 유형 삭제 의미 때문에 Java로 구현할 수 없다고 생각합니다.


또한 여러 동작 (산 다형성)을 상속 할 수 있습니다. 여러 인터페이스를 구현할 수 있으며 확장 인터페이스가 제공됩니다. 또한 시스템 전체에서 유형과 전체적으로 연결되지 않고도 유형에 첨부하려는 동작을 구현할 수 있습니다.
Clever Neologism

25
이 전체 답변이 잘못되었습니다. C #의 확장 메서드는 구문 설탕으로, 메서드 호출의 대상을 정적 메서드의 첫 번째 인수로 이동하기 위해 컴파일러가 약간 재정렬합니다. 기존 메소드를 대체 할 수 없습니다. 확장 방법이 모나드 일 필요는 없습니다. 말 그대로 정적 메소드를 호출하는 더 편리한 방법으로 클래스에 인스턴스 메소드를 추가하는 것처럼 보입니다. 이 답변에 동의하면 이것을 읽으십시오
Matt Klein

3
글쎄,이 경우 구문 설탕이 무엇인지 정의하고 구문 설탕을 내부 매크로 구문이라고 부릅니다. 확장 메소드의 경우 컴파일러는 적어도 확장 메소드가 대체 할 정적 클래스를 찾아야합니다. 무의미한 모나드이어야하는 방법에 대한 답은 없습니다. 또한 오버로드에 사용할 수 있지만 확장 메소드 기능은 아니며 일반 매개 변수 유형 기반 오버로드입니다. 메소드를 직접 호출하면 작동하는 방식과 동일하며 Java의 많은 흥미로운 경우에는 작동하지 않습니다 제네릭 형식 인수가 삭제 되었기 때문입니다.
user1686250

1
@ user1686250 Java로 구현할 수있다 ( "Java"에 의해 JVM에서 실행되는 바이트 코드를 의미한다고 가정한다) ... Kotlin, 바이트 코드로 컴파일하는 확장자가있다. 정적 방법에 대한 구문 설탕입니다. IntelliJ에서 디 컴파일러를 사용하여 해당 Java가 어떻게 보이는지 확인할 수 있습니다.
Jeffrey Blattman

@ user1686250 제네릭에 대해 전혀 이해하지 못하기 때문에 제네릭에 대해 쓰고있는 것을 개발할 수 있습니까 (또는 링크 제공)? 일반적인 정적 메소드 이외의 방법과 관련이 있습니까?
C.Champagne


10

기술적으로 C # Extension은 Java와 동등한 기능이 없습니다. 그러나보다 명확한 코드와 유지 관리 성을 위해 이러한 기능을 구현하려면 매니 폴드 프레임 워크를 사용해야합니다.

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

XTend의 자바 소스 코드에 자바의 슈퍼 세트이며, 컴파일 - 언어 1  -이 작업을 지원합니다.


Java가 아닌 코드가 Java로 컴파일되면 확장 방법이 있습니까? 아니면 Java 코드는 정적 메소드입니까?
Fabio Milheiro

@Bomboca 다른 사람들이 지적했듯이 Java에는 확장 방법이 없습니다. 따라서 Java로 컴파일 된 XTend 코드는 어떻게 든 Java 확장 메소드를 작성하지 않습니다. 그러나 XTend에서 독점적으로 일하는 경우주의를 기울이거나 신경 쓰지 않습니다. 그러나 귀하의 질문에 대답하기 위해 정적 메소드가 반드시 필요한 것은 아닙니다. XTend의 주요 저자는 blog.efftinge.de/2011/11/…에서 이에
Erick G. Hagstrom

예, 왜 그렇게 생각하지 않았는지 모르겠습니다. 감사!
Fabio Milheiro

@Sam XTend를 소개해 주셔서 감사합니다. 들어 본 적이 없습니다.
jpaugh

7

매니 폴드 는 Java에 C # 스타일 확장 메소드 및 기타 여러 기능을 제공합니다. 다른 도구와는 달리, 매니 폴드에는 제한이 없으며하지 않는 고통 제네릭, 람다, IDE 등 매니 폴드와 같은 F 번호 스타일 등 여러 가지 다른 기능 제공과 함께 문제에서 사용자 정의 유형 , 타이프 라이터 스타일의 구조 인터페이스 , 자바 스크립트 스타일 의 expando 유형 .

또한 IntelliJ는 매니 폴드 플러그인을 통해 매니 폴드를 포괄적으로 지원합니다 .

매니 폴드는 github에서 사용할 수있는 오픈 소스 프로젝트 입니다.



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

112
그것은 전혀 쓸모 가 없습니다.
SLaks

2
@ 슬 락스 : 왜 정확히? 이것은 자신이 제안한 "자신의 수업을 작성"입니다.
Goran Jovic

23
@Goran :이 방법으로 메소드를 정의한 다음 즉시 한 번만 호출 하면 됩니다.
SLaks

3
@ 슬 락스 : 좋아, 포인트를 촬영. 제한된 솔루션과 비교하여 명명 된 클래스를 작성하는 것이 좋습니다.
Goran Jovic

C # 확장 메소드와 Java 익명 클래스에는 큰 차이가 있습니다. C #에서 확장 방법은 실제로 정적 방법 인 구문 설탕입니다. IDE와 컴파일러는 확장 메서드가 확장 클래스의 인스턴스 메서드 인 것처럼 보이게합니다. (참고 :이 문맥에서 "확장 된"은 Java에서 일반적으로하는 "상속 된"을 의미하지 않습니다.)
HairOfTheDog

4

Defender Methods (예 : 기본 메소드)가 Java 8로 만들 수있는 약간의 기회가있는 것 같습니다. 그러나 내가 이해하는 한, 저자interface임의의 사용자가 아니라 소급하여 확장 할 수 있습니다.

Defender Methods + 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).
Fabio Milheiro

@FabioMilheiro 나는 그 맥락에서 추상 클래스를 "인터페이스"로 관대하게 포함시켰다. 자동 생성 클래스 (xsd.exe)는 같은 종류입니다. 생성 된 파일을 수정하여 확장 할 수는 있지만 확장해서는 안됩니다. 일반적으로 동일한 어셈블리에 있어야하는 "부분"을 사용하여 확장합니다. 그렇지 않은 경우 확장 방법은 매우 대안입니다. 궁극적으로, 이들은 정적 메소드 일뿐입니다 (생성 된 IL 코드를 보면 차이가 없습니다).
mbx

예. HttpContextBase는 추상화되었지만 관대함을 이해합니다. 인터페이스를 추상화라고 부르면 다소 자연스러운 것처럼 보일 수 있습니다. 그럼에도 불구하고 나는 그것이 추상화되어야한다는 것을 의미하지는 않았다. 방금 많은 확장 메서드를 작성한 클래스의 예를 들었습니다.
Fabio Milheiro

2

Java 8부터 사용 가능한 기본 메소드 구현을 사용하여 Java에서 C # 확장 메소드의 구현을 시뮬레이션 할 수 있습니다. 다음과 같이 base () 메소드를 통해 지원 오브젝트에 액세스 할 수있는 인터페이스를 정의하여 시작합니다.

public interface Extension<T> {

    default T base() {
        return null;
    }
}

인터페이스는 상태를 가질 수 없으므로 null을 반환하지만 나중에 프록시를 통해 수정해야합니다.

확장 개발자는 확장 메서드가 포함 된 새 인터페이스로이 인터페이스를 확장해야합니다. List 인터페이스에 forEach 소비자를 추가한다고 가정 해 보겠습니다.

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

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

}

Extension 인터페이스를 확장하기 때문에 확장 메서드 내에서 base () 메서드를 호출하여 연결된 지원 개체에 액세스 할 수 있습니다.

확장 인터페이스에는 지정된 지원 객체의 확장을 생성하는 팩토리 메소드가 있어야합니다.

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
그것은 kludge의 지옥이다!
Dmitry Avtonomov

1

하나는 데코레이터 객체 지향 디자인 패턴을 사용할 수 있습니다 . Java의 표준 라이브러리에서 사용되는이 패턴의 예는 DataOutputStream입니다. 입니다.

다음은 List의 기능을 향상시키기위한 코드입니다.

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

추신 : 나는 Kotlin 의 큰 팬입니다 . 확장 메소드가 있으며 JVM에서도 실행됩니다.


0

(RE) Collections 인터페이스를 구현하고 Java Collection에 대한 예제를 추가하여 C #과 같은 확장 / 헬퍼 메소드를 작성할 수 있습니다.

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은 이제 기본 메소드를 지원하며 이는 C#확장 메소드 와 유사 합니다.


9
잘못된; 이 질문의 예는 여전히 불가능합니다.
SLaks

@SLaks Java와 C # 확장의 차이점은 무엇입니까?
Fabio Milheiro

3
기본 메소드는 인터페이스 내에서만 정의 할 수 있습니다. docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
SLaks

DarVar,이 주석 스레드는 기본 방법 이 언급 된 유일한 곳 으로 필사적으로 기억하려고했습니다. 이름이 아니라면 언급 해 주셔서 감사합니다! :-) (링크에 대한 @SLaks 감사)
jpaugh

나는 C # 확장 방법과 동일한 사용을 제공 할 인터페이스의 정적 메서드에서 최종 결과 원인, 그러나, 당신은 여전히 매개 변수로는 통과 C #을 달리 클래스 (이) 키워드에 인터페이스를 구현해야 그 대답을 upvote에 것
zaPlayer
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.