สร้างตัวอย่างประเภททั่วไปใน Java?


576

เป็นไปได้ไหมที่จะสร้างอินสแตนซ์ประเภททั่วไปใน Java ฉันกำลังคิดตามสิ่งที่ฉันเห็นว่าคำตอบคือno( เนื่องจากการลบประเภท ) แต่ฉันจะสนใจถ้าใครสามารถเห็นสิ่งที่ฉันหายไป:

class SomeContainer<E>
{
    E createContents()
    {
        return what???
    }
}

แก้ไข: ปรากฎว่าสามารถใช้โทเค็นชนิด Superเพื่อแก้ไขปัญหาของฉัน แต่ต้องใช้รหัสที่ใช้การสะท้อนจำนวนมากเนื่องจากคำตอบบางส่วนด้านล่างระบุไว้

ฉันจะออกจากที่นี่ซักครู่เพื่อดูว่ามีใครมาพร้อมกับสิ่งที่แตกต่างจากบทความ Artimaของ Ian Robertson หรือไม่


2
เพิ่งทดสอบประสิทธิภาพบนอุปกรณ์ Android 10000 การดำเนินการและ: 8-9 ms ใช้เวลา SomeClass ใหม่ (), 9-11 ms ใช้ Factory <SomeClass> .createInstance () และ 64-71 ms ใช้การสะท้อนที่สั้นที่สุด: SomeClass z = SomeClass.class.newInstance () และการทดสอบทั้งหมดอยู่ในช่วงลองจับเดียว Reflection newInstance () ส่งข้อยกเว้นต่างกัน 4 ข้อจำได้ไหม ดังนั้นฉันจึงตัดสินใจใช้รูปแบบจากโรงงาน
Deepscorn

ดูเพิ่มเติมได้ที่: stackoverflow.com/a/5684761/59087
Dave Jarvis

4
ด้วย Java 8 ตอนนี้คุณสามารถผ่านการอ้างอิงคอนสตรัคเตอร์หรือแลมบ์ดาซึ่งทำให้ปัญหานี้ค่อนข้างยุ่งยากในการแก้ไข ดูคำตอบของฉันด้านล่างสำหรับรายละเอียด
Daniel Pryden

ฉันคิดว่านี่เป็นความคิดที่ไม่ดีที่จะเขียนโค้ดดังกล่าวซึ่งเป็นวิธีที่ฉลาดกว่าและสามารถอ่านได้ในการแก้ปัญหาภายใต้
Krzysztof Cichocki

1
@DavidCitron "สักครู่"เขาพูด ... มันเป็นเวลาสิบเอ็ดปีแล้ว ...
MC Emperor

คำตอบ:


332

คุณถูก. new E()คุณไม่สามารถทำ แต่คุณสามารถเปลี่ยนเป็น

private static class SomeContainer<E> {
    E createContents(Class<E> clazz) {
        return clazz.newInstance();
    }
}

มันเป็นความเจ็บปวด แต่มันได้ผล การห่อด้วยแพทเทิร์นจากโรงงานทำให้สามารถทนได้มากกว่าเดิม


11
ใช่ฉันเห็นวิธีแก้ปัญหานั้นแล้ว แต่ใช้ได้เฉพาะเมื่อคุณมีการอ้างอิงไปยังวัตถุคลาสที่เป็นประเภทที่คุณต้องการสร้างอินสแตนซ์แล้ว
David Citron

11
ใช่ฉันรู้. มันจะดีถ้าคุณสามารถทำ E.class แต่เพียงช่วยให้คุณ Object.class เพราะลบออก :)
จัสตินรัดด์

6
นั่นเป็นแนวทางที่ถูกต้องสำหรับปัญหานี้ มันไม่ใช่สิ่งที่คุณต้องการ แต่มันคือสิ่งที่คุณได้รับ
Joachim Sauer

38
และคุณจะเรียกเมธอด createContents () ได้อย่างไร?
Alexis Dufrenoy

8
นี่ไม่ใช่วิธีเดียวในการทำเช่นนี้อีกต่อไปมีวิธีที่ดีกว่าในตอนนี้ที่ไม่จำเป็นต้องผ่านการClass<?>อ้างอิงโดยใช้ Guava และ TypeToken ดูคำตอบสำหรับรหัสและลิงก์!

129

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

public abstract class Foo<E> {

  public E instance;  

  public Foo() throws Exception {
    instance = ((Class)((ParameterizedType)this.getClass().
       getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
    ...
  }

}

ดังนั้นเมื่อคุณ subclass Foo คุณจะได้รับตัวอย่างของ Bar เช่น

// notice that this in anonymous subclass of Foo
assert( new Foo<Bar>() {}.instance instanceof Bar );

แต่มันใช้งานได้มากมายและใช้ได้กับคลาสย่อยเท่านั้น แม้ว่าจะมีประโยชน์


2
ใช่นี้เป็นสิ่งที่ดีโดยเฉพาะอย่างยิ่งถ้าระดับทั่วไปเป็นนามธรรมที่คุณสามารถทำเช่นนี้ใน subclasses คอนกรีต :)
ปิแอร์เฮนรี่

วิธีนี้ใช้ได้เช่นกันหากคลาสFooไม่ได้เป็นนามธรรม แต่ทำไมมันทำงานกับ subclasses ที่ไม่ระบุชื่อของ Foo เท่านั้น? สมมติว่าเราสร้างFooเป็นรูปธรรม (เราออกไปabstract) ทำไมจะnew Foo<Bar>();ทำให้เกิดข้อผิดพลาดในขณะที่new Foo<Bar>(){};ไม่ได้? (ข้อยกเว้น: "คลาสไม่สามารถส่งไปยัง ParameterizedType")
Tim Kuipers

2
@TimKuipers <E>in class Foo<E>ไม่ได้ผูกกับประเภทใด ๆ คุณจะเห็นพฤติกรรมที่โดดเด่นเมื่อใดก็ตามที่Eไม่ได้เป็นแบบคงที่ผูกไว้เช่นเดียวกับใน: new Foo<Bar>(), หรือnew Foo<T>() {...} class Fizz <E> extends Foo<E>กรณีแรกไม่มีการผูกแบบคงที่จะถูกลบในเวลารวบรวม กรณีที่สองทดแทนตัวแปรชนิดอื่น (T) แทนที่Eแต่ยังไม่ถูกผูกไว้ และในกรณีสุดท้ายควรชัดเจนว่าEยังไม่หลุด
William Price

4
ตัวอย่างของการผูกพันแบบคงที่พารามิเตอร์ชนิดจะเป็นclass Fizz extends Foo<Bar>- ในกรณีนี้ผู้ใช้ของFizzบางสิ่งบางอย่างได้รับที่เป็นFoo<Bar>และไม่สามารถเป็นอะไรก็ได้ Foo<Bar>แต่ ดังนั้นในกรณีนี้คอมไพเลอร์ยินดีที่จะเข้ารหัสข้อมูลนั้นลงในเมทาดาทาของชั้นเรียนFizzและทำให้พร้อมใช้งานเป็นParameterizedTypeรหัสการสะท้อนกลับ เมื่อคุณสร้างคลาสภายในที่ไม่ระบุชื่อเหมือนnew Foo<Bar>() {...}มันกำลังทำสิ่งเดียวกันยกเว้นFizzคอมไพเลอร์จะสร้างชื่อคลาสที่ "ไม่ระบุชื่อ" ที่คุณไม่รู้จนกว่าจะมีการรวบรวมคลาสภายนอก
William Price

4
ควรสังเกตว่าสิ่งนี้จะไม่ทำงานหากอาร์กิวเมนต์ประเภทนั้นเป็นแบบ ParameterizedType ด้วย ตัวอย่างเช่นFoo<Bar<Baz>>. คุณจะสร้างตัวอย่างParameterizedTypeImplที่ไม่สามารถสร้างได้อย่างชัดเจน ดังนั้นจึงเป็นความคิดที่ดีที่จะตรวจสอบว่าเป็นกลับgetActualTypeArguments()[0] ParameterizedTypeถ้าเป็นเช่นนั้นคุณต้องการรับชนิด raw และสร้างอินสแตนซ์ของประเภทนั้นแทน
บดขยี้

106

ใน Java 8 คุณสามารถใช้Supplierอินเทอร์เฟซที่ใช้งานได้เพื่อให้สิ่งนี้ทำได้อย่างง่ายดาย:

class SomeContainer<E> {
  private Supplier<E> supplier;

  SomeContainer(Supplier<E> supplier) {
    this.supplier = supplier;
  }

  E createContents() {
    return supplier.get();
  }
}

คุณจะสร้างคลาสนี้เช่นนี้:

SomeContainer<String> stringContainer = new SomeContainer<>(String::new);

ไวยากรณ์String::newในบรรทัดนั้นเป็นการอ้างอิงคอนสตรัคเตอร์

หากนวกรรมิกของคุณรับอาร์กิวเมนต์คุณสามารถใช้นิพจน์แลมบ์ดาแทน:

SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));

6
สิ่งที่ดี. มันหลีกเลี่ยงการสะท้อนและต้องปฏิบัติต่อข้อยกเว้น
Bruno Leite

2
ดีมาก. น่าเสียดายสำหรับผู้ใช้ Android ต้องใช้ API ระดับ 24 หรือสูงกว่า
Michael Updike

1
ตามความยุ่งยากน้อยลงและวิธีการใช้งานสิ่งนี้จะเป็นที่ยอมรับในความเห็นของฉัน
Tomas

2
... และก็ไม่ได้แตกต่างกันเพื่อตอบนี้แม้เก่าแสดงให้เห็นว่ารูปแบบทางเทคนิคที่อยู่เบื้องหลังมันคือแม้อายุมากกว่าการสนับสนุน Java สำหรับการแสดงออกแลมบ์ดาและการอ้างอิงวิธีการในขณะที่คุณยังสามารถใช้รหัสเก่ากับพวกเขาเมื่อคุณอัพเกรดคอมไพเลอร์ของคุณ ...
โฮล

มันจะเป็นไปได้ที่จะใส่เพียงแค่SomeContainer stringContainer = new SomeContainer(String::new);?
Aaron Franke

87

คุณจะต้องมีโรงงานนามธรรมบางประเภทหรือประเภทอื่นเพื่อส่งผ่านบั๊กไปที่:

interface Factory<E> {
    E create();
}

class SomeContainer<E> {
    private final Factory<E> factory;
    SomeContainer(Factory<E> factory) {
        this.factory = factory;
    }
    E createContents() {
        return factory.create();
    }
}

.. และวิธีที่ Factory.create () มีลักษณะอย่างไร
OhadR

6
@OhadR Factory<>เป็นอินเทอร์เฟซจึงไม่มีเนื้อหา ประเด็นคือคุณต้องมีเลเยอร์ของการเปลี่ยนทิศทางเพื่อส่งผ่านบัคไปยังเมธอดที่ "รู้" รหัสที่ต้องการเพื่อสร้างอินสแตนซ์ มันเป็นการดีกว่าที่จะทำสิ่งนี้ด้วยรหัสปกติแทนที่จะใช้ภาษาศาสตร์เชิงโลหะClassหรือConstructorเมื่อการไตร่ตรองนำมาซึ่งความเจ็บปวดทั้งโลก
Tom Hawtin - tackline

2
ตอนนี้วันที่คุณสามารถสร้างอินสแตนซ์จากโรงงานพร้อมกับนิพจน์การอ้างอิงเมธอดดังนี้:SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);
Lii

24
package org.foo.com;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * Basically the same answer as noah's.
 */
public class Home<E>
{

    @SuppressWarnings ("unchecked")
    public Class<E> getTypeParameterClass()
    {
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }

    private static class StringHome extends Home<String>
    {
    }

    private static class StringBuilderHome extends Home<StringBuilder>
    {
    }

    private static class StringBufferHome extends Home<StringBuffer>
    {
    }   

    /**
     * This prints "String", "StringBuilder" and "StringBuffer"
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException
    {
        Object object0 = new StringHome().getTypeParameterClass().newInstance();
        Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
        Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
        System.out.println(object0.getClass().getSimpleName());
        System.out.println(object1.getClass().getSimpleName());
        System.out.println(object2.getClass().getSimpleName());
    }

}

3
วิธีการที่ดีโดยรหัสนี้อาจทำให้ ClassCastException ถ้าคุณใช้ในประเภททั่วไป จากนั้นคุณดึงอาร์กิวเมนต์ realType กลับมาคุณควรตรวจสอบว่ามันเป็น ParamterizedType และถ้าเช่นนั้นให้ส่งคืน RawType ของเขา (หรือสิ่งที่ดีกว่านี้) อีกปัญหาหนึ่งคือเมื่อเราขยายเพิ่มเติมแล้วเมื่อรหัสนี้จะโยน ClassCastExeption
Damian Leszczyński - Vash

3
เกิดจาก: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl ไม่สามารถส่งไปยัง java.lang.Class
juan

@ DamianLeszczyński-Vash จะล้มเหลวด้วยเช่นclass GenericHome<T> extends Home<T>{}
Holger

22

หากคุณต้องการอินสแตนซ์ใหม่ของอาร์กิวเมนต์ชนิดภายในคลาสทั่วไปให้ตัวสร้างของคุณต้องการคลาสของมัน ...

public final class Foo<T> {

    private Class<T> typeArgumentClass;

    public Foo(Class<T> typeArgumentClass) {

        this.typeArgumentClass = typeArgumentClass;
    }

    public void doSomethingThatRequiresNewT() throws Exception {

        T myNewT = typeArgumentClass.newInstance();
        ...
    }
}

การใช้งาน:

Foo<Bar> barFoo = new Foo<Bar>(Bar.class);
Foo<Etc> etcFoo = new Foo<Etc>(Etc.class);

ข้อดี:

  • ง่ายกว่า (และเป็นปัญหาน้อยกว่า) กว่าวิธี Super Type Token (STT) ของ Robertson
  • มีประสิทธิภาพมากกว่าวิธี STT (ซึ่งจะกินมือถือของคุณเป็นอาหารเช้า)

จุดด้อย:

  • ไม่สามารถส่ง Class ไปยัง Constructor เริ่มต้น (ซึ่งเป็นสาเหตุที่ Foo ถือเป็นที่สิ้นสุด) หากคุณต้องการคอนสตรัคเตอร์เริ่มต้นคุณสามารถเพิ่มเมธอด setter ได้ตลอดเวลา แต่คุณต้องจำไว้ว่าให้โทรหาเธอในภายหลัง
  • การคัดค้านของโรเบิร์ตสัน ... มากกว่าบาร์แกะดำ (แม้ว่าการระบุคลาสอาร์กิวเมนต์อีกครั้งจะไม่ฆ่าคุณ) และตรงกันข้ามกับการอ้างสิทธิ์ของโรเบิร์ตสันส์นี้ไม่ได้เป็นการละเมิดครูใหญ่ต่อไปเพราะผู้แปลจะรับรองความถูกต้องของประเภท
  • ไม่ได้Foo<L>พิสูจน์อย่างสมบูรณ์ สำหรับ starters ... newInstance()จะโยน wobbler ถ้า class อาร์กิวเมนต์ type ไม่มี constructor เริ่มต้น วิธีนี้ใช้ได้กับโซลูชันที่ทราบกันดีอยู่แล้ว
  • ไม่มีการห่อหุ้มทั้งหมดของวิธี STT ไม่ใช่เรื่องใหญ่ (พิจารณาค่าใช้จ่ายประสิทธิภาพที่ยอดเยี่ยมของ STT)

22

คุณสามารถทำได้ในตอนนี้และไม่จำเป็นต้องมีรหัสการสะท้อน

import com.google.common.reflect.TypeToken;

public class Q26289147
{
    public static void main(final String[] args) throws IllegalAccessException, InstantiationException
    {
        final StrawManParameterizedClass<String> smpc = new StrawManParameterizedClass<String>() {};
        final String string = (String) smpc.type.getRawType().newInstance();
        System.out.format("string = \"%s\"",string);
    }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
}

แน่นอนถ้าคุณต้องการเรียกนวกรรมิกที่ต้องการการสะท้อนบางอย่าง แต่นั่นเป็นเอกสารที่ดีมาก ๆ เคล็ดลับนี้ไม่ได้!

นี่คือJavaDoc สำหรับ TypeToken


6
วิธีนี้ใช้ได้กับกรณีจำนวน จำกัด เช่นเดียวกับคำตอบของ @ noah พร้อมการสะท้อนกลับ ฉันลองพวกเขาทั้งหมดในวันนี้ ... และฉันสิ้นสุดที่ผ่านอินสแตนซ์ของคลาสพารามิเตอร์ไปยังคลาสที่กำหนดพารามิเตอร์ (เพื่อให้สามารถเรียก. newInstance () ได้ การขาด "generics" ที่ยิ่งใหญ่มาก ... ใหม่ Foo <Bar> (Bar.class); ... class Foo <T> {คลาสสุดท้ายส่วนตัว <T> mTFactory; Foo (คลาส <T> tClass) {mTFactory = tClass; ... } T instance = tFactory.newInstance (); }
yvolk

สามารถใช้งานได้ในทุกกรณีแม้วิธีการในโรงงานแบบคงที่ที่ใช้พารามิเตอร์ทั่วไป

13

คิดเกี่ยวกับวิธีการทำงานที่มากขึ้น: แทนที่จะสร้าง E บางอย่างจากสิ่งใด (ซึ่งชัดเจนว่าเป็นกลิ่นรหัส) ให้ส่งผ่านฟังก์ชั่นที่รู้วิธีสร้างเช่น

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}

6
ในทางเทคนิคคุณไม่ได้ผ่านฟังก์ชั่นคุณกำลังผ่านวัตถุฟังก์ชั่น (หรือที่เรียกว่าfunctor )
Lorne Laliberte

3
หรืออาจจะเอาชนะการจับExceptionใช้Supplier<E>แทน
Martin D

10

จากบทช่วยสอน Java - ข้อ จำกัด เกี่ยวกับ Generics :

ไม่สามารถสร้างอินสแตนซ์ของพารามิเตอร์ประเภท

คุณไม่สามารถสร้างอินสแตนซ์ของพารามิเตอร์ชนิดได้ ตัวอย่างเช่นรหัสต่อไปนี้ทำให้เกิดข้อผิดพลาดในการรวบรวมเวลา:

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

คุณสามารถสร้างวัตถุของพารามิเตอร์ชนิดผ่านการสะท้อนกลับได้:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

คุณสามารถเรียกใช้เมธอดผนวกได้ดังนี้:

List<String> ls = new ArrayList<>();
append(ls, String.class);

6

นี่เป็นตัวเลือกที่ฉันคิดขึ้นมามันอาจช่วยได้:

public static class Container<E> {
    private Class<E> clazz;

    public Container(Class<E> clazz) {
        this.clazz = clazz;
    }

    public E createContents() throws Exception {
        return clazz.newInstance();
    }
}

แก้ไข: อีกวิธีหนึ่งคุณสามารถใช้ตัวสร้างนี้ (แต่มันต้องมีตัวอย่างของ E):

@SuppressWarnings("unchecked")
public Container(E instance) {
    this.clazz = (Class<E>) instance.getClass();
}

ใช่มันใช้งานได้เหมือนกันแม้ไม่มี generics - กับ generics การสร้างอินสแตนซ์ของคอนเทนเนอร์นี้จะซ้ำซ้อนเล็กน้อย (คุณต้องระบุว่า "E" สองครั้ง)
David Citron

ดีว่าเป็นสิ่งที่เกิดขึ้นเมื่อคุณใช้ Java และยาชื่อสามัญ ... พวกเขาจะไม่สวยและมีข้อ จำกัด อย่างรุนแรง ...
ไมค์หิน

6

หากคุณไม่ต้องการพิมพ์ชื่อคลาสสองครั้งระหว่างการสร้างอินสแตนซ์ใน:

new SomeContainer<SomeType>(SomeType.class);

คุณสามารถใช้วิธีการจากโรงงาน:

<E> SomeContainer<E> createContainer(Class<E> class); 

ชอบใน:

public class Container<E> {

    public static <E> Container<E> create(Class<E> c) {
        return new Container<E>(c);
    }

    Class<E> c;

    public Container(Class<E> c) {
        super();
        this.c = c;
    }

    public E createInstance()
            throws InstantiationException,
            IllegalAccessException {
        return c.newInstance();
    }

}

6

Java น่าเสียดายที่ไม่อนุญาตให้ทำในสิ่งที่คุณต้องการ ดูวิธีแก้ปัญหาอย่างเป็นทางการ :

คุณไม่สามารถสร้างอินสแตนซ์ของพารามิเตอร์ชนิดได้ ตัวอย่างเช่นรหัสต่อไปนี้ทำให้เกิดข้อผิดพลาดในการรวบรวมเวลา:

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

คุณสามารถสร้างวัตถุของพารามิเตอร์ชนิดผ่านการสะท้อนกลับได้:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

คุณสามารถเรียกใช้เมธอดผนวกได้ดังนี้:

List<String> ls = new ArrayList<>();
append(ls, String.class);

คุณช่วยบอกฉันหน่อยได้ไหมว่าทำไมคุณถึง downvoting เมื่อคุณทำเช่นนั้น? ฉันไม่เห็นว่าทำไมการแก้ปัญหาอย่างเป็นทางการเป็นวิธีแก้ปัญหาที่ไม่ดี ขอบคุณ
Neepsnikeep

ฉันเดาว่าคุณจะลงคะแนนเพราะคำตอบของคุณเหมือนกับของ Justin Rudd: stackoverflow.com/a/75254/103412
Torsten

5

เมื่อคุณทำงานกับ E ในเวลาคอมไพล์คุณไม่สนใจประเภททั่วไปที่แท้จริง "E" (ไม่ว่าคุณจะใช้การสะท้อนหรือทำงานกับคลาสพื้นฐานของประเภททั่วไป) ดังนั้นให้ subclass แสดงตัวอย่างของ E

Abstract class SomeContainer<E>
{

    abstract protected  E createContents();
    public doWork(){
        E obj = createContents();
        // Do the work with E 

     }
}


BlackContainer extends SomeContainer<Black>{
    Black createContents() {
        return new  Black();
    }
}

4

คุณสามารถใช้ได้:

Class.forName(String).getConstructor(arguments types).newInstance(arguments)

แต่คุณต้องระบุชื่อคลาสที่แน่นอนรวมถึงแพ็คเกจต่างๆเช่น java.io.FileInputStream. ฉันใช้สิ่งนี้เพื่อสร้างตัวแยกวิเคราะห์นิพจน์ทางคณิตศาสตร์


15
และคุณจะได้ชื่อชั้นที่แน่นอนของประเภททั่วไปที่ runtime ได้อย่างไร
David Citron

คุณต้องบันทึกโดยใช้ตัวอย่างของคลาสนั้น ทำได้แม้ว่าจะไม่ค่อยสะดวก หากทั่วไปของคุณมีสมาชิกของประเภท E (หรือ T หรืออะไรก็ตาม) foo.getClass().getName()ได้รับชื่อของมันไบนารีเป็นเพียง อินสแตนซ์นั้นมาจากไหน ขณะนี้ฉันกำลังส่งต่อเป็นผู้สร้างในโครงการที่ฉันกำลังทำอยู่
Mark Storer

3

หวังว่ามันจะไม่สายเกินไปที่จะช่วย !!!

Java เป็นประเภทที่ปลอดภัยมีเพียง Object เท่านั้นที่สามารถสร้างอินสแตนซ์ได้

ในกรณีของฉันฉันไม่สามารถส่งผ่านพารามิเตอร์ไปยังcreateContentsวิธีการ โซลูชันของฉันใช้ส่วนขยายแทนคำตอบที่ร้องทั้งหมด

private static class SomeContainer<E extends Object> {
    E e;
    E createContents() throws Exception{
        return (E) e.getClass().getDeclaredConstructor().newInstance();
    }
}

นี่คือกรณีตัวอย่างของฉันที่ฉันไม่สามารถผ่านพารามิเตอร์ได้

public class SomeContainer<E extends Object> {
    E object;

    void resetObject throws Exception{
        object = (E) object.getClass().getDeclaredConstructor().newInstance();
    }
}

การใช้การสะท้อนการสร้างข้อผิดพลาดรันไทม์ถ้าคุณขยายคลาสทั่วไปของคุณโดยไม่มีชนิดวัตถุ หากต้องการขยายประเภททั่วไปของคุณเป็นวัตถุให้แปลงข้อผิดพลาดนี้เป็นข้อผิดพลาดในการรวบรวมเวลา


3

ใช้TypeToken<T>คลาส:

public class MyClass<T> {
    public T doSomething() {
        return (T) new TypeToken<T>(){}.getRawType().newInstance();
    }
}

หากคุณใช้ Guava แทน GSON มันแตกต่างกันเล็กน้อย:(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
Jacob van Lingen

2

ฉันคิดว่าฉันสามารถทำได้ แต่ค่อนข้างผิดหวัง: มันใช้งานไม่ได้ แต่ฉันคิดว่ามันยังคุ้มค่ากับการแบ่งปัน

บางทีบางคนสามารถแก้ไขได้:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface SomeContainer<E> {
    E createContents();
}

public class Main {

    @SuppressWarnings("unchecked")
    public static <E> SomeContainer<E> createSomeContainer() {
        return (SomeContainer<E>) Proxy.newProxyInstance(Main.class.getClassLoader(),
                new Class[]{ SomeContainer.class }, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Class<?> returnType = method.getReturnType();
                return returnType.newInstance();
            }
        });
    }

    public static void main(String[] args) {
        SomeContainer<String> container = createSomeContainer();

    [*] System.out.println("String created: [" +container.createContents()+"]");

    }
}

มันผลิต:

Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
    at Main.main(Main.java:26)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

สาย 26 [*]เป็นหนึ่งเดียวกับที่

โซลูชันที่ทำงานได้อย่างเดียวคือ @JustinRudd


2

คำตอบของ @ Noah

เหตุผลในการเปลี่ยนแปลง

a]ปลอดภัยกว่าหากใช้ประเภทสามัญมากกว่า 1 ประเภทในกรณีที่คุณเปลี่ยนคำสั่งซื้อ

b]ลายเซ็นประเภททั่วไปของคลาสมีการเปลี่ยนแปลงเป็นครั้งคราวเพื่อให้คุณจะไม่แปลกใจกับข้อยกเว้นที่ไม่สามารถอธิบายได้ในรันไทม์

รหัสที่แข็งแกร่ง

public abstract class Clazz<P extends Params, M extends Model> {

    protected M model;

    protected void createModel() {
    Type[] typeArguments = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
    for (Type type : typeArguments) {
        if ((type instanceof Class) && (Model.class.isAssignableFrom((Class) type))) {
            try {
                model = ((Class<M>) type).newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

หรือใช้สายการบินเดียว

รหัสหนึ่งบรรทัด

model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();

2

คุณสามารถทำอะไรได้ -

  1. ก่อนอื่นให้ประกาศตัวแปรของคลาสทั่วไป

    2. จากนั้นสร้าง constructor ของมันและยกตัวอย่างวัตถุนั้น

  2. จากนั้นใช้ทุกที่ที่คุณต้องการใช้

ตัวอย่าง-

1

private Class<E> entity;

2

public xyzservice(Class<E> entity) {
        this.entity = entity;
    }



public E getEntity(Class<E> entity) throws InstantiationException, IllegalAccessException {
        return entity.newInstance();
    }

3

E e = getEntity (เอนทิตี);


0

อย่างที่คุณพูดคุณไม่สามารถทำได้เพราะการลบประเภท คุณสามารถเรียงลำดับได้โดยใช้การสะท้อนกลับ แต่ต้องการรหัสจำนวนมากและการจัดการข้อผิดพลาดจำนวนมาก


คุณจะทำมันอย่างไรโดยใช้การสะท้อน? วิธีการเดียวที่ฉันเห็นคือ Class.getTypeParameters () แต่จะคืนค่าเฉพาะชนิดที่ประกาศไม่ใช่ประเภทรันไทม์
David Citron

คุณกำลังพูดถึงเรื่องนี้หรือไม่? artima.com/weblogs/viewpost.jsp?thread=208860
David Citron

0

ถ้าคุณหมายถึง new E() มันเป็นไปไม่ได้ และฉันจะเพิ่มว่ามันไม่ถูกต้องเสมอ - คุณจะรู้ได้อย่างไรว่า E มีคอนสตรัคเตอร์ที่ไม่มีการวางตลาดหรือไม่ แต่คุณสามารถมอบหมายการสร้างให้กับคลาสอื่นที่รู้วิธีสร้างอินสแตนซ์ - ซึ่งอาจเป็นClass<E>หรือรหัสที่กำหนดเองของคุณเช่นนี้

interface Factory<E>{
    E create();
}    

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}

0
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();

1
สิ่งนี้ใช้ไม่ได้กับตัวอย่างของฉันในคำถามเดิม superclass ในสำหรับเป็นเพียงSomeContainer Objectดังนั้นการthis.getClass().getGenericSuperclass()ส่งกลับClass(ชั้น java.lang.Object) ParameterizedTypeไม่ได้เป็น นี้เป็นจริงชี้แล้วออกโดยเพียร์ตอบstackoverflow.com/questions/75175/...เช่นกัน
David Citron

1
ผิดทั้งหมด: ข้อยกเว้นในเธรด "main" java.lang.ClassCastException: java.lang.Class ไม่สามารถส่งไปยัง java.lang.reflect.ParameterizedType
Aubin

0

คุณสามารถทำสิ่งนี้ได้ด้วยตัวอย่างต่อไปนี้:

import java.lang.reflect.ParameterizedType;

public class SomeContainer<E> {
   E createContents() throws InstantiationException, IllegalAccessException {
      ParameterizedType genericSuperclass = (ParameterizedType)
         getClass().getGenericSuperclass();
      @SuppressWarnings("unchecked")
      Class<E> clazz = (Class<E>)
         genericSuperclass.getActualTypeArguments()[0];
      return clazz.newInstance();
   }
   public static void main( String[] args ) throws Throwable {
      SomeContainer< Long > scl = new SomeContainer<>();
      Long l = scl.createContents();
      System.out.println( l );
   }
}

4
ผิดทั้งหมด: ข้อยกเว้นในเธรด "main" java.lang.ClassCastException: java.lang.Class ไม่สามารถส่งไปยัง java.lang.reflect.ParameterizedType
Aubin

0

มีห้องสมุดต่าง ๆ ที่สามารถแก้ปัญหาEให้คุณได้โดยใช้เทคนิคที่คล้ายกับบทความของ Robertson ที่กล่าวถึง นี่คือการดำเนินการcreateContentsที่ใช้TypeToolsเพื่อแก้ไขคลาส raw ที่แทนด้วย E:

E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

สิ่งนี้จะถือว่า getClass () แก้ไขเป็นคลาสย่อยของ SomeContainer และจะล้มเหลวเป็นอย่างอื่นเนื่องจากค่าพารามิเตอร์ที่แท้จริงของ E จะถูกลบเมื่อรันไทม์หากไม่ถูกดักจับในคลาสย่อย


0

นี่คือการใช้งานcreateContentsที่ใช้TypeToolsเพื่อแก้ไขคลาส raw ที่แสดงโดยE:

E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

วิธีการนี้ใช้งานได้เฉพาะในกรณีที่SomeContainerเป็นคลาสย่อยดังนั้นค่าจริงของEจะถูกบันทึกในการกำหนดประเภท:

class SomeStringContainer extends SomeContainer<String>

มิฉะนั้นค่าของ E จะถูกลบเมื่อรันไทม์และไม่สามารถกู้คืนได้


-1

คุณสามารถใช้ classloader และชื่อคลาสได้ในที่สุดพารามิเตอร์บางตัว

final ClassLoader classLoader = ...
final Class<?> aClass = classLoader.loadClass("java.lang.Integer");
final Constructor<?> constructor = aClass.getConstructor(int.class);
final Object o = constructor.newInstance(123);
System.out.println("o = " + o);

สิ่งนี้เลวร้ายยิ่งกว่าเพียงแค่ผ่านคลาสวัตถุ
newacct

คุณไม่จำเป็นต้องอ้างอิง class loader อย่างชัดเจนเลย
Stefan Reich

-1

นี่คือโซลูชันที่ได้รับการปรับปรุงตามที่ParameterizedType.getActualTypeArgumentsกล่าวไว้แล้วโดย @noah, @Lars Bohl และคนอื่น ๆ

การปรับปรุงเล็กน้อยครั้งแรกในการนำไปใช้ โรงงานไม่ควรส่งคืนอินสแตนซ์ แต่เป็นประเภท ทันทีที่คุณส่งคืนอินสแตนซ์โดยใช้Class.newInstance()คุณจะลดขอบเขตการใช้งาน เพราะคอนสตรัคเตอร์ที่ไม่มีข้อโต้แย้งเท่านั้นที่สามารถเรียกสิ่งนี้ได้ วิธีที่ดีกว่าคือส่งคืนชนิดและอนุญาตให้ลูกค้าเลือกตัวสร้างที่เขาต้องการเรียกใช้:

public class TypeReference<T> {
  public Class<T> type(){
    try {
      ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
      if (pt.getActualTypeArguments() == null || pt.getActualTypeArguments().length == 0){
        throw new IllegalStateException("Could not define type");
      }
      if (pt.getActualTypeArguments().length != 1){
        throw new IllegalStateException("More than one type has been found");
      }
      Type type = pt.getActualTypeArguments()[0];
      String typeAsString = type.getTypeName();
      return (Class<T>) Class.forName(typeAsString);

    } catch (Exception e){
      throw new IllegalStateException("Could not identify type", e);
    }

  }
}

นี่คือตัวอย่างการใช้งาน @Lars Bohl ได้แสดงให้เห็นถึงวิธีการลงชื่อเข้าใช้เพื่อรับการแปลเจนเนอเรชั่นใหม่ผ่านทางส่วนขยาย @noah {}เพียงผ่านการสร้างอินสแตนซ์ที่มี นี่คือการทดสอบเพื่อแสดงให้เห็นถึงทั้งสองกรณี:

import java.lang.reflect.Constructor;

public class TypeReferenceTest {

  private static final String NAME = "Peter";

  private static class Person{
    final String name;

    Person(String name) {
      this.name = name;
    }
  }

  @Test
  public void erased() {
    TypeReference<Person> p = new TypeReference<>();
    Assert.assertNotNull(p);
    try {
      p.type();
      Assert.fail();
    } catch (Exception e){
      Assert.assertEquals("Could not identify type", e.getMessage());
    }
  }

  @Test
  public void reified() throws Exception {
    TypeReference<Person> p = new TypeReference<Person>(){};
    Assert.assertNotNull(p);
    Assert.assertEquals(Person.class.getName(), p.type().getName());
    Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
    Assert.assertNotNull(ctor);
    Person person = (Person) ctor.newInstance(NAME);
    Assert.assertEquals(NAME, person.name);
  }

  static class TypeReferencePerson extends TypeReference<Person>{}

  @Test
  public void reifiedExtenension() throws Exception {
    TypeReference<Person> p = new TypeReferencePerson();
    Assert.assertNotNull(p);
    Assert.assertEquals(Person.class.getName(), p.type().getName());
    Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
    Assert.assertNotNull(ctor);
    Person person = (Person) ctor.newInstance(NAME);
    Assert.assertEquals(NAME, person.name);
  }
}

หมายเหตุ:คุณสามารถบังคับให้ลูกค้าของTypeReferenceมักจะใช้เช่นเมื่อถูกสร้างขึ้นโดยการทำชั้นนี้เป็นนามธรรม:{} public abstract class TypeReference<T>ฉันไม่ได้ทำเพียงเพื่อแสดงกรณีทดสอบที่ถูกลบ

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