Java'da tür bağımlılığı, Bölüm 1

Tür uyumluluğunu anlamak, iyi Java programları yazmak için temeldir, ancak Java dili öğeleri arasındaki farklılıkların etkileşimi, yeni başlayanlara oldukça akademik görünebilir. Bu makale, zorlukların üstesinden gelmeye hazır yazılım geliştiricilerine yöneliktir! Bölüm 1, dizi türleri ve genel türler gibi daha basit öğelerin yanı sıra özel Java dili öğesi olan joker karakter arasındaki kovaryant ve çelişkili ilişkileri ortaya koymaktadır. Bölüm 2, yaygın API örneklerinde ve lambda ifadelerinde tür bağımlılığını ve varyansı araştırıyor.

indir Kaynağı indirin Bu makale için kaynak kodunu alın, "Java'da tür bağımlılığı, Bölüm 1." JavaWorld için Dr. Andreas Solymosi tarafından düzenlendi.

Kavramlar ve terminoloji

Çeşitli Java dili unsurları arasındaki kovaryans ve kontravarlık ilişkilerine girmeden önce, paylaşılan bir kavramsal çerçeveye sahip olduğumuzdan emin olalım.

Uyumluluk

Nesne yönelimli programlamada uyumluluk , Şekil 1'de gösterildiği gibi türler arasındaki yönlendirilmiş bir ilişkiyi ifade eder.

Andreas Solymosi

Türlerin değişkenleri arasında veri aktarımı mümkünse Java'da iki türün uyumlu olduğunu söylüyoruz . Derleyici kabul ederse veri aktarımı mümkündür ve atama veya parametre geçişi yoluyla yapılır. Örnek shortolarak uyumludur intçünkü atama intVariable = shortVariable;mümkündür. Ancak boolean, intatama intVariable = booleanVariable;mümkün olmadığı için uyumlu değildir; derleyici bunu kabul etmeyecektir.

Uyumluluk yönlendirilmiş bir ilişki olduğu için bazen uyumludur, ancak uyumlu değildir veya aynı şekilde değildir. Açık veya örtük uyumluluğu tartışmaya başladığımızda bunu daha ayrıntılı olarak göreceğiz.T1T2T2T1

Önemli olan, referans türleri arasındaki uyumluluğun yalnızca bir tür hiyerarşisi içinde mümkün olmasıdır . ObjectÖrneğin tüm sınıf türleri ile uyumludur , çünkü tüm sınıflar örtük olarak Object. Integerbununla uyumlu değildir Float, çünkü Floatüst sınıfı değildir Integer. Integerile uyumludur Number, çünkü Numberbir (soyut) üst sınıfıdır Integer. Aynı tür hiyerarşide bulundukları için, derleyici atamayı kabul eder numberReference = integerReference;.

Uyumluluğun açıkça işaretlenip işaretlenmemesine bağlı olarak örtülü veya açık uyumluluktan bahsediyoruz . Örneğin, kısa (yukarıda gösterildiği gibi) örtük olarak uyumludur, intancak bunun tersi değildir: atama shortVariable = intVariable;mümkün değildir. Bununla birlikte, kısa, açıkça uyumludur int, çünkü atama shortVariable = (short)intVariable;mümkündür. Burada , tip dönüşümü olarak da bilinen döküm yoluyla uyumluluğu işaretlemeliyiz .

Benzer şekilde, referans türleri arasında: integerReference = numberReference;kabul edilemez, sadece integerReference = (Integer) numberReference;kabul edilecektir. Bu nedenle, Integerbir örtülü uyumlu Numberama Numbersadece açıkça uyumlu Integer.

Bağımlılık

Bir tür, diğer türlere bağlı olabilir. Örneğin, dizi türü int[]ilkel türe bağlıdır int. Benzer şekilde, genel tür ArrayList, türe bağlıdır Customer. Yöntemler, parametrelerinin türlerine bağlı olarak türe de bağlı olabilir. Örneğin yöntem void increment(Integer i); türüne bağlıdır Integer. Bazı yöntemler (bazı genel türler gibi), birden fazla türe bağlıdır - birden fazla parametreye sahip yöntemler gibi.

Kovaryans ve kontravaryans

Kovaryans ve kontravaryans, türlere göre uyumluluğu belirler. Her iki durumda da varyans yönlendirilmiş bir ilişkidir. Kovaryans "aynı yönde farklı" ya da tercüme edilebilir ile-farklı ise, contravariance "ters yönde, farklı" anlamına gelir ya da karşı-Farklı . Kovaryant ve kontravaryant tipler aynı değildir, ancak aralarında bir korelasyon vardır. İsimler korelasyonun yönünü ima ediyor.

Dolayısıyla kovaryans , iki türün uyumluluğunun, onlara bağlı olan türlerin uyumluluğunu ifade ettiği anlamına gelir. Tür uyumluluğu göz önüne alındığında, Şekil 2'de gösterildiği gibi bağımlı türlerin kovaryant olduğu varsayılır.

Andreas Solymosi

Uyumluluğu için uyumluluğunu eder ) için ). Bağımlı tür , kovaryant olarak adlandırılır ; veya daha doğrusu )) ile eşdeğişken ).T1T2A(T1A(T2A(T)A(T1A(T2

Başka bir örnek için: atama numberArray = integerArray;mümkün olduğu için (en azından Java'da), dizi türleri Integer[]ve birlikte Number[]değişkendir. Yani, biz söyleyebiliriz Integer[]olduğunu örtük eşdeğişkin için Number[]. Tersi doğru değildir ederken - atama integerArray = numberArray;mümkün değildir - atama tipi döküm ile ( integerArray = (Integer[])numberArray;) ise mümkün; Dolayısıyla, diyebiliriz, Number[]olduğu açıkça eşdeğişkin için Integer[].

Özetlemek gerekirse: Integerörtük olarak uyumludur Number, bu nedenle Integer[]örtülü olarak eşdeğişken Number[]ve Number[]açıkça eş değişkendir Integer[]. Şekil 3 göstermektedir.

Andreas Solymosi

Genel olarak, dizi türlerinin Java'da kovaryant olduğunu söyleyebiliriz. Makalenin ilerleyen bölümlerinde genel türler arasındaki kovaryans örneklerine bakacağız.

Kontraviyans

Kovaryans gibi kontravans da yönlendirilmiş bir ilişkidir. Kovaryans ile-farklı anlamına gelirken kontravans, farklı-karşı anlamına gelir . Daha önce bahsettiğim gibi isimler korelasyonun yönünü ifade ediyor . Varyansın genel olarak türlerin bir özniteliği değil, yalnızca bağımlı türler (diziler ve genel türler ve ayrıca Bölüm 2'de tartışacağım yöntemler gibi) olduğunu belirtmek de önemlidir .

To uyumluluğunun ) ile ) arasında uyumluluğunu belirtmesi durumunda, karşı A(T)değişken olarak adlandırılan gibi bir bağımlı tür . Şekil 4 göstermektedir.T1T2A(T2A(T1

Andreas Solymosi

Bir dil elemanı (tip veya yöntem) A(T)bağlı Tolan bildirdiğinden uyumluluk varsa için uyumluluğunu eder ) için ). Uyumluluğu durumunda için uyumluluğunu anlaşılacağı üzere) ), daha sonra türü olan kontravaryant . Uyumluluğu durumunda arasında herhangi arasında uyumluluk anlamına gelmez ) ve ), daha sonra bir değişmez .T1T2A(T1A(T2T1T2A(T2A(T1A(T)T1T2A(T1A(T2A(T)

Java'daki dizi türleri örtük olarak çelişkili değildir , ancak genel türler gibi açıkça çelişkili olabilirler . Makalenin ilerleyen bölümlerinde bazı örnekler sunacağım.

Türe bağlı öğeler: Yöntemler ve türler

Java'da yöntemler, dizi türleri ve genel (parametreleştirilmiş) türler, türe bağlı öğelerdir. Yöntemler, parametrelerinin türlerine bağlıdır. Bir dizi türü, T[]öğelerinin türlerine bağlıdır T. Genel bir tür G, tür parametresine bağlıdır T. Şekil 5 göstermektedir.

Andreas Solymosi

Çoğunlukla bu makale tür uyumluluğuna odaklanır, ancak Bölüm 2'nin sonuna doğru yöntemler arasındaki uyumluluğa değineceğim.

Örtük ve açık tür uyumluluğu

Earlier, you saw the type T1 being implicitly (or explicitly) compatible to T2. This is only true if the assignment of a variable of type T1 to a variable of type T2 is allowed without (or with) tagging. Type casting is the most frequent way to tag explicit compatibility:

 variableOfTypeT2 = variableOfTypeT1; // implicit compatible variableOfTypeT2 = (T2)variableOfTypeT1; // explicit compatible 

For example, int is implicitly compatible to long and explicitly compatible to short:

 int intVariable = 5; long longVariable = intVariable; // implicit compatible short shortVariable = (short)intVariable; // explicit compatible 

Implicit and explicit compatibility exists not only in assignments, but also in passing parameters from a method call to a method definition and back. Together with input parameters, this means also passing a function result, which you would do as an output parameter.

Note that boolean isn't compatible to any other type, nor can a primitive and a reference type ever be compatible.

Method parameters

We say, a method reads input parameters and writes output parameters. Parameters of primitive types are always input parameters. A return value of a function is always an output parameter. Parameters of reference types can be both: if the method changes the reference (or a primitive parameter), the change remains within the method (meaning it isn't visible outside the method after the call--this is known as call by value). If the method changes the referred object, however, the change remains after being returned from the method--this is known as call by reference.

A (reference) subtype is implicitly compatible to its supertype, and a supertype is explicitly compatible to its subtype. This means that reference types are compatible only within their hierarchy branch--upward implicitly and downward explicitly:

 referenceOfSuperType = referenceOfSubType; // implicit compatible referenceOfSubType = (SubType)referenceOfSuperType; // explicit compatible 

The Java compiler typically allows implicit compatibility for an assignment only if there is no danger of losing information at runtime between the different types. (Note, however, that this rule isn't valid for losing precision, such as in an assignment from int to float.) For example, int is implicitly compatible to long because a long variable holds every int value. In contrast, a short variable does not hold any int values; thus, only explicit compatibility is allowed between these elements.

Andreas Solymosi

Şekil 6'daki örtük uyumluluğun ilişkinin geçişli olduğunu varsaydığını unutmayın : shortile uyumludur long.

Şekil 6'da gördüğünüze benzer şekilde, bir alt tipin intreferansını bir süper tipin referansını atamak her zaman mümkündür . Diğer yöndeki aynı atamanın a atabileceğini unutmayın ClassCastException, ancak Java derleyicisi buna yalnızca tür çevriminde izin verir.

Dizi türleri için kovaryans ve kontravaryans

Java'da, bazı dizi türleri eşdeğişken ve / veya aykırıdır. Eğer kovaryans durumunda bu araçlar o Tkadar uyumludur U, daha sonra T[]da uyumludur U[]. Kontravans durumunda U[], uyumlu olduğu anlamına gelir T[]. Java'da ilkel tür dizileri değişmez:

 longArray = intArray; // type error shortArray = (short[])intArray; // type error 

Başvuru türleri dizileri örtük olarak birlikte değişkendir ve açıkça çelişkilidir , ancak:

 SuperType[] superArray; SubType[] subArray; ... superArray = subArray; // implicit covariant subArray = (SubType[])superArray; // explicit contravariant 
Andreas Solymosi

Şekil 7. Diziler için örtük kovaryans

Bunun pratik olarak anlamı, dizi bileşenlerinin atamasının ArrayStoreExceptionçalışma zamanında gerçekleştirilebileceğidir. Bir dizi başvurusu SuperTypebir dizi nesnesine başvuruyorsa SubTypeve bileşenlerinden biri bir SuperTypenesneye atanmışsa , o zaman:

 superArray[1] = new SuperType(); // throws ArrayStoreException 

This is sometimes called the covariance problem. The true problem is not so much the exception (which could be avoided with programming discipline), but that the virtual machine must check every assignment in an array element at runtime. This puts Java at an efficiency disadvantage against languages without covariance (where a compatible assignment for array references is prohibited) or languages like Scala, where covariance can be switched off.

An example for covariance

In a simple example, the array reference is of type Object[] but the array object and the elements are of different classes:

 Object[] objectArray; // array reference objectArray = new String[3]; // array object; compatible assignment objectArray[0] = new Integer(5); // throws ArrayStoreException 

Kovaryans nedeniyle, derleyici dizi elemanlarına yapılan son atamanın doğruluğunu kontrol edemez - JVM bunu yapar ve önemli bir masrafla. Ancak, dizi türleri arasında tür uyumluluğunun kullanımı yoksa derleyici masrafı ortadan kaldırabilir.

Andreas Solymosi

Java'da, üst tipinin bir nesnesine atıfta bulunan bazı türdeki bir referans değişkeninin yasak olduğunu unutmayın: Şekil 8'deki oklar yukarı doğru yönlendirilmemelidir.

Genel türlerdeki varyanslar ve joker karakterler

Java'da genel (parametreleştirilmiş) türler örtük olarak değişmezdir , yani genel bir türün farklı örnekleri birbiriyle uyumlu değildir. Hatta tip çevrim uyumluluğa neden olmaz:

 Generic superGeneric; Generic subGeneric; subGeneric = (Generic)superGeneric; // type error superGeneric = (Generic)subGeneric; // type error 

Yazım hataları olsa bile ortaya çıkar subGeneric.getClass() == superGeneric.getClass(). Sorun, yöntemin getClass()ham türü belirlemesidir - bu nedenle bir tür parametresi, bir yöntemin imzasına ait değildir. Böylece, iki yöntem bildirimi

 void method(Generic p); void method(Generic p); 

bir arayüz (veya soyut sınıf) tanımında birlikte meydana gelmemelidir.