แลมบ์ดาเป็นอนุกรมได้อย่างไร


157

ฉันจะทำให้แลมบ์ดาเป็นอนุกรมได้อย่างไร

NotSerializableExceptionยกตัวอย่างเช่นโค้ดด้านล่างพ่น ฉันจะแก้ไขได้อย่างไรโดยไม่สร้างSerializableRunnableอินเทอร์เฟซ "จำลอง"

public static void main(String[] args) throws Exception {
    File file = Files.createTempFile("lambda", "ser").toFile();
    try (ObjectOutput oo = new ObjectOutputStream(new FileOutputStream(file))) {
        Runnable r = () -> System.out.println("Can I be serialized?");
        oo.writeObject(r);
    }

    try (ObjectInput oi = new ObjectInputStream(new FileInputStream(file))) {
        Runnable  r = (Runnable) oi.readObject();
        r.run();
    }
}

18
ในขณะที่สิ่งนี้เป็นไปได้ (ดูคำตอบที่เลือก) ทุกคนน่าจะคิดอย่างรอบคอบเกี่ยวกับการทำเช่นนี้ มันเป็นอย่างเป็นทางการ"หมดแรง"และสามารถมีร้ายแรง รักษาความปลอดภัย ผลกระทบ
David

คำตอบ:


260

Java 8 เปิดตัวไปได้ที่จะโยนวัตถุที่จะแยกประเภทโดยการเพิ่มขอบเขตหลาย ในกรณีของการทำให้เป็นอนุกรมจึงเป็นไปได้ที่จะเขียน:

Runnable r = (Runnable & Serializable)() -> System.out.println("Serializable!");

แลมบ์ดาจะกลายเป็นอนุกรมโดยอัตโนมัติ


4
น่าสนใจมาก - คุณสมบัตินี้ดูเหมือนจะทรงพลังมาก มีการใช้งานของการแสดงออกที่หล่อดังกล่าวนอกเหนือจากการแกะ lambdas หรือไม่? เช่นตอนนี้มันเป็นไปได้ไหมที่จะทำสิ่งที่คล้ายกับคลาสนิรนามทั่วไป?
Balder

6
@Balder เพิ่มสิ่งอำนวยความสะดวกในการส่งไปยังประเภทการแยกเพื่อให้ประเภทเป้าหมายสำหรับการอนุมานประเภทของ lambdas เนื่องจาก AICs มีประเภทรายการ (เช่นประเภทของมันไม่ได้อนุมาน) การคัดเลือก AIC ให้เป็นประเภทจุดตัดจึงไม่มีประโยชน์ (เป็นไปได้ แต่ก็ไม่มีประโยชน์) หากต้องการให้ AIC ใช้งานหลายอินเตอร์เฟสคุณจะต้องสร้างส่วนย่อยใหม่ที่ขยายทั้งหมดของพวกเขาแล้วสร้างอินสแตนซ์นั้น
Stuart Marks

2
สิ่งนี้จะสร้างคำเตือนคอมไพเลอร์ว่าไม่มีการกำหนด serialVersionUID?
คิริลล์ Rakhman

11
หมายเหตุ: ใช้งานได้เฉพาะในกรณีที่คุณใช้นักแสดงระหว่างการก่อสร้าง ต่อไปนี้จะโยน ClassCastException: Runnable r = () -> System.out.println ("Serializable!"); runnable serializableR = (Runnable & Serializable) r;
bcody

3
@bcody ใช่ดูเพิ่มเติมได้ที่: stackoverflow.com/questions/25391656/…
assylias

24

การก่อสร้างเดียวกันสามารถใช้สำหรับการอ้างอิงวิธีการ ตัวอย่างเช่นรหัสนี้:

import java.io.Serializable;

public class Test {
    static Object bar(String s) {
        return "make serializable";
    }

    void m () {
        SAM s1 = (SAM & Serializable) Test::bar;
        SAM s2 = (SAM & Serializable) t -> "make serializable";
    }

    interface SAM {
        Object action(String s);
    }
}

กำหนดนิพจน์แลมบ์ดาและการอ้างอิงวิธีการด้วยประเภทเป้าหมายที่ปรับแต่งได้


18

หล่อน่าเกลียดมาก ฉันชอบที่จะกำหนดนามสกุลของ Serializable ให้กับส่วนต่อประสานการทำงานที่ฉันใช้

ตัวอย่างเช่น:

interface SerializableFunction<T,R> extends Function<T,R>, Serializable {}
interface SerializableConsumer<T> extends Consumer<T>, Serializable {}

ดังนั้นวิธีการยอมรับแลมบ์ดานั้นสามารถนิยามได้เช่น:

private void someFunction(SerializableFunction<String, Object> function) {
   ...
}

และเรียกฟังก์ชั่นที่คุณสามารถผ่านแลมบ์ดาของคุณโดยไม่ต้องมีนักแสดงที่น่าเกลียด:

someFunction(arg -> doXYZ(arg));

2
ฉันชอบคำตอบนี้เพราะผู้โทรภายนอกที่คุณไม่ได้เขียนจะถูกจัดลำดับโดยอัตโนมัติ หากคุณต้องการให้วัตถุที่ส่งเป็นแบบอนุกรมอินเตอร์เฟซของคุณควรเป็นแบบอนุกรมซึ่งเป็นประเภทของจุดเชื่อมต่อ แต่คำถามก็พูดว่า "โดยไม่ต้องสร้างSerializableRunnable'ดัมมี่' อินเตอร์เฟซ"
Slevin

4

ในกรณีที่มีคนมาที่นี่ขณะสร้างรหัส Beam / Dataflow:

Beam มีSerializableFunction Interface ของตัวเองดังนั้นจึงไม่จำเป็นต้องมีส่วนต่อประสานแบบจำลองหรือแบบปลดล็อกอย่างละเอียด


3

หากคุณมีความเต็มใจที่จะเปลี่ยนไปเป็นอันดับกรอบอีกเช่นKryoSerializableคุณจะได้รับการกำจัดของขอบเขตหลายหรือความต้องการที่อินเตอร์เฟซที่นำมาใช้จะต้องดำเนินการ วิธีการคือ

  1. ปรับเปลี่ยน the InnerClassLambdaMetafactoryเพื่อสร้างรหัสที่จำเป็นสำหรับการทำให้เป็นอนุกรมเสมอ
  2. เรียกใช้โดยตรงในLambdaMetaFactoryระหว่างการดีซีเรียลไลเซชัน

ดูรายละเอียดและรหัสได้ที่บล็อกโพสต์นี้

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