java - जावा में जेनेरिक प्रकार का उदाहरण बनाएं?




generics (16)

@ नोहा के जवाब का एक अपरिवर्तनीयता।

बदलाव के कारण

ए] यदि आप ऑर्डर बदलते हैं तो 1 से अधिक सामान्य प्रकार का उपयोग सुरक्षित होता है।

बी] समय-समय पर एक वर्ग जेनेरिक प्रकार हस्ताक्षर बदलता है ताकि आप रनटाइम में अनपेक्षित अपवादों से आश्चर्यचकित नहीं होंगे।

मजबूत कोड

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

जावा में एक सामान्य प्रकार का उदाहरण बनाना संभव है? मैं सोच रहा हूं कि मैंने क्या देखा है कि उत्तर no ( टाइप एरर के कारण ), लेकिन मुझे कोई दिलचस्पी होगी यदि कोई ऐसा कुछ देख सके जो मुझे याद आ रहा है:

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

संपादित करें: यह पता चला है कि मेरे मुद्दे को हल करने के लिए सुपर टाइप टोकन का उपयोग किया जा सकता है, लेकिन इसके लिए बहुत से प्रतिबिंब-आधारित कोड की आवश्यकता होती है, क्योंकि नीचे दिए गए कुछ उत्तरों संकेत दिए गए हैं।

मैं यह देखने के लिए थोड़ी देर के लिए छोड़ दूंगा कि क्या कोई भी इयान रॉबर्टसन के आर्टिमा आलेख से नाटकीय रूप से अलग है या नहीं।


अब आप यह कर सकते हैं और इसे प्रतिबिंब कोड के समूह की आवश्यकता नहीं है।

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

बेशक अगर आपको कन्स्ट्रक्टर को कॉल करने की आवश्यकता है जिसके लिए कुछ प्रतिबिंब की आवश्यकता होगी, लेकिन यह बहुत अच्छी तरह से प्रलेखित है, यह चाल नहीं है!

टाइप टोकन के लिए जावाडॉक यहां दिया गया है।


आप उपयोग कर सकते हैं:

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

लेकिन आपको पैकेज सहित सटीक वर्ग नाम की आपूर्ति करने की आवश्यकता है, उदाहरण के लिए। java.io.FileInputStream । मैंने गणित अभिव्यक्ति पार्सर बनाने के लिए इसका इस्तेमाल किया।


आप एक क्लासलोडर और कक्षा का नाम, अंत में कुछ पैरामीटर के साथ कर सकते हैं।

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

एक और अधिक कार्यात्मक दृष्टिकोण के बारे में सोचें: कुछ ई को कुछ भी बनाने के बजाय (जो स्पष्ट रूप से एक कोड गंध है) बनाने के बजाय, एक फ़ंक्शन पास करें जो जानता है कि कैसे एक बनाना है, यानी

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

जब आप संकलन समय पर ई के साथ काम कर रहे हैं तो आप वास्तव में वास्तविक जेनेरिक प्रकार "ई" की परवाह नहीं करते हैं (या तो आप प्रतिबिंब का उपयोग करते हैं या जेनेरिक प्रकार के बेस क्लास के साथ काम करते हैं) तो सबक्लास ई के उदाहरण प्रदान करें।

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

जावा दुर्भाग्य से आप जो करना चाहते हैं उसे अनुमति नहीं देता है। Http://docs.oracle.com/javase/tutorial/java/generics/restrictions.html#createObjects देखें

आप एक प्रकार पैरामीटर का उदाहरण नहीं बना सकते हैं। उदाहरण के लिए, निम्न कोड संकलन-समय त्रुटि का कारण बनता है:

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

जैसा कि आपने कहा था, आप टाइप एरर के कारण वास्तव में ऐसा नहीं कर सकते हैं। आप प्रतिबिंब का उपयोग करके इसे कर सकते हैं, लेकिन इसके लिए बहुत सारे कोड और त्रुटि प्रबंधन की आवश्यकता होती है।


तुम सही हो। आप "नया ई" नहीं कर सकते हैं। लेकिन आप इसे बदल सकते हैं

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

यह दर्द है। लेकिन यह काम करता है। कारखाने के पैटर्न में इसे लपेटकर यह थोड़ा और सहनशील बनाता है।


मैंने सोचा कि मैं ऐसा कर सकता हूं, लेकिन काफी निराश हूं: यह काम नहीं करता है, लेकिन मुझे लगता है कि यह अभी भी साझा करने लायक है।

शायद कोई सही कर सकता है:

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 द्वारा एकमात्र व्यवहार्य समाधान है


यदि आपका मतलब new E() तो यह असंभव है। और मैं जोड़ता हूं कि यह हमेशा सही नहीं होता है - अगर आपको सार्वजनिक नो-Args कन्स्ट्रक्टर है तो आप कैसे जानते हैं? लेकिन आप हमेशा किसी अन्य वर्ग को सृजन का प्रतिनिधि बना सकते हैं जो एक उदाहरण बनाने के बारे में जानता है - यह Class<E> या आपका कस्टम कोड इस तरह हो सकता है

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

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

यदि आपको सामान्य वर्ग के अंदर किसी प्रकार के तर्क के नए उदाहरण की आवश्यकता है तो अपने रचनाकारों को अपनी कक्षा की मांग करें ...

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

पेशेवरों:

  • रॉबर्टसन के सुपर टाइप टोकन (एसटीटी) दृष्टिकोण की तुलना में बहुत आसान (और कम समस्याग्रस्त)।
  • एसटीटी दृष्टिकोण से अधिक कुशल (जो नाश्ते के लिए आपका सेलफोन खाएगा)।

विपक्ष:

  • क्लास को डिफॉल्ट कन्स्ट्रक्टर में पास नहीं कर सकता (यही कारण है कि फू अंतिम है)। यदि आपको वास्तव में एक डिफ़ॉल्ट कन्स्ट्रक्टर की आवश्यकता है तो आप हमेशा एक सेटर विधि जोड़ सकते हैं लेकिन फिर आपको उसे बाद में कॉल देना याद रखना चाहिए।
  • रॉबर्टसन की आपत्ति ... एक काले भेड़ की तुलना में अधिक बार्स (हालांकि टाइप तर्क वर्ग को एक और बार निर्दिष्ट करना आपको बिल्कुल मार नहीं देगा)। और रॉबर्टसन के दावों के विपरीत यह डीआरवाई प्रिंसिपल का उल्लंघन नहीं करता है क्योंकि संकलक टाइप शुद्धता सुनिश्चित करेगा।
  • पूरी तरह से Foo<L> सबूत नहीं। स्टार्टर्स के लिए ... newInstance() एक wobbler फेंक देगा अगर टाइप तर्क वर्ग में एक डिफ़ॉल्ट कन्स्ट्रक्टर नहीं है। यह वैसे भी सभी ज्ञात समाधानों पर लागू होता है।
  • एसटीटी दृष्टिकोण के कुल encapsulation को कम करता है। हालांकि एक बड़ा सौदा नहीं है (एसटीटी के अपमानजनक प्रदर्शन ओवरहेड पर विचार करना)।

यहां एक विकल्प है जिसके साथ मैं आया हूं, इससे मदद मिल सकती है:

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

संपादित करें: वैकल्पिक रूप से आप इस कन्स्ट्रक्टर का उपयोग कर सकते हैं (लेकिन इसे ई के उदाहरण की आवश्यकता है):

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

रॉबर्टसन लेख की चर्चा के समान तकनीकों का उपयोग करके आपके लिए E को हल करने वाले विभिन्न पुस्तकालय हैं। यहां TypeTools द्वारा प्रतिनिधित्व कच्चे वर्ग को हल करने के लिए TypeTools का उपयोग करने वाले createContents का एक कार्यान्वयन है:

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

यह मानता है कि getClass () SomeContainer के उप-वर्ग में हल हो जाता है और अन्यथा विफल हो जाएगा क्योंकि ई के वास्तविक पैरामीटरयुक्त मान को रनटाइम पर मिटा दिया जाएगा यदि यह सबक्लास में कैप्चर नहीं किया गया है।


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

}

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




generics