CodeGym /Java Blogu /Rastgele /Java Generics: Pratikte köşeli parantezler nasıl kullanıl...
John Squirrels
Seviye
San Francisco

Java Generics: Pratikte köşeli parantezler nasıl kullanılır?

grupta yayınlandı

giriiş

JSE 5.0 ile başlayarak, jenerikler Java dilinin cephaneliğine eklendi.

Java'da jenerikler nelerdir?

Jenerikler, Java'nın jenerik programlamayı uygulamaya yönelik özel mekanizmasıdır; algoritmaların açıklamasını değiştirmeden farklı veri türleriyle çalışmanıza izin veren verileri ve algoritmaları tanımlamanın bir yolu. Oracle web sitesinde jeneriklere ayrılmış ayrı bir öğretici vardır: " Ders ". Jenerikleri anlamak için önce neden ihtiyaç duyulduğunu ve ne verdiklerini anlamanız gerekir. Eğitimin " Neden Generics Kullanılmalı? " bölümü, birkaç amacın derleme zamanında daha güçlü tip denetimi ve açık yayınlara olan ihtiyacı ortadan kaldırmak olduğunu söylüyor. Sevgili TutorialspointJava'da Jenerikler: pratikte köşeli parantezler nasıl kullanılır - 1 çevrimiçi java derleyicimizde bazı testler için hazırlanalım . Aşağıdaki koda sahip olduğunuzu varsayalım:

import java.util.*;
public class HelloWorld {
	public static void main(String []args) {
		List list = new ArrayList();
		list.add("Hello");
		String text = list.get(0) + ", world!";
		System.out.print(text);
	}
}
Bu kod mükemmel bir şekilde çalışacaktır. Ama ya patron bize gelip "Merhaba dünya!" aşırı kullanılan bir ifadedir ve yalnızca "Merhaba" döndürmeniz gerekir? ", world!" kelimesini birleştiren kodu kaldıracağız. Bu yeterince zararsız görünüyor, değil mi? Ama aslında Derleme Zamanında bir hata alıyoruz:

error: incompatible types: Object cannot be converted to String
Sorun şu ki Listemizde Nesneler depolanıyor. String, Object'in soyundan gelir (çünkü tüm Java sınıfları dolaylı olarak Object mirasını alır ), bu da açık bir atamaya ihtiyacımız olduğu, ancak bir tane eklemediğimiz anlamına gelir. Birleştirme işlemi sırasında, nesne kullanılarak statik String.valueOf(obj) yöntemi çağrılacaktır. Sonunda, Object sınıfının toString yöntemini çağırır . Başka bir deyişle, Listemiz bir Object içerir . Bu, belirli bir türe ( Object değil) ihtiyaç duyduğumuz her yerde tür dönüşümünü kendimiz yapmamız gerekeceği anlamına gelir:

import java.util.*;
public class HelloWorld {
	public static void main(String []args) {
		List list = new ArrayList();
		list.add("Hello!");
		list.add(123);
		for (Object str : list) {
		    System.out.println("-" + (String)str);
		}
	}
}
Ancak bu durumda List nesneleri aldığı için sadece String s değil, Integer s de saklayabilir . Ancak en kötüsü, derleyicinin burada yanlış bir şey görmemesidir. Ve şimdi ÇALIŞMA SÜRESİNDE ("çalışma zamanı hatası" olarak bilinen) bir hata alacağız. Hata şu olacaktır:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Bunun pek iyi olmadığını kabul etmelisiniz. Ve tüm bunlar, derleyicinin, programcının amacını her zaman doğru bir şekilde tahmin edebilecek bir yapay zeka olmadığı için. Java SE 5, derleyiciye niyetlerimizi - hangi türleri kullanacağımızı - söylememize izin vermek için jenerikleri tanıttı. Derleyiciye ne istediğimizi söyleyerek kodumuzu düzeltiriz:

import java.util.*;
public class HelloWorld {
	public static void main(String []args) {
		List<String> list = new ArrayList<>();
		list.add("Hello!");
		list.add(123);
		for (Object str : list) {
		    System.out.println("-" + str);
		}
	}
}
Gördüğünüz gibi, artık bir String'e dönüştürmeye ihtiyacımız yok . Ek olarak, tip bağımsız değişkenini çevreleyen açılı parantezlerimiz var. Şimdi derleyici, listeye 123 ekleyen satırı kaldırana kadar sınıfı derlememize izin vermeyecek, çünkü bu bir Tamsayı . Ve bize bunu söyleyecek. Birçok kişi jeneriklere "sözdizimsel şeker" diyor. Ve haklılar, çünkü jenerikler derlendikten sonra gerçekten aynı tip dönüşümler haline geliyorlar. Derlenmiş sınıfların bayt koduna bakalım: biri açık bir atama kullanan ve diğeri jenerik kullanan: Java'da Jenerikler: pratikte köşeli parantezler nasıl kullanılır - 2Derlemeden sonra, tüm jenerikler silinir. Buna " tip silme " denir". Tür silme ve jenerikler, JDK'nın eski sürümleriyle geriye dönük uyumlu olacak şekilde tasarlanırken aynı zamanda derleyicinin Java'nın yeni sürümlerinde tür tanımlarına yardımcı olmasına izin verir.

Ham tipler

Jeneriklerden bahsetmişken, her zaman iki kategorimiz vardır: parametreleştirilmiş tipler ve ham tipler. Ham tipler, açılı ayraçlar içinde "tür açıklamasını" atlayan türlerdir: Java'da Jenerikler: pratikte köşeli parantezler nasıl kullanılır - 3Parametreli türler ise bir "açıklama" içerir: Java'da Jenerikler: pratikte köşeli parantezler nasıl kullanılır - 4Gördüğünüz gibi, ekran görüntüsünde bir okla işaretlenmiş alışılmadık bir yapı kullandık. Bu, Java SE 7'ye eklenen özel sözdizimidir. Buna " elmas " denir. Neden? Köşeli ayraçlar bir baklava şekli oluşturur: <> . Elmas sözdiziminin " tür çıkarımı " kavramıyla ilişkili olduğunu da bilmelisiniz . Sonuçta, derleyici, <> görereksağda, değeri atanan değişkenin türünü bulduğu atama operatörünün sol tarafına bakar. Bu kısımda bulduklarına göre sağdaki değerin türünü anlar. Aslında, solda genel bir tür verilir, ancak sağda verilmezse, derleyici türü anlayabilir:

import java.util.*;
public class HelloWorld {
	public static void main(String []args) {
		List<String> list = new ArrayList();
		list.add("Hello, World");
		String data = list.get(0);
		System.out.println(data);
	}
}
Ancak bu, yeni stili jeneriklerle ve onlarsız eski stili karıştırıyor. Ve bu son derece istenmeyen bir durumdur. Yukarıdaki kodu derlerken aşağıdaki mesajı alıyoruz:

Note: HelloWorld.java uses unchecked or unsafe operations
Aslında buraya bir elmas bile eklemenizin nedeni anlaşılmaz görünüyor. Ama işte bir örnek:

import java.util.*;
public class HelloWorld {
	public static void main(String []args) {
		List<String> list = Arrays.asList("Hello", "World");
		List<Integer> data = new ArrayList(list);
		Integer intNumber = data.get(0);
		System.out.println(data);
	}
}
ArrayList'in bir koleksiyonu bağımsız değişken olarak alan ikinci bir kurucusu olduğunu hatırlayacaksınız . Ve burası uğursuz bir şeyin gizlendiği yerdir. Elmas sözdizimi olmadan, derleyici aldatıldığını anlamıyor. Elmas sözdizimi ile yapar. Bu nedenle, Kural 1 şudur: her zaman parametreleştirilmiş türlerle elmas sözdizimini kullanın. Aksi takdirde, ham türleri kullandığımız yerleri kaçırma riskiyle karşı karşıya kalırız. "Denetlenmeyen veya güvenli olmayan işlemleri kullanır" uyarılarını ortadan kaldırmak için, bir yöntem veya sınıf üzerinde @SuppressWarnings("unchecked") ek açıklamasını kullanabiliriz . Ama neden kullanmaya karar verdiğinizi düşünün. Bir numaralı kuralı hatırla. Belki bir tür argümanı eklemeniz gerekir.

Java Genel yöntemleri

Jenerikler, parametre türleri ve dönüş türleri parametreleştirilmiş olan yöntemler oluşturmanıza izin verir. Oracle eğitiminde bu yeteneğe ayrı bir bölüm ayrılmıştır: " Genel Yöntemler ". Bu eğitimde öğretilen sözdizimini hatırlamak önemlidir:
  • açılı ayraçlar içinde tür parametrelerinin bir listesini içerir;
  • tür parametrelerinin listesi, yöntemin dönüş türünden önce gelir.
Bir örneğe bakalım:

import java.util.*;
public class HelloWorld {
	
    public static class Util {
        public static <T> T getValue(Object obj, Class<T> clazz) {
            return (T) obj;
        }
        public static <T> T getValue(Object obj) {
            return (T) obj;
        }
    }

    public static void main(String []args) {
		List list = Arrays.asList("Author", "Book");
		for (Object element : list) {
		    String data = Util.getValue(element, String.class);
		    System.out.println(data);
		    System.out.println(Util.<String>getValue(element));
		}
    }
}
Util sınıfına bakarsanız , onun iki genel yöntemi olduğunu görürsünüz. Tip çıkarımı olasılığı sayesinde, tipi doğrudan derleyiciye belirtebiliriz veya kendimiz belirleyebiliriz. Her iki seçenek de örnekte sunulmuştur. Bu arada, düşünürseniz sözdizimi çok mantıklı. Genel bir yöntem bildirirken, tür parametresini yöntemden ÖNCE belirtiriz, çünkü tür parametresini yöntemden sonra bildirirsek, JVM hangi türün kullanılacağını anlayamaz. Buna göre öncelikle T type parametresini kullanacağımızı beyan ediyoruz ve ardından bu type'ı döndüreceğimizi söylüyoruz. Doğal olarak, Util.<Integer>getValue(element, String.class) bir hatayla başarısız olur:uyumsuz türler: Class<String>, Class<Integer> öğesine dönüştürülemez . Genel yöntemleri kullanırken, tip silmeyi her zaman hatırlamalısınız. Bir örneğe bakalım:

import java.util.*;
public class HelloWorld {
	
    public static class Util {
        public static <T> T getValue(Object obj) {
            return (T) obj;
        }
    }

    public static void main(String []args) {
		List list = Arrays.asList(2, 3);
		for (Object element : list) {
		    System.out.println(Util.<Integer>getValue(element) + 1);
		}
    }
}
Bu gayet iyi çalışacak. Ancak, yalnızca derleyici çağrılan yöntemin dönüş türünün Tamsayı olduğunu anladığı sürece . Konsol çıktı deyimini aşağıdaki satırla değiştirin:

System.out.println(Util.getValue(element) + 1);
Bir hata alıyoruz:

bad operand types for binary operator '+', first type: Object, second type: int.
Başka bir deyişle, tip silme gerçekleşti. Derleyici, kimsenin türü belirtmediğini görür, bu nedenle tür, Nesne olarak belirtilir ve yöntem bir hatayla başarısız olur.

Genel sınıflar

Yalnızca yöntemler parametreleştirilemez. Sınıflar da olabilir. Oracle'ın öğreticisinin "Genel Türler" bölümü buna ayrılmıştır. Bir örnek ele alalım:

public static class SomeType<T> {
	public <E> void test(Collection<E> collection) {
		for (E element : collection) {
			System.out.println(element);
		}
	}
	public void test(List<Integer> collection) {
		for (Integer element : collection) {
			System.out.println(element);
		}
	}
}
Burada her şey basit. Jenerik sınıfı kullanırsak, sınıf adından sonra type parametresi belirtilir. Şimdi ana yöntemde bu sınıfın bir örneğini oluşturalım :

public static void main(String []args) {
	SomeType<String> st = new SomeType<>();
	List<String> list = Arrays.asList("test");
	st.test(list);
}
Bu kod iyi çalışacaktır. Derleyici, List of Numbers ve Collection of Strings olduğunu görür . Peki ya type parametresini ortadan kaldırırsak ve şunu yaparsak:

SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
Bir hata alıyoruz:

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
Yine, bu tip silmedir. Sınıf artık bir tür parametresi kullanmadığından, derleyici bir List geçtiğimiz için List<Integer> yönteminin en uygun olduğuna karar verir. Ve bir hata ile başarısız oluyoruz. Bu nedenle, Kural 2'ye sahibiz: Genel bir sınıfınız varsa, her zaman tür parametrelerini belirtin.

Kısıtlamalar

Jenerik metotlarda ve n sınıflarda belirtilen türleri kısıtlayabiliriz. Örneğin, bir kapsayıcının tür bağımsız değişkeni olarak yalnızca bir Sayı kabul etmesini istediğimizi varsayalım. Bu özellik , Oracle öğreticisinin Sınırlı Tip Parametreleri bölümünde açıklanmıştır . Bir örneğe bakalım:

import java.util.*;
public class HelloWorld {
	
    public static class NumberContainer<T extends Number> {
        private T number;
    
        public NumberContainer(T number) { this.number = number; }
    
        public void print() {
            System.out.println(number);
        }
    }

    public static void main(String []args) {
		NumberContainer number1 = new NumberContainer(2L);
		NumberContainer number2 = new NumberContainer(1);
		NumberContainer number3 = new NumberContainer("f");
    }
}
Gördüğünüz gibi, type parametresini Number sınıfı/arabirimi veya onun soyundan gelenlerle sınırladık . Yalnızca bir sınıf değil, arabirimler de belirtebileceğinizi unutmayın. Örneğin:

public static class NumberContainer<T extends Number & Comparable> {
Jenerikler ayrıca joker karakterleri de destekler Üç türe ayrılırlar: Joker karakter kullanımınız Get-Put ilkesine uygun olmalıdır . Aşağıdaki gibi ifade edilebilir:
  • Bir yapıdan yalnızca değerler alacağınız zaman bir genişletme joker karakteri kullanın.
  • Bir yapıya yalnızca değerler koyduğunuzda süper joker karakter kullanın .
  • Ve bir yapıdan/yapıya hem almak hem de koymak istediğinizde joker karakter kullanmayın.
Bu ilke aynı zamanda Üretici Genişletiyor Tüketici Süper (PECS) ilkesi olarak da adlandırılır. İşte Java'nın Collections.copy yönteminin kaynak kodundan küçük bir örnek: Java'da Jenerikler: pratikte köşeli parantezler nasıl kullanılır - 5Ve işte neyin ÇALIŞMAYACAĞINA dair küçük bir örnek:

public static class TestClass {
	public static void print(List<? extends String> list) {
		list.add("Hello, World!");
		System.out.println(list.get(0));
	}
}

public static void main(String []args) {
	List<String> list = new ArrayList<>();
	TestClass.print(list);
}
Ancak, extensions öğesini super ile değiştirirseniz , her şey yolunda demektir. İçeriğini görüntülemeden önce listeyi bir değerle doldurduğumuz için , bu bir tüketicidir . Buna göre süper kullanıyoruz.

Miras

Jeneriklerin başka bir ilginç özelliği daha vardır: kalıtım. Generics için kalıtımın çalışma şekli, Oracle'ın eğitiminde " Generics, Inheritance, and Subtypes " başlığı altında açıklanmıştır. Önemli olan aşağıdakileri hatırlamak ve tanımaktır. Bunu yapamayız:

List<CharSequence> list1 = new ArrayList<String>();
Çünkü kalıtım jeneriklerle farklı çalışır: Java'da Jenerikler: pratikte köşeli parantezler nasıl kullanılır - 6İşte bir hatayla başarısız olacak başka bir iyi örnek:

List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
Yine, burada her şey basit. List<String>, List<Object> öğesinin alt öğesi değildir , ancak String, Object öğesinin alt öğesidir . Öğrendiklerinizi pekiştirmek için Java Kursumuzdan bir video dersi izlemenizi öneririz.

Çözüm

Bu yüzden jeneriklerle ilgili hafızamızı tazeledik. Yeteneklerinden nadiren tam olarak yararlanırsanız, bazı ayrıntılar bulanıklaşır. Umarım bu kısa inceleme hafızanızı canlandırmanıza yardımcı olmuştur. Daha da iyi sonuçlar için, aşağıdaki materyale aşina olmanızı şiddetle tavsiye ederim:
Yorumlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION