Java เทียบเท่ากับวิธีการขยาย C #


176

ฉันกำลังมองหาการใช้งานฟังก์ชั่นในรายการวัตถุเช่นเดียวกับใน C # โดยใช้วิธีการขยาย

บางสิ่งเช่นนี้

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

ฉันจะทำสิ่งนั้นใน Java ได้อย่างไร


8
เลือกตัวเลือกนี้: github.com/nicholas22/jpropel ตัวอย่าง: สตริงใหม่ [] {"james", "john", "john", "eddie"} .where (startsWith ("j")) ชัดเจน (); ใช้ lombok-pg ซึ่งให้วิธีการที่ดี
NT_

6
Microsoft ทำให้ถูกต้องแน่นอนเมื่อพวกเขาอนุญาตส่วนขยาย การจัดคลาสย่อยเพื่อเพิ่มฟังก์ชันการทำงานใหม่ไม่ทำงานหากฉันต้องการฟังก์ชันในคลาสที่ส่งคืนให้ฉันที่อื่น ชอบเพิ่มวิธีการใน String และวันที่
tggagne

3
ie java.lang.String เป็นคลาสสุดท้ายดังนั้นคุณจึงไม่สามารถขยายได้ การใช้วิธีการคงที่เป็นวิธีหนึ่ง แต่มันแสดงให้เห็นว่ารหัสไม่สามารถอ่านได้บางครั้งฉันคิดว่า C # ซ้ายอายุเป็นคอมพิวเตอร์ lang วิธีการขยายชั้นเรียนบางส่วน LINQ และอื่น ๆ ..
Davut Gürbüz

7
@Roadrunner, rofl! การตอบโต้ที่ดีที่สุดสำหรับคุณลักษณะภาษาที่หายไปคือการกล่าวว่าคุณลักษณะภาษาที่ขาดหายไปนั้นเป็นสิ่งที่ชั่วร้ายและไม่ต้องการ เรื่องนี้เป็นที่รู้จักกัน
Kirk Woll

6
วิธีการขยายไม่ใช่ "ความชั่วร้าย" พวกเขาปรับปรุงการอ่านรหัสอย่างมาก อีกหนึ่งการตัดสินใจที่ไม่ดีในจาวา
csauve

คำตอบ:


196

Java ไม่รองรับวิธีการขยาย

แต่คุณสามารถสร้างวิธีการแบบสแตติกปกติหรือเขียนคลาสของคุณเอง


63
ฉันเสียแล้วหลังจากใช้วิธีการขยาย - แต่วิธีคงที่จะทำเคล็ดลับเช่นกัน
bbqchickenrobot

31
แต่ไวยากรณ์นั้นดีมากและทำให้โปรแกรมเข้าใจง่ายขึ้น :) ฉันชอบที่ Ruby ให้คุณทำสิ่งเดียวกันเกือบทุกอย่างยกเว้นว่าคุณสามารถปรับเปลี่ยนบิวด์ในคลาสและเพิ่มเมธอดใหม่ได้
knownasilya

18
@ เคน: ใช่และนั่นคือจุดรวม! ทำไมคุณถึงเขียนด้วยภาษาจาวาและไม่ใช่รหัส JVM โดยตรง? ไม่ใช่ "แค่เรื่องของไวยากรณ์" ใช่ไหม
Fyodor Soikin

30
วิธีการขยายสามารถทำให้โค้ดดูสง่างามยิ่งขึ้นเมื่อเทียบกับวิธีสแตติกพิเศษจากคลาสอื่น ๆ เกือบทุกภาษาที่ทันสมัยมากขึ้นอนุญาตให้ขยายคลาสที่มีอยู่บางชนิด: C #, php, purpose-c, javascript Java แสดงอายุที่นี่อย่างแน่นอน ลองนึกภาพคุณต้องการเขียน JSONObject ไปยังดิสก์ คุณโทร jsonobj.writeToDisk () หรือ someunrelatedclass.writeToDisk (jsonobj) หรือไม่
woens

8
เหตุผลที่เกลียดชัง Java กำลังเติบโต และฉันหยุดมองหาพวกเขาสองสามปีที่ผ่านมา ......
จอห์น Demetriou

54

วิธีการขยายไม่เพียง แต่เป็นวิธีการคงที่และไม่เพียง แต่ความสะดวกสบายไวยากรณ์น้ำตาลในความเป็นจริงพวกเขาเป็นเครื่องมือที่มีประสิทธิภาพมาก สิ่งสำคัญที่มีความสามารถในการแทนที่วิธีการต่าง ๆ ขึ้นอยู่กับการสร้างอินสแตนซ์ของพารามิเตอร์ต่าง ๆ นี่คล้ายกับคลาสประเภทของ Haskell และที่จริงแล้วดูเหมือนว่าพวกเขาจะอยู่ใน C # เพื่อสนับสนุน Monads ของ C # (เช่น LINQ) แม้การทิ้งไวยากรณ์ของ LINQ ฉันยังไม่รู้วิธีการใช้อินเทอร์เฟซที่คล้ายคลึงกันใน Java

และฉันคิดว่ามันเป็นไปไม่ได้ที่จะนำไปใช้ใน Java เพราะความหมายของการลบประเภทของจาวาของพารามิเตอร์ทั่วไป


นอกจากนี้ยังช่วยให้คุณสามารถสืบทอดพฤติกรรมหลายอย่าง (sans polymorphism) คุณสามารถใช้หลายอินเตอร์เฟสและด้วยวิธีการขยาย นอกจากนี้ยังช่วยให้คุณสามารถใช้พฤติกรรมที่คุณต้องการแนบกับประเภทโดยไม่ต้องเชื่อมโยงกับประเภททั่วโลก
Neologism ที่ชาญฉลาด

25
คำตอบทั้งหมดนี้ผิด วิธีการขยายใน C # เป็นเพียงน้ำตาล syntactic ที่คอมไพเลอร์จัดเรียงใหม่เล็กน้อยเพื่อย้ายเป้าหมายของวิธีการเรียกไปยังอาร์กิวเมนต์แรกของวิธีการคงที่ คุณไม่สามารถแทนที่วิธีการที่มีอยู่ ไม่มีข้อกำหนดว่าวิธีการขยายเป็น monad แท้จริงแล้วมันเป็นวิธีที่สะดวกกว่าในการเรียกใช้เมธอดแบบสแตติกซึ่งให้ลักษณะของการเพิ่มเมธอดอินสแตนซ์ให้กับคลาส โปรดอ่านสิ่งนี้หากคุณเห็นด้วยกับคำตอบนี้
Matt Klein

3
ดีในกรณีนี้กำหนดว่าไวยากรณ์น้ำตาลคืออะไรฉันจะเรียกน้ำตาลไวยากรณ์เป็นไวยากรณ์แมโครภายในสำหรับวิธีการขยายรวบรวมเรียบเรียงอย่างน้อยต้องดูระดับคงที่วิธีการขยายอยู่เพื่อทดแทน ไม่มีคำตอบเกี่ยวกับวิธีการที่ควรจะเป็น monad ซึ่งไม่ใช่ความรู้สึก นอกจากนี้คุณสามารถใช้มันสำหรับการโหลดมากเกินไป แต่มันไม่ได้เป็นคุณสมบัติส่วนขยายวิธีการมันเป็นพารามิเตอร์แบบธรรมดาการบรรทุกเกินพิกัดแบบเดียวกับที่มันจะทำงานถ้าวิธีการที่เรียกว่าโดยตรงและจะไม่ทำงานในกรณีที่น่าสนใจมากมายใน Java เนื่องจากข้อโต้แย้งประเภททั่วไปการลบ
user1686250

1
@ user1686250 เป็นไปได้ที่จะนำไปใช้ใน Java (โดย "Java" ฉันถือว่าคุณหมายถึง bytecode ที่ทำงานบน JVM) ... Kotlin ซึ่งคอมไพล์ไปยัง bytecode มีส่วนขยาย มันเป็นเพียงน้ำตาล syntactic มากกว่าวิธีการคงที่ คุณสามารถใช้ decompiler ใน IntelliJ เพื่อดูว่า Java ที่เทียบเท่ามีหน้าตาเป็นอย่างไร
Jeffrey Blattman

@ user1686250 คุณช่วยพัฒนาสิ่งที่คุณกำลังเขียนเกี่ยวกับ generics (หรือให้ลิงค์) เพราะฉันไม่ได้รับจุดเกี่ยวกับข้อมูลทั่วไป มันจะเกี่ยวข้องกับวิธีอื่นที่ไม่ใช่วิธีสแตติกปกติหรือไม่?
C.Champagne

17

โปรเจ็กต์ลอมบอกมีคำอธิบายประกอบ@ExtensionMethodที่สามารถใช้เพื่อให้บรรลุการทำงานที่คุณต้องการ


3
Manifoldให้ครอบคลุมอย่างราบรื่น C # สไตล์วิธีขยายการสนับสนุนสำหรับJavajava.lang.Stringช่วยให้คุณสามารถเพิ่มวิธีการเรียนที่คุณไม่สามารถควบคุมเช่น การสาธิต: http://manifold.systems/images/ExtensionMethod.mp4
สกอตต์

10

ในทางเทคนิค C # Extension นั้นไม่เทียบเท่าใน 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();
  }
}

7

เอ็กซ์เท็นด์ภาษา - ซึ่งเป็นซุปเปอร์ชุดของ Java และคอมไพล์ชวารหัสที่มา1  - รองรับนี้


เมื่อโค้ดที่ไม่ใช่ Java นั้นคอมไพล์ไปยัง Java คุณมีวิธีการขยายหรือไม่? หรือรหัส Java เป็นเพียงวิธีการคงที่?
Fabio Milheiro

@Bomboca ตามที่คนอื่นสังเกตเห็น Java ไม่มีวิธีการขยาย ดังนั้นรหัส XTend ที่คอมไพล์ไปยัง Java จึงไม่ได้สร้างวิธีการขยาย Java อย่างใด แต่ถ้าคุณทำงานใน XTend โดยเฉพาะคุณจะไม่สังเกตเห็นหรือสนใจ แต่เพื่อตอบคำถามของคุณคุณไม่จำเป็นต้องมีวิธีแบบคงที่เช่นกัน ผู้เขียนหลักของ XTend มีรายการบล็อกเกี่ยวกับเรื่องนี้ที่blog.efftinge.de/2011/11/…
Erick G. Hagstrom

ใช่ไม่รู้ว่าทำไมฉันไม่คิดเช่นกัน ขอบคุณ!
Fabio Milheiro

@ Sam ขอบคุณที่แนะนำฉันให้กับ XTend ฉันไม่เคยได้ยินมาก่อน
jpaugh

7

Manifold นำเสนอ Java ด้วยวิธีการขยาย C # -style และคุณสมบัติอื่น ๆ ซึ่งแตกต่างจากเครื่องมืออื่น ๆ Manifold มีข้อ จำกัด และไม่ต้องทนทุกข์ทรมานจากปัญหาเกี่ยวกับข้อมูลทั่วไป, lambdas, IDE ฯลฯ Manifold มีคุณสมบัติอื่น ๆ เช่น F # สไตล์ประเภทที่กำหนดเอง , typescript สไตล์การเชื่อมต่อโครงสร้างและ Javascript สไตล์ประเภท Expando

นอกจากนี้ IntelliJ ให้การสนับสนุนที่ครอบคลุมสำหรับ Manifold ผ่าน Manifold ปลั๊กอิน

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
นั่นไร้ประโยชน์อย่างเต็มที่
Slaks

2
@ Slaks: ทำไมล่ะ นี่คือ "เขียนชั้นเรียนของคุณเอง" ที่แนะนำด้วยตัวเอง
Goran Jovic

23
@Goran: ทั้งหมดนี้ช่วยให้คุณทำคือการกำหนดวิธีการแล้วโทรทันทีครั้งเดียว
Slaks

3
@Slaks: เอาล่ะเอาล่ะชี้ไป เมื่อเทียบกับโซลูชันที่ จำกัด การเขียนคลาสที่มีชื่อจะดีกว่า
Goran Jovic

มีความแตกต่างอย่างมากระหว่างวิธีการขยาย C # กับคลาสที่ไม่ระบุชื่อ Java ใน C # วิธีการขยายเป็นน้ำตาลเชิงซ้อนสำหรับสิ่งที่เป็นเพียงวิธีคงที่ IDE และคอมไพเลอร์สร้างเมธอดส่วนขยายปรากฏราวกับเป็นวิธีอินสแตนซ์ของคลาสแบบขยาย (หมายเหตุ: "ขยาย" ในบริบทนี้ไม่ได้หมายความว่า "สืบทอด" ตามปกติใน Java)
HairOfTheDog

4

ดูเหมือนว่ามีบางโอกาสเพียงเล็กน้อยที่ Defender วิธี (วิธีการเริ่มต้น IE) อาจจะทำให้มันกลายเป็น Java 8 อย่างไรก็ตามเท่าที่ผมเข้าใจพวกเขาพวกเขาเท่านั้นที่อนุญาตให้ผู้เขียนของinterfaceย้อนหลังขยายมันไม่ผู้ใช้โดยพลการ

Defender Methods + Interface Injection จะสามารถใช้วิธีการขยาย C # -style ได้อย่างเต็มที่ แต่ 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
วิธีการขยายมักสำหรับรหัสที่ไม่สามารถแก้ไขหรือสืบทอดเช่นคลาสสุดท้าย / ปิดผนึกและพลังงานหลักคือส่วนขยายของอินเทอร์เฟซเช่นการขยาย IEnumerable <T> แน่นอนพวกเขาเป็นเพียงน้ำตาล syntactic สำหรับวิธีการคงที่ จุดประสงค์คือรหัสนั้นอ่านได้ง่ายกว่ามาก รหัสทำความสะอาดหมายถึงการบำรุงรักษา / การพัฒนาที่ดีขึ้น
mbx

1
นั่นไม่ใช่แค่มัน @mbx วิธีการขยายยังมีประโยชน์ในการขยายฟังก์ชันการทำงานของคลาสที่ไม่ได้ปิดผนึก แต่คุณไม่สามารถขยายได้เนื่องจากคุณไม่ได้ควบคุมสิ่งที่ส่งคืนอินสแตนซ์เช่น HttpContextBase ซึ่งเป็นคลาสนามธรรม
Fabio Milheiro

@FabioMilheiro ฉันรวมคลาสนามธรรมไว้เป็น "อินเตอร์เฟส" ในบริบทนั้น คลาสที่สร้างโดยอัตโนมัติ (xsd.exe) เป็นประเภทเดียวกัน: คุณทำได้ แต่ไม่ควรขยายโดยการแก้ไขไฟล์ที่สร้างขึ้น โดยปกติแล้วคุณจะขยายพวกเขาโดยใช้ "บางส่วน" ซึ่งต้องการให้พวกเขาอยู่ในการชุมนุมเดียวกัน หากไม่ใช่วิธีการขยายก็เป็นทางเลือกที่ดูดีทีเดียว ในที่สุดพวกเขาเป็นเพียงวิธีคงที่ (ไม่มีความแตกต่างถ้าคุณดูรหัส IL ที่สร้างขึ้น)
mbx

ใช่ ... HttpContextBase เป็นสิ่งที่เป็นนามธรรมแม้ว่าฉันจะเข้าใจความเอื้ออาทรของคุณ การเรียกอินเทอร์เฟซสิ่งที่เป็นนามธรรมอาจดูค่อนข้างเป็นธรรมชาติมากกว่า ไม่ว่าจะเป็นเรื่องอะไรฉันก็ไม่ได้หมายความว่ามันจะต้องเป็นนามธรรม ฉันเพิ่งยกตัวอย่างของชั้นเรียนที่ฉันเขียนวิธีการขยายจำนวนมาก
Fabio Milheiro

2

เราสามารถจำลองการใช้งานวิธีการขยาย C # ใน Java โดยใช้วิธีการเริ่มต้นที่มีอยู่ตั้งแต่ Java 8 เราเริ่มต้นด้วยการกำหนดอินเตอร์เฟสที่จะช่วยให้เราสามารถเข้าถึงวัตถุสนับสนุนผ่านวิธีการ base () ดังนี้:

public interface Extension<T> {

    default T base() {
        return null;
    }
}

เราคืนค่า null เนื่องจากอินเตอร์เฟสไม่สามารถระบุสถานะได้ แต่จะต้องแก้ไขในภายหลังผ่านทางพร็อกซี

ผู้พัฒนาส่วนขยายจะต้องขยายส่วนต่อประสานนี้โดยส่วนต่อประสานใหม่ที่มีวิธีการส่วนขยาย สมมติว่าเราต้องการเพิ่มผู้บริโภคแต่ละรายในส่วนติดต่อรายการ:

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

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

}

เนื่องจากเราขยายส่วนต่อขยายส่วนขยายเราสามารถเรียกใช้เมธอด 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;
        }
    }
}

เราสร้างพร็อกซีที่ใช้ส่วนขยายส่วนต่อประสานและส่วนต่อประสานทั้งหมดที่ใช้งานโดยประเภทของวัตถุสนับสนุน ตัวจัดการการเรียกใช้ที่กำหนดให้กับพร็อกซีจะส่งการเรียกไปยังวัตถุสนับสนุนทั้งหมดยกเว้นวิธี "ฐาน" ซึ่งจะต้องส่งคืนวัตถุสนับสนุนมิฉะนั้นการใช้งานเริ่มต้นจะส่งคืน 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

หนึ่งอาจจะใช้มัณฑนากรรูปแบบการออกแบบเชิงวัตถุ ตัวอย่างของรูปแบบนี้ที่ใช้ในไลบรารีมาตรฐานของ Java คือDataOutputStream 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 / like extension / helper โดย (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;
}

-7

Java8 ตอนนี้รองรับวิธีการเริ่มต้นซึ่งคล้ายกับC#วิธีการขยายของ


9
ไม่ถูกต้อง; ตัวอย่างในคำถามนี้ยังคงเป็นไปไม่ได้
Slaks

@SLaks อะไรคือความแตกต่างระหว่าง Java และส่วนขยาย C #?
Fabio Milheiro

3
วิธีการเริ่มต้นสามารถกำหนดได้เฉพาะในส่วนต่อประสาน docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
SLaks

DarVar, กระทู้ความคิดเห็นนี้เป็นสถานที่เดียวที่มีการกล่าวถึงวิธีการเริ่มต้นซึ่งฉันพยายามอย่างยิ่งที่จะจำ ขอบคุณที่พูดถึงพวกเขาหากไม่ใช่โดยชื่อ! :-) (ขอบคุณ @SLaks สำหรับลิงก์)
jpaugh

ฉันจะ upvote คำตอบนั้นก่อให้เกิดผลลัพธ์สุดท้ายจากวิธีการคงที่ในส่วนต่อประสานจะให้ใช้เช่นเดียวกับวิธีการขยาย C # อย่างไรก็ตามคุณยังต้องใช้ส่วนต่อประสานในชั้นเรียนของคุณซึ่งแตกต่างจาก C # คุณผ่านคำหลัก (นี้) เป็นพารามิเตอร์
zaPlayer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.