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(); 拡張メソッドの良さを提供するlombok-pgを使用します。
NT_2011年

6
マイクロソフトが拡張機能を許可したとき、それは間違いなく正しいものでした。新しい機能を追加するためのサブクラス化は、他の場所で返されたクラスの関数が必要な場合は機能しません。StringとDateにメソッドを追加するように。
tggagne 2012年

3
つまり、java.lang.Stringは最終クラスなので、拡張することはできません。静的メソッドを使用することは1つの方法ですが、コードが判読できない場合があります。拡張メソッド、部分クラス、LINQなど..上
DavutGürbüz

7
@ Roadrunner、rofl!不足している言語機能への最良のレトルトは、不足している言語機能が邪悪で望ましくないということです。これは知られています。
Kirk Woll 2013

6
拡張メソッドは「悪」ではありません。コードの可読性が大幅に向上します。Javaにおける多くの貧弱な設計上の決定のうちの1つだけです。
csauve 2015

回答:


196

Javaは拡張メソッドをサポートしていません。

代わりに、通常の静的メソッドを作成するか、独自のクラスを作成できます。


63
拡張メソッドを使用した後、私は甘やかされて育った-しかし、静的メソッドもトリックを行います。
bbqchickenrobot

31
しかし、構文がとても良く、プログラムを理解しやすくします:)実際に組み込みクラスを変更して新しいメソッドを追加できることを除いて、Rubyでほとんど同じことを実行できることも気に入っています。
Knowasilya 2013年

18
@ケン:はい、そしてそれがすべてのポイントです!なぜJVMバイトコードではなく、Javaで記述するのですか?「構文の問題」ではないですか?
Fyodor Soikin、2013年

30
拡張メソッドを使用すると、他のクラスの静的メソッドを追加するよりもはるかに洗練されたコードを作成できます。最近のほとんどすべての言語では、C#、php、objective-c、javascriptなどの既存のクラス拡張を使用できます。Javaは確かにその時代を示しています。JSONObjectをディスクに書き込みたいとします。jsonobj.writeToDisk()またはsomeunrelatedclass.writeToDisk(jsonobj)を呼び出しますか?
2014年

8
Javaを憎む理由は増え続けています。そして、私は数年前にそれらを探すのをやめました……
John Demetriou

54

拡張メソッドは単なる静的メソッドではなく、単なる便利な構文糖衣ではなく、実際には非常に強力なツールです。主なものは、さまざまなジェネリックのパラメーターのインスタンス化に基づいてさまざまなメソッドをオーバーライドする機能です。これはHaskellの型クラスに似ており、実際はC#のモナド(つまりLINQ)をサポートするためにC#にあるようです。LINQ構文を削除しても、Javaで同様のインターフェイスを実装する方法はまだわかりません。

そして、Javaのジェネリックパラメーターの型消去のセマンティクスのため、Javaでそれらを実装することは可能ではないと思います。


また、複数の動作を継承することもできます(多態性とは異なります)。複数のインターフェースを実装することができ、それにはそれらの拡張メソッドが付属しています。また、システム全体で型にグローバルに関連付けられることなく、型にアタッチする動作を実装することもできます。
賢いネオロジズム

25
この全体の答えは間違っています。C#の拡張メソッドは、コンパイラーがメソッドの呼び出しのターゲットを静的メソッドの最初の引数に移動するために少し並べ替える、単なる構文上の砂糖です。既存のメソッドをオーバーライドすることはできません。拡張メソッドがモナドである必要はありません。文字通り、静的メソッドを呼び出すより便利な方法であり、インスタンスメソッドをクラスに追加するように見えます。この回答に同意する場合は、こちらをお読みください
Matt Klein

3
さて、この場合、構文シュガーとは何かを定義します。拡張メソッドコンパイラーは、少なくとも拡張メソッドが配置する静的クラスを検索する必要があるため、構文マクロを内部マクロ構文と呼びます。メソッドについての答えには、意味のないモナドであるべきものは何もありません。また、オーバーロードに使用できますが、拡張メソッド機能ではありません。これは、単純なパラメータータイプベースのオーバーロードであり、メソッドが直接呼び出された場合と同様に機能し、Javaの多くの興味深いケースでは機能しません。ジェネリック型引数の消去のため。
user1686250 2017

1
@ user1686250 Javaで実装できます(「Java」とは、JVMで実行されるバイトコードを意味するものと想定しています)...バイトコードにコンパイルされるKotlinには拡張機能があります。静的メソッドに対する単なる構文上の砂糖です。IntelliJの逆コンパイラを使用して、同等のJavaがどのように見えるかを確認できます。
Jeffrey Blattman、2018

@ user1686250ジェネリックについてのポイントが絶対にわからないので、ジェネリックについて書いている内容を開発(またはリンクを提供)していただけませんか。通常の静的メソッド以外にどのように関連していますか?
C.シャンパーニュ


10

技術的には、C#拡張機能には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の JavaソースコードへのJavaのスーパーセットであり、そしてコンパイル-言語1  -これをサポートしています。


Java以外のコードをJavaにコンパイルする場合、拡張メソッドはありますか?または、Javaコードは単なる静的メソッドですか?
Fabio Milheiro 2014

@Bomboca他の人が指摘したように、Javaには拡張メソッドがありません。したがって、JavaにコンパイルされたXTendコードは、どういうわけかJava拡張メソッドを作成しません。しかし、もしXTendだけで作業している場合は、気付かないし、気にする必要もありません。しかし、あなたの質問に答えるために、必ずしも静的メソッドを持っている必要はありません。XTENDの主な著者は、このことについてブログのエントリがあるblog.efftinge.de/2011/11/...
エリックG.ハグストロームを

はい、なぜ私もそう思わなかったのか分かりません。ありがとう!
Fabio Milheiro、2015

@Sam XTendを紹介してくれてありがとう-聞いたことがない。
jpaugh

7

Manifoldは、C#スタイルの拡張メソッドとその他のいくつかの機能をJavaに提供します。他のツールとは異なり、マニフォールドには制限がなく、ジェネリック、ラムダ、IDEなどの問題に悩まされません。マニフォールドは、F#スタイルのカスタムタイプ、TypeScriptスタイルの構造インターフェース、Javascriptスタイルのエキスパンドタイプなど、他のいくつかの機能を提供します

さらに、IntelliJは、マニホールドプラグインを介してマニホールドの包括的なサポートを提供します

Manifoldは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
それはまったく役に立たない。
10

2
@Slaks:なぜ正確に?これはあなたが提案した「あなた自身のクラスを書く」です。
Goran Jovic

23
@Goran:これにより、メソッドを定義して、すぐに一度だけ呼び出すことができます。
SLaks

3
@Slaks:大丈夫です、ポイントを取ります。その限られたソリューションと比較して、名前付きクラスを書く方が良いでしょう。
Goran Jovic

C#拡張メソッドとJava匿名クラスの間には大きな違いがあります。C#では、拡張メソッドは、実際には単なる静的メソッドの構文糖衣です。IDEとコンパイラは、拡張メソッドを拡張クラスのインスタンスメソッドであるかのように見せます。(注:このコンテキストでの「拡張」は、Javaで通常行われる「継承」を意味しません。)
HairOfTheDog

4

Defenderメソッド(つまり、デフォルトのメソッド)がJava 8になる可能性は少しあるようです。しかし、私が理解している限り、作成interface任意のユーザーではなく、遡って拡張することしかできません。

Defenderメソッド+インターフェイスインジェクションは、C#スタイルの拡張メソッドを完全に実装できますが、AFAICSのインターフェイスインジェクションは、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
拡張メソッドは通常、final / sealedクラスのように変更または継承できないコード用であり、その主な機能は、IEnumerable <T>の拡張など、インターフェイスの拡張です。もちろん、それらは静的メソッドの構文糖衣にすぎません。目的は、コードをより読みやすくすることです。よりクリーンなコードは、より優れた保守性/進化性を意味します。
mbx 2012

1
@mbxだけではありません。拡張メソッドは、シールされていないクラスのクラス機能を拡張するのにも役立ちますが、抽象クラスであるHttpContextBaseなど、インスタンスを返すものは何も制御できないため、拡張できません。
Fabio Milheiro 2012

@FabioMilheiro私はそのコンテキストに「インターフェース」として抽象クラスを惜しみなく含めました。自動生成されたクラス(xsd.exe)は同じ種類です。生成されたファイルを変更することで、それらを拡張することはできますが、拡張しないでください。通常は、「部分的」を使用してそれらを拡張します。これには、それらを同じアセンブリに配置する必要があります。そうでない場合、拡張メソッドは見栄えの良い代替手段です。結局のところ、これらは静的メソッドにすぎません(生成されたILコードを確認しても違いはありません)。
mbx 2012

はい... HttpContextBaseは抽象化ですが、私はあなたの寛大さを理解しています。インターフェースを抽象化と呼ぶことは、いくぶん自然に思われたかもしれません。それにもかかわらず、私はそれが抽象である必要があることを意味しなかった。私は、多くの拡張メソッドを記述したクラスの例を示しただけです。
Fabio Milheiro、2012

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()メソッドを呼び出して、アタッチするサポートオブジェクトにアクセスできます。

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

拡張インターフェースと、サポートオブジェクトのタイプによって実装されるすべてのインターフェースを実装するプロキシを作成します。プロキシに指定された呼び出しハンドラは、サポートオブジェクトを返す必要がある「base」メソッドを除いて、サポートオブジェクトへのすべての呼び出しをディスパッチします。それ以外の場合、デフォルトの実装は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
それは地獄の一つです!
Dmitry Avtonomov

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

コレクションインターフェイスを(RE)実装し、Javaコレクションの例を追加することで、拡張機能/ヘルパーメソッドのような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 2014年

@SLaks Java拡張機能とC#拡張機能の違いは何ですか?
Fabio Milheiro 2014年

3
デフォルトのメソッドは、インターフェース内でのみ定義できます。 docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
SLaks

DarVar、このコメントスレッドは、デフォルトメソッドが言及された唯一の場所であり、私は必死に覚えようとしていました。名前ではないとしても、それらに言及してくれてありがとう!:
jpaugh

私はその答えを賛成し、インターフェイスの静的メソッドからの最終結果はC#拡張メソッドと同じ使用法を提供するため、ただし、パラメーターとして(この)キーワードを渡すC#とは異なり、クラスにインターフェイスを実装する必要があります
zaPlayer
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.