Java setara dengan metode ekstensi C #

176

Saya mencari untuk mengimplementasikan fungsionalitas dalam daftar objek seperti yang saya lakukan di C # menggunakan metode ekstensi.

Sesuatu seperti ini:

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

Bagaimana saya melakukannya di Jawa?

Fabio Milheiro
sumber
8
Periksa yang ini: github.com/nicholas22/jpropel, contoh: String baru [] {"james", "john", "john", "eddie"} .where (beginWith ("j")). Different (); Ini menggunakan lombok-pg yang memberikan kebaikan metode ekstensi.
NT_
6
Microsoft pasti benar ketika mereka mengizinkan ekstensi. Subkelas untuk menambahkan fungsionalitas baru tidak berfungsi jika saya membutuhkan fungsi di kelas dikembalikan kepada saya di tempat lain. Seperti menambahkan metode ke String dan Date.
tggagne
3
yaitu java.lang.String adalah kelas terakhir, jadi Anda tidak dapat memperpanjangnya. Menggunakan metode statis adalah cara tetapi kadang-kadang menunjukkan kode tidak dapat dibaca. Saya pikir C # meninggalkan usia sebagai bahasa komputer. Metode ekstensi, kelas parsial, LINQ dan sebagainya ..
Davut Gürbüz
7
@Roadrunner, rofl! Retort terbaik untuk fitur bahasa yang hilang adalah bahwa fitur bahasa yang hilang itu jahat dan tidak diinginkan. Ini sudah diketahui.
Kirk Woll
6
Metode penyuluhan bukan "jahat". Mereka sangat meningkatkan keterbacaan kode. Hanya satu lagi dari banyak keputusan desain yang buruk di Jawa.
csauve

Jawaban:

196

Java tidak mendukung metode ekstensi.

Sebagai gantinya, Anda dapat membuat metode statis biasa, atau menulis kelas Anda sendiri.

Slaks
sumber
63
Saya manja setelah menggunakan metode ekstensi - tetapi metode statis akan melakukan trik juga.
bbqchickenrobot
31
Tapi sintaksnya sangat bagus, dan membuat program lebih mudah dimengerti :) Saya juga suka bagaimana Ruby memungkinkan Anda untuk melakukan hal yang hampir sama, kecuali Anda benar-benar dapat memodifikasi kelas bawaan dan menambahkan metode baru.
knownasilya
18
@ Ken: Ya, dan itulah intinya! Mengapa Anda menulis dalam Java dan tidak secara langsung dalam kode byte JVM? Bukankah itu "hanya masalah sintaksis"?
Fyodor Soikin
30
Metode ekstensi dapat membuat kode jauh lebih elegan dibandingkan dengan metode statis tambahan dari beberapa kelas lain. Hampir semua bahasa modern memungkinkan beberapa jenis ekstensi kelas yang ada: C #, php, objective-c, javascript. Jawa pasti menunjukkan umurnya di sini. Bayangkan Anda ingin menulis JSONObject ke disk. Apakah Anda memanggil jsonobj.writeToDisk () atauclass.writeToDisk (jsonobj)?
wol
8
Alasan untuk membenci Jawa terus tumbuh. Dan saya berhenti mencari mereka beberapa tahun yang lalu ......
John Demetriou
54

Metode penyuluhan bukan hanya metode statis dan bukan hanya kenyamanan sintaksis gula, pada kenyataannya mereka adalah alat yang sangat kuat. Hal utama yang ada adalah kemampuan untuk mengganti metode yang berbeda berdasarkan instantiasi parameter generik yang berbeda. Ini mirip dengan kelas tipe Haskell, dan pada kenyataannya, sepertinya mereka berada di C # untuk mendukung Monad C # (yaitu LINQ). Bahkan menjatuhkan sintaks LINQ, saya masih tidak tahu cara untuk mengimplementasikan antarmuka serupa di Jawa.

Dan saya rasa tidak mungkin untuk mengimplementasikannya di Jawa, karena semantik tipe generik dari parameter generik.

pengguna1686250
sumber
Mereka juga memungkinkan Anda mewarisi banyak perilaku (tanpa polimorfisme). Anda dapat mengimplementasikan banyak antarmuka, dan dengan itu muncul metode ekstensi mereka. Mereka juga memungkinkan Anda untuk menerapkan perilaku yang ingin Anda lampirkan ke jenis tanpa secara global terkait dengan jenis sistem.
Neologisme Pintar
25
Seluruh jawaban ini salah. Metode ekstensi dalam C # hanyalah gula sintaksis yang disusun ulang sedikit oleh kompiler untuk memindahkan target pemanggilan metode ke argumen pertama metode statis. Anda tidak dapat mengganti metode yang ada. Tidak ada persyaratan bahwa metode ekstensi menjadi monad. Ini hanya cara yang lebih nyaman untuk memanggil metode statis, yang memberikan tampilan menambahkan metode contoh ke kelas. Silakan baca ini jika Anda setuju dengan jawaban ini
Matt Klein
3
baik, dalam hal ini, mendefinisikan apa itu sintaksis gula, saya akan menyebut sintaksis gula menjadi sintaksis makro internal, untuk metode ekstensi kompiler setidaknya harus mencari kelas statis metode ekstensi terletak untuk menggantikan. Tidak ada dalam jawaban tentang metode harus monad yang tidak masuk akal. Juga, Anda dapat menggunakannya untuk kelebihan beban, tetapi ini bukan fitur metode ekstensi, ini adalah tipe parameter sederhana berdasarkan kelebihan beban, cara yang sama akan berfungsi jika metode ini dipanggil secara langsung, dan itu tidak akan berfungsi dalam banyak kasus menarik di Jawa karena penghapusan jenis argumen generik.
user1686250
1
@ user1686250 Mungkin untuk mengimplementasikan di Java (dengan "Java" Saya berasumsi maksud Anda bytecode yang berjalan pada JVM) ... Kotlin, yang mengkompilasi ke bytecode memiliki ekstensi. Ini hanya gula sintaksis daripada metode statis. Anda dapat menggunakan dekompiler di IntelliJ untuk melihat seperti apa tampilan Java.
Jeffrey Blattman
@ user1686250 Bisakah Anda mengembangkan apa yang Anda tulis tentang obat generik (atau memberikan tautan) karena saya sama sekali tidak mengerti tentang obat generik. Dengan cara apa selain metode statis yang terkait dengan itu?
C.Champagne
10

Secara teknis C # Extension tidak memiliki padanan di Java. Tetapi jika Anda ingin mengimplementasikan fungsi-fungsi tersebut untuk kode yang lebih bersih dan pemeliharaan, Anda harus menggunakan kerangka kerja 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
sumber
7

Bahasa XTend - yang merupakan set super Java, dan mengkompilasi ke kode sumber Java 1  - mendukung ini.

Sam Keays
sumber
Ketika kode yang bukan Java dikompilasi ke Java, apakah Anda memiliki metode ekstensi? Atau kode Java hanyalah metode statis?
Fabio Milheiro
@Bomboca Seperti yang telah dicatat oleh orang lain, Java tidak memiliki metode ekstensi. Jadi kode XTend, dikompilasi ke Java, entah bagaimana tidak membuat metode ekstensi Java. Tetapi jika Anda bekerja di XTend secara eksklusif, Anda tidak akan memperhatikan atau peduli. Tetapi, untuk menjawab pertanyaan Anda, Anda tidak harus memiliki metode statis juga. Penulis utama XTend memiliki entri blog tentang ini di blog.efftinge.de/2011/11/…
Erick G. Hagstrom
Ya, tidak tahu mengapa saya tidak memikirkan itu juga. Terima kasih!
Fabio Milheiro
@ Sam Terima kasih telah memperkenalkan saya ke XTend - Saya belum pernah mendengarnya.
jpaugh
7

Manifold memberi Java metode ekstensi gaya C # dan beberapa fitur lainnya. Tidak seperti alat lain, Manifold tidak memiliki batasan dan tidak menderita masalah dengan generik, lambdas, IDE dll. Manifold menyediakan beberapa fitur lain seperti tipe kustom gaya-F , antarmuka struktural gaya-TypeScript , dan tipe ekspansi gaya Javascript .

Selain itu, IntelliJ menyediakan dukungan komprehensif untuk Manifold melalui plugin Manifold .

Manifold adalah proyek sumber terbuka yang tersedia di github .

Scott
sumber
6

Pilihan lain adalah dengan menggunakan kelas ForwardingXXX dari perpustakaan google-jambu.

Aravind Yarram
sumber
5

Java tidak memiliki fitur seperti itu. Alih-alih, Anda bisa membuat subkelas reguler dari implementasi daftar atau membuat kelas dalam anonim:

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

Masalahnya adalah memanggil metode ini. Anda dapat melakukannya "di tempat":

new ArrayList<String>() {
   public String getData() {
       return ""; // add your implementation here. 
   }
}.getData();
AlexR
sumber
112
Itu sama sekali tidak berguna.
SLaks
2
@ Slaks: Kenapa tepatnya? Ini adalah "tulis kelas Anda sendiri" yang disarankan oleh Anda sendiri.
Goran Jovic
23
@ Goran: Yang dapat Anda lakukan hanyalah menentukan metode, lalu segera panggil, sekali .
SLaks
3
@ Slaks: Baiklah, titik diambil. Dibandingkan dengan solusi terbatas itu, menulis kelas bernama akan lebih baik.
Goran Jovic
Ada perbedaan besar, besar antara metode ekstensi C # dan kelas anonim Java. Dalam C # metode ekstensi adalah gula sintaksis untuk apa yang sebenarnya hanya metode statis. IDE dan kompiler membuat metode ekstensi tampak seolah-olah itu adalah metode instance dari kelas extended. (Catatan: "diperluas" dalam konteks ini tidak berarti "diwariskan" seperti biasanya di Jawa.)
HairOfTheDog
4

Sepertinya ada beberapa kemungkinan kecil bahwa Metode Defender (metode standar yaitu) mungkin membuatnya menjadi Java 8. Namun, sejauh yang saya memahami mereka, mereka hanya memungkinkan penulis dariinterface surut memperpanjang, tidak pengguna sewenang-wenang.

Metode Defender + Injeksi Antarmuka akan dapat sepenuhnya menerapkan metode ekstensi gaya C #, tetapi AFAICS, Antarmuka Antarmuka bahkan belum ada di peta jalan Java 8.

Jörg W Mittag
sumber
3

Agak terlambat ke pesta tentang pertanyaan ini, tetapi kalau-kalau ada yang merasa berguna saya baru saja membuat subkelas:

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
sumber
4
Metode ekstensi biasanya untuk kode yang tidak dapat dimodifikasi atau diwariskan seperti kelas akhir / tertutup dan kekuatan utamanya adalah ekstensi Antarmuka misalnya memperluas IEnumerable <T>. Tentu saja, mereka hanya gula sintaksis untuk metode statis. Tujuannya, agar kodenya jauh lebih mudah dibaca. Kode bersih berarti pemeliharaan yang lebih baik / evolvabilitas.
mbx
1
Bukan hanya itu @mbx. Metode ekstensi juga berguna untuk memperluas fungsionalitas kelas dari kelas yang tidak disegel tetapi Anda tidak dapat memperluasnya karena Anda tidak mengontrol apa pun yang mengembalikan instance, misalnya HttpContextBase yang merupakan kelas abstrak.
Fabio Milheiro
@FabioMilheiro Saya dengan murah hati memasukkan kelas abstrak sebagai "antarmuka" dalam konteks itu. Kelas yang dibuat secara otomatis (xsd.exe) adalah dari jenis yang sama: Anda bisa tetapi tidak harus memperpanjang mereka dengan memodifikasi file yang dihasilkan. Anda biasanya akan memperpanjang mereka dengan menggunakan "parsial" yang mengharuskan mereka untuk berada di majelis yang sama. Jika tidak, metode ekstensi adalah alternatif yang terlihat cantik. Pada akhirnya, mereka hanya metode statis (tidak ada perbedaan jika Anda melihat Kode IL yang dihasilkan).
mbx
Ya ... HttpContextBase adalah abstraksi meskipun saya mengerti kemurahan hati Anda. Memanggil antarmuka sebagai abstraksi mungkin tampak agak lebih alami. Terlepas dari itu, saya tidak bermaksud itu harus menjadi abstraksi. Saya baru saja memberikan contoh kelas yang saya tulis banyak metode ekstensi.
Fabio Milheiro
2

Kita dapat mensimulasikan implementasi metode ekstensi C # di Java dengan menggunakan implementasi metode default yang tersedia sejak Java 8. Kita mulai dengan mendefinisikan antarmuka yang akan memungkinkan kita untuk mengakses objek dukungan melalui metode base (), seperti:

public interface Extension<T> {

    default T base() {
        return null;
    }
}

Kami mengembalikan nol karena antarmuka tidak dapat memiliki keadaan, tetapi ini harus diperbaiki nanti melalui proxy.

Pengembang ekstensi harus memperluas antarmuka ini dengan antarmuka baru yang berisi metode ekstensi. Katakanlah kita ingin menambahkan konsumen forEach pada antarmuka Daftar:

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

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

}

Karena kami memperluas antarmuka Ekstensi, kami dapat memanggil metode base () di dalam metode ekstensi kami untuk mengakses objek dukungan yang kami lampirkan.

Antarmuka Ekstensi harus memiliki metode pabrik yang akan membuat ekstensi objek dukungan yang diberikan:

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

Kami membuat proksi yang mengimplementasikan antarmuka ekstensi dan semua antarmuka yang diterapkan oleh jenis objek dukungan. Penangan doa yang diberikan kepada proxy akan mengirimkan semua panggilan ke objek dukungan, kecuali untuk metode "basis", yang harus mengembalikan objek dukungan, jika tidak, implementasi defaultnya adalah mengembalikan nol:

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

}

Kemudian, kita bisa menggunakan metode Extension.create () untuk melampirkan antarmuka yang berisi metode ekstensi ke objek dukungan. Hasilnya adalah sebuah objek yang dapat dilemparkan ke antarmuka ekstensi dimana kita masih dapat mengakses objek dukungan yang memanggil metode base (). Setelah referensi dilemparkan ke antarmuka ekstensi, kita sekarang dapat memanggil metode ekstensi dengan aman yang dapat memiliki akses ke objek dukungan, sehingga sekarang kita dapat melampirkan metode baru ke objek yang ada, tetapi tidak dengan jenis definisi:

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

}

Jadi, ini adalah cara kita dapat mensimulasikan kemampuan untuk memperluas objek di Jawa dengan menambahkan kontrak baru kepada mereka, yang memungkinkan kita untuk memanggil metode tambahan pada objek yang diberikan.

Di bawah ini Anda dapat menemukan kode antarmuka Ekstensi:

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

}
Iulian Ilie-Némedi
sumber
1
itu adalah neraka!
Dmitry Avtonomov
1

Seseorang dapat menggunakan pola desain berorientasi objek dekorator . Contoh dari pola ini yang digunakan di pustaka standar Java adalah DataOutputStream .

Berikut beberapa kode untuk menambah fungsionalitas Daftar:

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 Saya penggemar berat Kotlin . Ini memiliki metode ekstensi dan juga berjalan di JVM.

Eric
sumber
0

Anda dapat membuat metode ekstensi / pembantu C # like dengan (RE) mengimplementasikan antarmuka Collections dan menambahkan- contoh untuk 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
sumber
-7

Java8 sekarang mendukung metode standar , yang mirip dengan C#metode ekstensi.

DarVar
sumber
9
Salah; contoh dalam pertanyaan ini masih mustahil.
SLaks
@ SLaks apa perbedaan antara ekstensi Java dan C #?
Fabio Milheiro
3
Metode default hanya dapat didefinisikan dalam antarmuka. docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
SLaks
DarVar, Utas komentar ini adalah satu-satunya tempat di mana metode standar disebutkan, yang saya coba ingat dengan putus asa. Terima kasih telah menyebutkannya, jika tidak dengan nama! :-) (Terima kasih @SLaks untuk tautannya)
jpaugh
Saya akan mengangkat jawaban itu, karena hasil akhir dari metode statis di antarmuka akan menyediakan penggunaan yang sama dengan metode ekstensi C #, namun, Anda masih perlu mengimplementasikan antarmuka di kelas Anda tidak seperti C # Anda memasukkan kata kunci (ini) sebagai parameter
zaPlayer