CodeGym /Java Blogu /Rastgele /Java'daki jenerikler
John Squirrels
Seviye
San Francisco

Java'daki jenerikler

grupta yayınlandı
MERHABA! Java Generics hakkında konuşacağız. Çok şey öğreneceğinizi söylemeliyim! Sadece bu ders değil, sonraki birkaç ders de jeneriklere ayrılacak. Dolayısıyla, jenerik ilaçlarla ilgileniyorsanız, bugün şanslısınız: jenerik ilaçların özellikleri hakkında çok şey öğreneceksiniz. Ve değilse, istifa et ve rahatla! :) Bu çok önemli bir konu ve bunu bilmeniz gerekiyor. Basit olanla başlayalım: "ne" ve "neden".

Java Jenerikleri nelerdir?

Jenerikler, parametresi olan türlerdir. Genel bir tür oluştururken, yalnızca bir türü değil, çalışacağı veri türünü de belirtirsiniz. Sanırım en bariz örnek aklınıza geldi bile: ArrayList! Bir programda genellikle şu şekilde bir tane oluştururuz:

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<String> myList1 = new ArrayList<>();
       myList1.add("Test String 1");
       myList1.add("Test String 2");
   }
}
Tahmin edebileceğiniz gibi, bu listenin bir özelliği de içine her şeyi sığdıramamamızdır: yalnızca String nesneleri ile çalışır. Şimdi Java'nın geçmişine biraz değinelim ve "neden?" sorusunu cevaplamaya çalışalım. Bunu yapmak için ArrayList sınıfının kendi basitleştirilmiş versiyonunu yazacağız. Listemiz yalnızca dahili bir diziye nasıl veri ekleneceğini ve bu diziden nasıl veri alınacağını bilir:

public class MyListClass {

   private Object[] data;
   private int count;

   public MyListClass() {
       this.data = new Object[10];
       this.count = 0;
   }

   public void add(Object o) {
       this.data[count] = o;
       count++;
   }

   public Object[] getData() {
       return data;
   }
}
Diyelim ki listemizin yalnızca Tamsayıları depolamasını istiyoruz . Genel bir tür kullanmıyoruz. add() yöntemine açık bir "instanceof Integer " denetimi eklemek istemiyoruz . Yapsaydık, tüm sınıfımız yalnızca Integer için uygun olurdu ve dünyadaki diğer tüm veri türleri için benzer bir sınıf yazmamız gerekirdi! Programcılarımıza güveneceğiz ve bizim istemediğimiz hiçbir şeyi eklemediklerinden emin olmak için koda bir yorum bırakacağız:

// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
Programcılardan biri bu yorumu gözden kaçırdı ve yanlışlıkla bir sayı listesine birkaç Dize koydu ve ardından toplamlarını hesapladı:

public class Main {

   public static void main(String[] args) {

       MyListClass list = new MyListClass();
       list.add(100);
       list.add(200);
       list.add("Lolkek");
       list.add("Shalala");

       Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
       System.out.println(sum1);

       Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
       System.out.println(sum2);
   }
}
Konsol çıktısı:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
      at Main.main (Main.java:14)
Bu durumun en kötü yanı ne? Kesinlikle programcının dikkatsizliği değil. En kötüsü de yanlış kodun programımızda önemli bir yerde kalması ve başarılı bir şekilde derlenmesi. Artık hatayla kod yazarken değil, yalnızca test sırasında karşılaşacağız (ve bu en iyi senaryo!). Geliştirmenin sonraki aşamalarında hataları düzeltmek, hem para hem de zaman açısından çok daha pahalıya mal olur. Bu tam olarak jeneriklerin bize fayda sağladığı yerdir: jenerik bir sınıf, şanssız programcının hatayı hemen algılamasını sağlar. Program basitçe derlenmeyecek!

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Integer> myList1 = new ArrayList<>();
      
       myList1.add(100);
       myList1.add(100);
       myList1.add ("Lolkek"); // Error!
       myList1.add("Shalala"); // Error!
   }
}
Programcı hatasını hemen anlar ve anında düzelir. Bu arada, bu tür bir hatayı görmek için kendi List sınıfımızı oluşturmamıza gerek yoktu . Köşeli parantezleri kaldırın ve sıradan bir ArrayList'ten ( <Integer> ) yazın!

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add(100);
      list.add(200);
      list.add("Lolkek");
      list.add("Shalala");

       System.out.println((Integer) list.get(0) + (Integer) list.get(1));
       System.out.println((Integer) list.get(2) + (Integer) list.get(3));
   }
}
Konsol çıktısı:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
     at Main.main(Main.java:16)
Başka bir deyişle, Java'nın "yerel" mekanizmalarını kullanırken bile bu tür bir hata yapabilir ve güvenli olmayan bir koleksiyon oluşturabiliriz. Ancak, bu kodu bir IDE'ye yapıştırırsak bir uyarı alırız: "Unchecked call to add(E) as a Member of raw type of java.util.List" Bir öğe eklerken bir şeylerin ters gidebileceği söylendi. genel bir türü olmayan bir koleksiyona. Peki "ham tip" ifadesi ne anlama geliyor? Ham tür, türü kaldırılmış genel bir sınıftır. Başka bir deyişle, List myList1 ham bir türdür . Ham türün tersi, genel bir türdür — parametreleştirilmiş tür(ler) in bir göstergesi olan genel bir sınıf . Örneğin, List<String> myList1. Dilin ham türlerin kullanımına neden izin verdiğini sorabilirsiniz . Nedeni basit. Java'nın yaratıcıları, uyumluluk sorunları yaratmamak için dildeki ham türler için desteği bıraktı. Java 5.0 piyasaya sürüldüğünde (jenerikler ilk olarak bu sürümde ortaya çıktı), ham türler kullanılarak zaten birçok kod yazılmıştı . Sonuç olarak, bu mekanizma bugün hala desteklenmektedir. Derslerde Joshua Bloch'un klasikleşmiş kitabı "Etkin Java"dan defalarca bahsettik. Dilin yaratıcılarından biri olarak, kitabında ham türleri ve türsel türleri atlamadı .Java'daki jenerikler nelerdir?  - 2Kitabın 23. bölümünün çok güzel bir başlığı var: "Yeni kodda ham türleri kullanmayın" Hatırlamanız gereken şey bu. Genel sınıfları kullanırken, genel bir türü asla ham bir türe dönüştürmeyin .

genel yöntemler

Java, sözde genel yöntemler oluşturarak bireysel yöntemleri parametreleştirmenize izin verir. Bu tür yöntemler nasıl yardımcı olur? Her şeyden önce, farklı türde yöntem parametreleriyle çalışmanıza izin vermeleri açısından faydalıdırlar. Aynı mantık farklı türlere güvenle uygulanabiliyorsa, genel bir yöntem harika bir çözüm olabilir. Bunu çok basit bir örnek olarak düşünün: Diyelim ki myList1 adlı bir listemiz var . Listeden tüm değerleri kaldırmak ve tüm boş alanları yeni değerlerle doldurmak istiyoruz. Genel bir yöntemle sınıfımız şöyle görünür:

public class TestClass {

   public static <T> void fill(List<T> list, T val) {
       for (int i = 0; i < list.size(); i++)
           list.set(i, val);
   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       strings.add("Old String 1");
       strings.add("Old String 2");
       strings.add("Old String 3");

       fill(strings, "New String");

       System.out.println(strings);

       List<Integer> numbers = new ArrayList<>();
       numbers.add(1);
       numbers.add(2);
       numbers.add(3);

       fill(numbers, 888);
       System.out.println(numbers);
   }
}
Söz dizimine dikkat edin. Biraz sıradışı görünüyor:

public static <T> void fill(List<T> list, T val)
Dönüş türünden önce <T> yazarız. Bu, genel bir yöntemle uğraştığımızı gösterir. Bu durumda, yöntem 2 parametreyi girdi olarak kabul eder: T nesnelerinin bir listesi ve başka bir ayrı T nesnesi. <T> kullanarak, yöntemin parametre türlerini parametrelendiriyoruz: Dizeler ve bir Tamsayı listesinden geçemiyoruz. Dizeler ve bir Dize listesi, Tamsayılar ve bir Tamsayı listesi, kendi Cat nesnelerimizin bir listesi ve başka bir Cat nesnesi — yapmamız gereken bu. main () yöntemi, fill() yönteminin farklı veri türleri ile çalışmak için nasıl kolayca kullanılabileceğini gösterir. İlk olarak, yöntemi bir String listesi ve girdi olarak bir String ile ve ardından bir Tamsayılar listesi ve bir Tamsayı ile kullanıyoruz. Konsol çıktısı:

[New String, New String, New String] [888, 888, 888]
Genel yöntemlerimiz olmadığını ve 30 farklı sınıf için fill() yönteminin mantığına ihtiyacımız olduğunu hayal edin. Farklı veri türleri için aynı yöntemi 30 kez yazmamız gerekir! Ancak genel yöntemler sayesinde kodumuzu yeniden kullanabiliriz! :)

Genel sınıflar

Standart Java kitaplıklarında sağlanan genel sınıflarla sınırlı değilsiniz — kendinizinkini oluşturabilirsiniz! İşte basit bir örnek:

public class Box<T> {

   private T t;

   public void set(T t) {
       this.t = t;
   }

   public T get() {
       return t;
   }

   public static void main(String[] args) {

       Box<String> stringBox = new Box<>();

       stringBox.set("Old String");
       System.out.println(stringBox.get());
       stringBox.set("New String");

       System.out.println(stringBox.get());
      
       stringBox.set(12345); // Compilation error!
   }
}
Box<T> sınıfımız genel bir sınıftır. Oluşturma sırasında bir veri türü ( <T> ) atadığımızda , artık bunun içine başka türden nesneler yerleştiremiyoruz. Bu örnekte görülebilir. Nesnemizi oluştururken Strings ile çalışacağını belirtmiştik:

Box<String> stringBox = new Box<>();
Ve son kod satırında kutunun içine 12345 sayısını girmeye çalıştığımızda derleme hatası alıyoruz! Bu kadar kolay! Kendi jenerik sınıfımızı oluşturduk! :) Bununla birlikte bugünkü dersimiz de sona eriyor. Ama jeneriklere veda etmiyoruz! Sonraki derslerde, daha gelişmiş özelliklerden bahsedeceğiz, o yüzden sakın gitmeyin! ) Öğrendiklerinizi pekiştirmek için Java Kursumuzdan bir video dersi izlemenizi öneririz.
Çalışmalarınızda başarılar! :)
Yorumlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION