java - شرح - generic معنى




إنشاء مثيل من النوع العام في جاوة؟ (16)

إذا كنت تحتاج إلى نسخة جديدة من وسيطة النوع داخل فئة عامة ، فاجعل مؤسسيك يطلبون صفها ...

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

الايجابيات:

  • أبسط من ذلك بكثير (وأقل إشكالية) من نهج روبرتسون سوبر توكين (STT).
  • أكثر فعالية من أسلوب STT (الذي سوف يأكل هاتفك المحمول لتناول الإفطار).

سلبيات:

  • لا يمكن تمرير Class إلى مُنشئ افتراضي (وهذا هو السبب في كون Foo نهائيًا). إذا كنت في حاجة فعلاً إلى مُنشئ افتراضي ، فيمكنك دائمًا إضافة طريقة تعيين ، ولكن يجب عليك تذكر أن تقوم بإتصالها في وقت لاحق.
  • اعتراض روبرتسون ... المزيد من الحانات من الخراف السوداء (على الرغم من تحديد فئة الحجة مرة واحدة مرة أخرى لن يقتلك بالضبط). وعلى عكس ادعاءات روبرتسون ، فإن هذا لا ينتهك مبدأ DRY على أي حال لأن المترجم سيضمن صحة النوع.
  • ليس تماما Foo<L> دليل. بالنسبة للمبتدئين ... newInstance() برمي المتذبذب إذا لم يكن لفئة الوسيطة type مُنشئ افتراضي. هذا ينطبق على جميع الحلول المعروفة على أي حال.
  • يفتقد الكلفة الإجمالية لنهج STT. لا صفقة كبيرة رغم ذلك (النظر في النفقات العامة الفاحشة من STT).

هل من الممكن إنشاء مثيل لنوع عام في Java؟ أفكر استنادًا إلى ما رأيته من أن الإجابة no ( بسبب محو الكتابة ) ، لكنني سأكون مهتمًا إذا كان بإمكان أي شخص رؤية شيء ما مفقود:

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

EDIT: تبيّن أنه يمكن استخدام Super Type Tokens لحل مشكلتي ، ولكنها تتطلب الكثير من الشفرات القائمة على الانعكاس ، كما أشارت بعض الإجابات أدناه.

سأترك هذا مفتوحا لبعض الوقت لمعرفة ما إذا كان أي شخص يأتي مع أي شيء مختلف بشكل كبير عن المادة آرتيما ايان روبرتسون.


إذا كنت تقصد new E() فمن المستحيل. وأود أن أضيف أنه ليس صحيحًا دائمًا - كيف يمكنك معرفة ما إذا كان E يمتلك منشئًا عامًا؟ ولكن يمكنك دائمًا تفويض الإنشاء إلى فئة أخرى تعرف كيفية إنشاء مثيل - يمكن أن تكون Class<E> أو رمزك المخصص مثل هذا

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

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

إضفاء على إجابة نوح @.

سبب التغيير

أ] أكثر أمانًا إذا تم استخدام أكثر من نوع عام واحد في حالة تغيير الترتيب.

ب) يتغير توقيع النوع العام من وقت لآخر حتى لا يفاجئك باستثناءات غير مفسرة في وقت التشغيل.

قانون قوي

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

إليك تطبيق createContents الذي يستخدم TypeTools لحل الطبقة الأولية التي يمثلها E :

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

لا يعمل هذا الأسلوب إلا إذا كان SomeContainer فئة فرعية بحيث يتم التقاط القيمة الفعلية لـ E في تعريف نوع:

class SomeStringContainer extends SomeContainer<String>

وإلا يتم مسح قيمة E في وقت التشغيل ولا يمكن استردادها.


جافا للأسف لا يسمح بما تريد القيام به. راجع 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);

دونو إذا كان هذا يساعد ، ولكن عندما تكون فئة فرعية (بما في ذلك المجهول) نوع عام ، تتوفر معلومات النوع عبر الانعكاس. على سبيل المثال،

public abstract class Foo<E> {

  public E instance;  

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

}

لذلك ، عندما تكون فئة فرعية Foo ، تحصل على مثال لـ Bar على سبيل المثال ،

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

ولكنه عمل كثير ، ولا يعمل إلا مع الفئات الفرعية. يمكن أن يكون سهل على الرغم من.


ظننت أنني يمكن أن أفعل ذلك ، لكن خيبة أمل كبيرة: إنه لا يعمل ، لكنني أعتقد أنه لا يزال يستحق المشاركة.

ربما يمكن للشخص تصحيح:

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


عندما تعمل مع E في وقت التحويل البرمجي ، فإنك لا تهتم بالنوع العام الفعلي "E" (إما أنك تستخدم الانعكاس أو تعمل مع فئة أساسية من النوع العام) ، لذلك اسمح للفئة الفرعية بتوفير مثال 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();
    }
}

في Java 8 ، يمكنك استخدام الواجهة الوظيفية للمورد لتحقيق ذلك بسهولة:

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

في ما يلي أحد الخيارات التي توصلت إليها ، قد يساعدك في:

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

من برنامج Java Tutorial - قيود على 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);

هناك العديد من المكتبات التي يمكن أن تحل E أجلك باستخدام تقنيات مشابهة لما ناقشته مقالة روبرتسون. في ما يلي تطبيق لـ createContents يستخدم TypeTools لحل الطبقة الأولية التي يمثلها E:

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

هذا يفترض أن getClass () يحل إلى فئة فرعية من SomeContainer ، وسوف تفشل وإلا لأن قيمة E الفعلية المعلمة سوف تم محوها في وقت التشغيل إذا لم يتم التقاطها في فئة فرعية.


يمكنك القيام بذلك الآن ولا يتطلب مجموعة من رمز الانعكاس.

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 for TypeToken .


يمكنك تحقيق ذلك باستخدام المقتطف التالي:

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

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