C # uzantısı yöntemlerine eşdeğer Java


176

Ben bir uzantı yöntemi kullanarak C # gibi bir nesne listesinde bir işlevsellik uygulamak için arıyorum.

Bunun gibi bir şey:

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

Java ile nasıl yapabilirim?


8
Bunu kontrol edin: github.com/nicholas22/jpropel, örnek: new String [] {"james", "john", "john", "eddie"} .where (startsWith ("j")). Farklı (); Uzatma yöntemi iyiliği sağlayan lombok-pg kullanır.
NT_

6
Microsoft, uzantılara izin verdiklerinde kesinlikle doğru yaptı. Başka bir yerde bana döndürülen bir sınıfta işlev gerekiyorsa yeni işlevsellik eklemek için alt sınıflar çalışmıyor. Dize ve Tarihe yöntem ekleme gibi.
tggagne

3
yani java.lang.String bir son sınıftır, bu yüzden genişletemezsiniz. Statik yöntemler kullanmak bir yol ama bazen kodu okunamaz gösterir. C # bir bilgisayar dili olarak bir yaş bıraktı düşünüyorum. Genişletme yöntemleri, kısmi dersler, LINQ vb.
Davut Gürbüz

7
@Roadrunner, rofl! Eksik dil özelliğine en iyi cevap, söz konusu eksik dil özelliğinin kötü ve istenmeyen olmasıdır. Bu biliniyor.
Kirk Woll

6
Genişletme yöntemleri "kötü" değildir. Kod okunabilirliğini büyük ölçüde artırırlar. Java'daki birçok kötü tasarım kararından sadece bir tane daha.
csauve

Yanıtlar:


196

Java, uzantı yöntemlerini desteklemez.

Bunun yerine, düzenli bir statik yöntem yapabilir veya kendi sınıfınızı yazabilirsiniz.


63
Ben uzatma yöntemleri kullandıktan sonra şımarık - ama statik yöntemler de hile yapacak.
bbqchickenrobot

31
Ancak sözdizimi çok güzel ve programın anlaşılmasını kolaylaştırıyor :) Ayrıca, yerleşik sınıfları değiştirip yeni yöntemler ekleyebilmeniz dışında, Ruby'nin neredeyse aynı şeyi yapmanıza nasıl izin verdiğini de seviyorum.
bilinenasilya

18
@Ken: Evet, asıl mesele bu! Neden Java ile yazıyorsunuz, doğrudan JVM bayt kodunda yazmıyorsunuz? "Sadece sözdizimi meselesi" değil mi?
Fyodor Soikin

30
Genişletme yöntemleri, diğer bazı sınıfların ekstra statik yöntemlerine kıyasla kodu çok daha zarif hale getirebilir. Neredeyse tüm modern diller bir çeşit mevcut sınıf uzantısına izin verir: C #, php, objektif-c, javascript. Java kesinlikle yaşını burada gösteriyor. Diske bir JSONObject yazmak istediğinizi düşünün. Jsonobj.writeToDisk () veya someunrelatedclass.writeToDisk (jsonobj) 'u çağırıyor musunuz?
14'te

8
Java'dan nefret etme nedenleri büyümeye devam ediyor. Ve birkaç yıl önce onları aramayı bıraktım ......
John Demetriou

54

Uzatma yöntemleri sadece statik yöntem değildir ve sadece kolaylık sözdizimi şekeri değildir, aslında oldukça güçlü bir araçtır. Ana şey, farklı jenerik parametrelerinin somutlaştırılmasına dayalı olarak farklı yöntemleri geçersiz kılma yeteneğidir. Bu Haskell'in tip sınıflarına benzer ve aslında, C # 'ın Monad'larını (yani LINQ) desteklemek için C #' da gibi görünüyorlar. LINQ sözdizimini bırakarak bile, Java'da benzer arayüzleri uygulamanın hiçbir yolunu bilmiyorum.

Java'nın jenerik parametrelerin tip silme semantiği nedeniyle bunları Java'da uygulamanın mümkün olduğunu düşünmüyorum.


Ayrıca birden fazla davranışı (sans polimorfizmi) miras almanıza izin verir. Birden fazla arabirim uygulayabilirsiniz ve bununla birlikte uzantı yöntemleri de gelir. Ayrıca, bir türe eklemek istediğiniz davranışı, sistem genelinde türle global olarak ilişkilendirmeden uygulamanızı sağlar.
Akıllı Neologizm

25
Bütün bu cevap yanlış. C # 'daki genişletme yöntemleri, derleyicinin yöntem çağrısının hedefini statik yöntemin ilk argümanına taşımak için biraz yeniden düzenlediği sözdizimsel şekerdir. Mevcut yöntemleri geçersiz kılamazsınız. Genişletme yönteminin monad olmasına gerek yoktur. Kelimenin tam anlamıyla, bir sınıfa örnek yöntemleri ekleme görünümü veren statik bir yöntemi çağırmanın daha kolay bir yoludur. Bu yanıta katılıyorsanız lütfen bunu okuyun
Matt Klein

3
iyi, bu durumda, sözdizimi şekerinin ne olduğunu tanımlayın, sözdizimi şekerini dahili makro sözdizimi olarak adlandırırım, genişletme yöntemleri için derleyici en azından genişletme yönteminin yerine konacak statik sınıfı aramalıdır. Yöntemde cevapta anlamsız olan monad olması gereken hiçbir şey yok. Ayrıca, aşırı yükleme için kullanabilirsiniz, ancak uzatma yöntemleri özelliği değildir, yöntem doğrudan çağrılırsa aynı şekilde çalışır ve Java'da birçok ilginç durumda çalışmaz çünkü jenerik tip argümanları silme.
user1686250

1
@ user1686250 Java uygulamak mümkün ("Java" tarafından bir JVM üzerinde çalışan bayt kodu demek istiyorum) ... Bayt kodu derleyen uzantıları vardır Kotlin. Statik yöntemlere göre sadece sözdizimsel şekerdir. Eşdeğer Java'nın nasıl göründüğünü görmek için IntelliJ'deki kod çözücüyü kullanabilirsiniz.
Jeffrey Blattman

@ user1686250 Jenerikler hakkında yazdıklarınızı geliştirebilir misiniz (veya bir link verirsiniz) çünkü jenerikler hakkında kesinlikle bir fikrim yok. Normal statik yöntemlerden başka hangi yolla ilişkilidir?
C.Champagne


10

Teknik olarak C # Uzantısı'nın Java'da eşdeğeri yoktur. Ancak, daha temiz bir kod ve sürdürülebilirlik için bu tür işlevleri uygulamak istiyorsanız, Manifold çerçevesini kullanmanız gerekir.

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 kaynak koduna Java süper dizi ve derler - dil 1  - Bu destekler.


Java olmayan kod Java'ya derlendiğinde bir uzantı yönteminiz var mı? Yoksa Java kodu sadece statik bir yöntem mi?
Fabio Milheiro

@Bomboca Diğerlerinin de belirttiği gibi, Java'nın uzantı yöntemleri yoktur. Bu nedenle, Java için derlenen XTend kodu bir şekilde bir Java uzantısı yöntemi oluşturmaz. Ama sadece XTend'de çalışıyorsanız, fark etmeyecek veya umursamayacaksınız. Ancak, sorunuzu cevaplamak için statik bir yönteminiz de olması gerekmez. XTend'in ana yazarı blog.efftinge.de/2011/11/…
Erick G. Hagstrom

Evet, neden böyle düşünmediğimi bilmiyorum. Teşekkürler!
Fabio Milheiro

@Sam Beni XTend ile tanıştırdığın için teşekkürler - daha önce hiç duymamıştım.
jpaugh

7

çeşitli , Java'ya C # stili uzantı yöntemleri ve diğer birçok özellik sağlar. Diğer araçlar aksine, Manifold herhangi bir sınırlama vardır ve gelmez acı jenerik, lambdas, IDE vb Manifold böyle F # tarzı gibi çeşitli diğer özellikler sağlar ile sorunları özel tipleri , typescript tarzı yapısal arayüzleri ve JavaScript tarzı expando türleri .

Ayrıca IntelliJ, Manifold eklentisi aracılığıyla Manifold için kapsamlı destek sağlar .

Manifold geçerli bir açık kaynak projesi olan github .



5

Java'nın böyle bir özelliği yoktur. Bunun yerine, liste uygulamanızın normal alt sınıfını oluşturabilir veya anonim iç sınıf oluşturabilirsiniz:

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

Sorun bu yöntemi çağırmaktır. Bunu "yerinde" yapabilirsiniz:

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

112
Bu tamamen işe yaramaz.
SLaks

2
@Slaks: Neden tam olarak? Bu, kendi önerdiğiniz "kendi sınıfınızı yazın" dır.
Goran Jovic

23
@ Goran: Tüm bunları yapmanıza izin veren bir yöntem tanımlamak ve sonra hemen bir kez çağırmaktır .
SLaks

3
@Slaks: Pekala, puan alındı. Bu sınırlı çözümle karşılaştırıldığında, adlandırılmış bir sınıf yazmak daha iyi olurdu.
Goran Jovic

C # uzantı yöntemleri ve Java anonim sınıfları arasında büyük, büyük bir fark vardır. C # 'da bir uzatma yöntemi, sadece statik bir yöntem için sözdizimsel şekerdir. IDE ve derleyici, bir genişletme yönteminin genişletilmiş sınıfın bir örnek yöntemi gibi görünmesini sağlar. (Not: Bu bağlamda "genişletilmiş", normalde Java'da olduğu gibi "miras alınan" anlamına gelmez.)
HairOfTheDog

4

Defender Yöntemleri (yani varsayılan yöntemleri) Ancak, bildiğim kadarıyla anladığımız şekliyle, sadece izin Java 8. haline getirebilir bazı küçük bir şans var gibi görünüyor yazar bir bir interfacegeriye dönük keyfi kullanıcıları değil genişletmek için.

Defender Yöntemleri + Arayüz Enjeksiyonu daha sonra C # tarzı uzatma yöntemlerini tam olarak uygulayabilecektir, ancak AFAICS, Arayüz Enjeksiyonu henüz Java 8 yol haritasında bile değildir.


3

Bu soruya partiye biraz geç, ancak herkesin faydalı bulması durumunda bir alt sınıf oluşturdum:

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
Genişletme yöntemleri genellikle son / kapalı sınıflar gibi değiştirilemeyen veya miras alınamayan kodlar içindir ve ana gücü, Arayüzlerin genişletilmesidir, örneğin IEnumerable <T> 'yi genişletir. Tabii ki, sadece statik yöntemler için sözdizimsel şekerdir. Amaç, kodun çok daha okunabilir olmasıdır. Daha temiz kod, daha iyi bakım / evrilebilirlik anlamına gelir.
mbx

1
Bu sadece @mbx değil. Genişletme yöntemleri, mühürlenmemiş sınıfların sınıf işlevselliğini genişletmek için de yararlıdır, ancak döndüremeyen örnekleri (ör. Soyut bir sınıf olan HttpContextBase) denetleyemediğiniz için genişletemeyeceğinizdir.
Fabio Milheiro

@FabioMilheiro Bu bağlamda soyut sınıfları cömertçe "arayüzler" olarak dahil ettim. Otomatik oluşturulan sınıflar (xsd.exe) aynı türdedir: Oluşturulan dosyaları değiştirerek bunları genişletemezsiniz. Normalde, aynı montajda bulunmalarını gerektiren "kısmi" kullanarak bunları genişletirsiniz. Değilse, uzantı yöntemleri oldukça iyi bir alternatiftir. Sonuçta, bunlar sadece statik yöntemlerdir (oluşturulan IL Koduna bakarsanız hiçbir fark yoktur).
mbx

Evet ... Cömertliğinizi anladığım halde HttpContextBase bir soyutlamadır. Bir arayüze soyutlama demek biraz daha doğal görünebilirdi. Ne olursa olsun, bunun bir soyutlama olması gerektiği anlamına gelmiyordum. Az önce birçok uzatma yöntemi yazdığım bir sınıf örneği verdim.
Fabio Milheiro

2

Java 8'den beri kullanılabilen varsayılan yöntem uygulamasını kullanarak Java'da C # uzantısı yöntemlerinin uygulanmasını simüle edebiliriz. Bu şekilde, destek nesnesine bir base () yöntemiyle erişmemizi sağlayacak bir arabirim tanımlayarak başlayalım:

public interface Extension<T> {

    default T base() {
        return null;
    }
}

Arabirimlerin durumu olamaz çünkü null döndürüyoruz, ancak bu daha sonra bir proxy aracılığıyla düzeltilmelidir.

Uzantı geliştiricisinin, bu arayüzü, uzantı yöntemlerini içeren yeni bir arayüzle genişletmesi gerekir. Diyelim ki Liste arayüzüne bir forEach tüketici eklemek istiyoruz:

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

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

}

Uzantı arayüzünü genişlettiğimizden, eklediğimiz destek nesnesine erişmek için uzantı yöntemimizin içindeki base () yöntemini çağırabiliriz.

Uzantı arabirimi, belirli bir destek nesnesinin uzantısını oluşturacak bir fabrika yöntemine sahip olmalıdır:

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

Uzantı arabirimini ve destek nesnesinin türü tarafından uygulanan tüm arabirimi uygulayan bir proxy oluştururuz. Proxy'ye verilen çağırma işleyicisi, destek nesnesini döndürmesi gereken "base" yöntemi dışında, destek nesnesine yapılan tüm çağrıları gönderir, aksi takdirde varsayılan uygulaması null döndürür:

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

}

Ardından, uzantı yöntemini içeren arabirimi destek nesnesine eklemek için Extension.create () yöntemini kullanabiliriz. Sonuç, base () yöntemini çağıran destek nesnesine erişebileceğimiz uzantı arabirimine dökülebilen bir nesnedir. Referansın uzantı arayüzüne dökülmesiyle, artık destek nesnesine erişebilecek uzantı yöntemlerini güvenli bir şekilde çağırabiliriz, böylece şimdi mevcut nesneye yeni yöntemler ekleyebiliriz, ancak tanımlayıcı türüne ekleyemeyiz:

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

}

Yani, bu, Java'da nesneleri, onlara yeni sözleşmeler ekleyerek genişletme yeteneğini simüle etmenin bir yoludur, bu da verilen nesneler üzerinde ek yöntemler çağırmamıza izin verir.

Uzantı arayüzünün kodunu aşağıda bulabilirsiniz:

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
Bu bir çamur cehennemi!
Dmitry Avtonomov

1

Birisi dekoratör nesneye yönelik tasarım desenini kullanmak olabilir . Java'nın standart kitaplığında kullanılan bu desene bir örnek DataOutputStream olabilir .

Bir Listenin işlevselliğini artırmak için bazı kodlar:

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'in büyük bir hayranıyım . Uzantı yöntemleri vardır ve ayrıca JVM üzerinde çalışır.


0

Koleksiyonlar arabirimini uygulayarak ve Java Koleksiyonu için örnek ekleyerek (RE) ile C # benzeri bir uzantı / yardımcı yöntem oluşturabilirsiniz:

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 artık uzantı yöntemlerine benzeyen varsayılan yöntemleri desteklemektedir C#.


9
Yanlış; bu sorudaki örnek hala imkansız.
SLaks

@SLaks Java ve C # uzantıları arasındaki fark nedir?
Fabio Milheiro

3
Varsayılan yöntemler yalnızca arabirim içinde tanımlanabilir. docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
SLaks

DarVar, Bu yorum iş parçacığı , umutsuzca hatırlamaya çalışıyorum varsayılan yöntemlerden bahsedildiği tek yerdi . Onlardan bahsettiğiniz için teşekkürler, isimle değilse! :-) (Bağlantı için teşekkürler @SLaks)
jpaugh

Ben cevap cevap, arayüzde statik bir yöntemden nihai sonuç C # uzantısı yöntemleri ile aynı kullanımı sağlayacak neden olur, ancak, yine de bir parametre olarak C # pass (this) anahtar kelime aksine sınıfta arabirimi uygulamak gerekir
zaPlayer
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.