java - شرح - لماذا تحتوي الطرق العامة والأنواع العامة على صيغة مقدمة من نوع مختلف؟




generic method java (5)

أثناء دراسة الأدوية الجنسية ، لاحظت وجود اختلاف في تركيب مقدمة النوع بين الطرق العامة والأنواع العامة (الطبقة أو الواجهة) التي أربكتني.

بناء جملة طريقة عامة

<T> void doStuff(T t) {
    // Do stuff with T
}

يقول المستندات

يتضمن بناء جملة أسلوب عام معلمة نوع ، داخل أقواس زاوية ، ويظهر قبل نوع إرجاع الطريقة

بناء الجملة لنوع عام هو

class Stuff<T> {
    // Do stuff with T
    T t;
}

يقول المستندات

يتبع مقطع معلمات النوع ، محدد بواسطة أقواس زاوية (<>) اسم الفئة . يحدد محددات النوع

لأنها لا تذكر لماذا يجب أن تأتي قبل أو بعد ذلك.

من أجل أن تكون متسقة مع بعضها البعض ، كنت أتوقع إما أن يكون بناء الجملة الأسلوب
void doStuff<T>(T t) {} أو صيغة بناء الجملة (للفئة) هي class <T>Stuff {} ، ولكن من الواضح أن هذا ليس هو الحال.

لماذا يجب تقديم الشخص من قبل ، والآخر بعد؟

لقد استخدمت الأدوية في الغالب على شكل List<String> وجادل بأن <String>List قد تبدو غريبة ، لكن هذه حجة ذاتية ، بالإضافة إلى أساليب مثلها كذلك. يمكنك استدعاء doStuff مثل this.<String>doStuff("a string");

أبحث عن تفسير تقني اعتقدت أنه ربما يجب تقديم <T> إلى طريقة ما قبل تحديد نوع الإرجاع لأن T قد يكون نوع الإرجاع وقد لا يتمكن المجمع من التطلع إلى الأمام مثل ذلك ، لكن ذلك يبدو غريباً لأن المجمعين ذكي.

أظن أن هناك تفسيراً لهذا الأمر يتجاوز "مصممي اللغات الذين قاموا بهذه الطريقة فقط" ، لكنني لم أتمكن من العثور عليه.


أظن ، لأنه يمكن أن تعلن أنها نوع من العودة:

 <T> T doStuff(T t) {
     // Do stuff with T
    return t;
}

يجب أن تقوم بتعريف النوع قبل أن تقوم بتعريف نوع الإرجاع ، لأنه لا يمكنك استخدام شيء لم يتم تعريفه بعد. على سبيل المثال ، لا يمكنك استخدام متغير x قبل التصريح به في مكان ما. أنا أحب (أي) لغة لمتابعة بعض القواعد المنطقية ، فإنه من الأسهل استخدام ذلك ، وفي وقت ما من معرفة ذلك ، فأنت تعرف فقط ما يمكن أن تتوقعه من ذلك. هذا هو الحال مع جافا ، لديها بعض الصعاب ، ولكن بشكل عام تتبع بعض القواعد. والشيء الذي لا يمكنك استخدام شيء ما قبل إعلانه أنه قاعدة قوية جدًا في جافا ، وهو جيد جدًا ، لأنه ينتج عددًا أقل من WTF عندما تحاول فهم شفرة جافا ، لذلك أعتقد أن هذا المنطق وراء ذلك. لكنني لا أعرف من المسؤول بالضبط عن هذا القرار ، اقتباس من ويكيبيديا:

في عام 1998 ، قام كل من Gilad Bracha و Martin Odersky و David Stoutamire و Philip Wadler بإنشاء Generic Java ، وهو امتداد للغة Java لدعم الأنواع العامة. [3] أدرجت Java عام في Java (2004، Java 5) مع إضافة أحرف البدل.

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

لا أعتقد أن لها أي علاقة بالتوافق مع الإصدارات السابقة مع جافا.


افتراضي القوي هو أنه لأنه كما قلتم لطريقة ، يمكن أن تكون المعلمة العامة أيضًا نوع إرجاع دالة:

public <RETURN_TYPE> RETURN_TYPE getResult();

لذا في الوقت الذي يصل فيه المحول إلى نوع الإرجاع الخاص بالوظيفة ، فإنه قد تمت مصادفة النوع (كما في ، فإنه يعرف أنه نوع عام).

إذا كان لديك بناء جملة مثل

public RETURN_TYPE getResult<RETURN_TYPE>();

سوف يتطلب ذلك عملية مسح ثانية للتحليل.

بالنسبة للصفوف ، هذه ليست مشكلة ، لأن كل الإشارات إلى النوع العام تظهر ضمن فدرة تعريف الصف ، أي بعد الإعلان عن النوع العام.


تم تقديم Java Generics باستخدام Java 1.5. فكرة ميزات اللغة الجديدة هي عدم كسر الإصدارات السابقة. يجب أن نضع في اعتبارك أن علم الوراثة هو ميزة سلامة النوع للغة / المطور. مع أن نوعين جديدين تم إدخال parameterized types type variables .

تقترح أنواع وقيم JLS 4.3 المرجعية بناء الجملة التالي لـ TypeArgument و TypeVariable .

ReferenceType: ClassOrInterfaceType TypeVariable ArrayType

ClassOrInterfaceType: ClassType InterfaceType

ClassType: TypeDeclSpecifier TypeArgumentsopt

InterfaceType: TypeDeclSpecifier TypeArgumentsopt

TypeDeclSpecifier: TypeName
ClassOrInterfaceType. معرف

TypeName: معرف TypeName. معرف

النوعالمتغير: المعرف

Array نوع: Type []

الأمثلة هي مثل هذه

Vector<String>
Seq<Seq<A>>
Seq<String>.Zipper<Integer>
Collection<Integer>
Pair<String,String>

ولأنواع المعلمات

Vector<String> x = new Vector<String>();
Vector<Integer> y = new Vector<Integer>();
return x.getClass() == y.getClass();

كلما لم يتم منحه أي تقييد سوف يفترض أنه java.lang.Object الكتابة سيكون على سبيل المثال Vector<Object> حتى يكون متوافقاً مع الإصدارات السابقة من Java.

يكون بناء جملة أساليب العامة حيث الفئة نفسها غير عام بناء الجملة التالي.

من JLs 8.4 إعلانات الأسلوب

MethodDeclaration: MethodHeader MethodBody

MethodHeader: MethodModifiersopt TypeParametersopt Result MethodDeclarator Throwsopt

MethodDeclarator: Identifier (FormalParameterListopt)

مثال يشبه هذا

public class GenericMethod {
    public static <T> T aMethod(T anObject) {
        return anObject;
    }
    public static void main(String[] args) {
        String greeting = "Hi";
        String reply = aMethod(greeting);
    }
}

الذي ينتج مع نوع محو ل

public class GenericMethod {
    public static Object aMethod(Object anObject) {
        return anObject;
    }
    public static void main(String[] args) {
        String greeting = "Hi";
        String reply = (String) aMethod(greeting);
    }
}

ومرة أخرى ، تعود إلى الإصدارات السابقة من Java. انظر ورقتي الاقتراح لمزيد من الاستدلال المتعمق

Adding Generics to the Java Programming Language: Participater Draft Specification

تخصص أنواع جافا العامة

حول الجزء التقني. خطوات إنشاء برنامج Java هي ترجمة ملف .java . يمكن للمرء أن يفعل ذلك باستخدام الأمر javac لإنشاء ملفات التصنيف. يوزع JavacParser الملف بأكمله بالمواصفات أعلاه ويقوم بإنشاء كود البايت. انظر here للحصول على شفرة المصدر JavacParser.

لنأخذ ملف Test.java التالي

class Things{}

class Stuff<T>{
    T t;

    public <U extends Things> U doStuff(T t, U u){
        return u;
    };
    public <T> T doStuff(T t){
        return t;
    };
}

للاحتفاظ بها إلى الخلف متوافقة لم تغير JVM سماتها السابقة لملفات الفصل. وأضافوا خاصية جديدة وسموها Signature . من ورقة propsal

عند استخدامه كسمة في طريقة أو حقل ، يعطي التوقيع النوع الكامل (الذي من المحتمل أن يكون عامًا) لهذه الطريقة أو الحقل. عند استخدامه كسمة فئة ، يشير التوقيع إلى معلمات النوع للفئة ، متبوعًا بنوعه الفائق ، متبوعًا بكل واجهاته. يتم توسيع بناء الجملة في التواقيع لأنواع معلمات ومتغيرات النوع. هناك أيضًا بنية توقيع جديدة لمعلمات النوع الرسمي. إن الامتدادات النحوية لسلاسل التوقيع هي كما يلي:

تحدد JVM Spec 4.3.4 بناء الجملة التالي

MethodTypeSignature: FormalTypeParametersopt (TypeSignature *) ReturnType ThrowsSignature *

ReturnType: TypeSignature VoidDescriptor

ThrowsSignature: ^ ClassTypeSignature ^ TypeVariableSignature

عن طريق تفكيك ملف javap -v مع javap -v نحصل على ما يلي:

class Stuff<T extends java.lang.Object> extends java.lang.Object
  minor version: 0
  major version: 52
  flags: ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#20         // java/lang/Object."<init>":()V
   #2 = Class              #21            // Stuff
   #3 = Class              #22            // java/lang/Object
   #4 = Utf8               t
   #5 = Utf8               Ljava/lang/Object;
   #6 = Utf8               Signature
   #7 = Utf8               TT;
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               doStuff
  #13 = Utf8               (Ljava/lang/Object;LThings;)LThings;
  #14 = Utf8               <U:LThings;>(TT;TU;)TU;
  #15 = Utf8               (Ljava/lang/Object;)Ljava/lang/Object;
  #16 = Utf8               <T:Ljava/lang/Object;>(TT;)TT;
  #17 = Utf8               <T:Ljava/lang/Object;>Ljava/lang/Object;
  #18 = Utf8               SourceFile
  #19 = Utf8               Test.java
  #20 = NameAndType        #8:#9          // "<init>":()V
  #21 = Utf8               Stuff
  #22 = Utf8               java/lang/Object
{
  T t;
    descriptor: Ljava/lang/Object;
    flags:
    Signature: #7                           // TT;

  Stuff();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 4: 0

  public <U extends Things> U doStuff(T, U);
    descriptor: (Ljava/lang/Object;LThings;)LThings;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=3, args_size=3
         0: aload_2
         1: areturn
      LineNumberTable:
        line 8: 0
    Signature: #14                          // <U:LThings;>(TT;TU;)TU;

  public <T extends java.lang.Object> T doStuff(T);
    descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: areturn
      LineNumberTable:
        line 11: 0
    Signature: #16                          // <T:Ljava/lang/Object;>(TT;)TT;
}
Signature: #17                          // <T:Ljava/lang/Object;>Ljava/lang/Object;
SourceFile: "Test.java"

طريقة

public <U extends Things> U doStuff(T t, U u){
        return u;
    };

يترجم إلى التوقيع للإشارة إلى أنها طريقة عامة

   Signature: #14                          // <U:LThings;>(TT;TU;)TU;

إذا استخدمنا فئة غير عام لإصدارات Java 1.5 السابقة على سبيل المثال

public String doObjectStuff(Object t, String u){
        return u;
    }

سوف تترجم ل

 public java.lang.String doObjectStuff(java.lang.Object, java.lang.String);
    descriptor: (Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=3, args_size=3
         0: aload_2
         1: areturn
      LineNumberTable:
        line 12: 0

والفرق الوحيد بين الاثنين هو أن الشخص لديه حقل سمة مميزة يشير إلى أنه بالفعل أسلوب عام بينما لا تحتوي عليه إصدارات Java 1.5 السابقة الأخرى. لكن كلاهما له نفس سمة descriptor

Non-Generic method
 descriptor: (Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/String;
Generic method 
 descriptor: (Ljava/lang/Object;LThings;)LThings;

مما يجعلها متوافقة إلى الوراء. لذا ستكون الإجابة كما اقترحت

"مصممي اللغة فقط جعلها بهذه الطريقة"

مع إضافة

"مصممي اللغة قاموا بهذه الطريقة فقط ، لجعلها متوافقة إلى الخلف مع عدم إضافة الكثير من التعليمات البرمجية "

تحرير: حول التعليق أنه ينبغي أن يكون من السهل التعامل مع بناء الجملة المختلفة وجدت فقرة في كتاب Java Generics and Collections by Philip Wadler، Maurice Naftalin

Generics in Java تشبه القوالب في C ++. هناك شيئان مهمان يجب أخذهما بعين الاعتبار حول العلاقة بين نماذج جافا وجداول لغة C ++: بناء الجملة والدلالات. إن التركيب اللغوي متشابه بشكل متعمد وأن الدلالات تختلف عن عمد.
من الناحية النظرية ، تم اختيار أقواس الزاوية لأنها مألوفة لدى مستخدمي C ++ ، ولأن الأقواس المربعة سيكون من الصعب تحليلها. ومع ذلك ، هناك اختلاف واحد في بناء الجملة. في C ++ ، تتطلب المعلمات المتداخلة مسافات إضافية ، بحيث ترى أشياء مثل هذه: List <List> [...] etc.

انظر هنا


ليس هناك سبب نظري عميق لهذا - يبدو أن هذه حالة من "مصممي اللغة فقط فعلوا ذلك بهذه الطريقة." فعلى سبيل المثال ، يستخدم C # بالضبط الصيغة التي تتساءل فيها عن سبب عدم تنفيذ Java. التعليمة البرمجية التالية:

private T Test<T>(T abc)
{
    throw new NotImplementedException();
}

سوف تترجم. C # مشابه بما فيه الكفاية لجافا أن هذا سيعني أنه لا يوجد سبب نظري أن جافا لم تكن قادرة على تنفيذ نفس الشيء أيضا (خاصة بالنظر إلى أن كلتا اللغتين قامت بتطبيق الأدوية في وقت مبكر في تطورها).

إن ميزة بناء جملة Java كما هي الآن هي أنه من الأسهل بشكل هامشي تطبيق محلل LL (1) للأساليب التي تستخدم بناء الجملة الحالي.


وبقدر ما أعرف أن الأدوية الجنيسة من جافا ، عندما تم تقديمها ، كانت مبنية على فكرة الأدوية الجنسية من GJ (امتداد لغة برمجة Java التي تدعم الأنواع العامة). لذلك تم أخذ الصيغة من GJ ، انظر GJ Specification .

هذه إجابة رسمية لسؤالك ، ولكن ليس إجابة لسؤالك في سياق GJ. ولكن من الواضح أنه لا علاقة له مع بناء الجملة C ++ لأنه يسبق قسم المعلمات C ++ كل من الكلمة الأساسية class ونوع الإرجاع للطريقة.





generics