Java Heap Pollution - Varargs Heap Pollution Nedir? Örneklerle Heap Pollution

1 - Giriş

Java programlama dilinde yığın kirliliği (heap pollution), parametreli tipte bir değişkenin, parametreli tipte olmayan bir nesneye referans (refers) bulunması durumunda ortaya çıkan bir durumdur.

Bu durum normalde derleme sırasında algılanır ve kontrol edilmemiş bir uyarı (unchecked warning) ile belirtilir.  Yığın kirliliği, çalışma zamanında (runtime) genellikle ClassCastException'a neden olur.

Kısacası, Heap pollution heap memory alanındaki yanlış/uygunsuz veri anlamına gelir.

2- Heap Pollution Örneği

Heap pollution için birkaç kod örnekleri verelim;

    public static void main(String[] args) {
        List list = new ArrayList<Integer>();
        List<String> listOfString = list;     // unchecked warning

        list.add("String Value");   // index 0
        list.add(1);                // index 1


        String str = listOfString.get(0);     // "String Value"
        String str2 = listOfString.get(1);    // INT (1) - ClassCastException
    }
  • Parametresiz olarak list dizi değişkenini tanımladık.
  • Sonrasında, list değişkeninin referans değerini listOfString dizi değişkenine atadık.
  • list değişkenine String değer (String Value) ve Integer (1) değer ekledik.
  • Daha önceden atamış olduğumuzlistOfString dizisinden;
    0. index değeri olan String value değerini alabildik.
    Heap pollution'a uğramış 1. index değeri olan değeri almak istediğimiz zaman ClassCastException hatası almış olduk.

2.1 - Heap Pollution Dikkat Edilmesi Gereken Bazı Noktalar

  • Generic'lerde parametreli olarak belirli tip kullanımıyla Heap Pollution önüne geçilebilir.
    List list = new ArrayList<Integer>(); //Unsafe
    
    List<Integer> list = new ArrayList<Integer>(); //Safe

  • Tür dönüşümlerinde veya atamalarında dikkat edilmeli.

3 -Varargs - Heap Pollution

Varargs parametresi kullandığımız zaman, potansiyel olarak heap pollution durumuna düşmemiz mümkündür.

VarArgs ile ilgili yazıyı okumak ve birçok örneğe bakmak için ->

Kısacası; Varargs'ı her kullandığımızda, Java derleyicisi verilen parametreleri tutmak için bir dizi oluşturur

3.1 - Heap Pollution VarArgs Örneği

heapPollutionMethod(List... stringLists) metodunu tanımlayalım.

    public static void heapPollutionMethod(List<String>... stringLists) {
        List<Integer> intList = Arrays.asList(10, 20);
        Object[] objectArray = stringLists;
        objectArray[2] = intList; // Heap pollution
        String str = stringLists[0].get(0);  // STRING "A"
        String str2 = stringLists[1].get(1); // STRING "E"
        String str3 = stringLists[2].get(1); // ClassCastException - (20) INT
    }

Main metottan şu şekilde çağıralım;

    public static void main(String[] args) {
        List<String> stringList1 = Arrays.asList("A", "B", "C");
        List<String> stringList2 = Arrays.asList("D", "E", "F");
        List<String> stringList3 = Arrays.asList("G", "H");

        heapPollutionMethod(stringList1, stringList2, stringList3);
    }
  • stringList1, stringList2, stringList3 String dizilerini belli değerlerle oluşturduk heapPollutionMethod'una argümanlar olarak göndermiş olduk.
  • (List... stringList) varargs parametresi, String tipindeki dizilerin referans değerlerini tutmaktadır.
  • 10 ve 20 int değerleriyle, List<Integer> intList olarak dizi tanımlaması yaptık.
  • stringLists referans değerlerini Object[] objectArray dizisine atamış olduk.
  • objectArray[2] = intList; ile oluşturmuş olduğumuz int dizisinin referans değerini objectArray[2] indexine atamış olduk. Heap Pollution burada meydana gelmektedir.
  • stringLists[0].get(0); ile "A" değerini, stringLists[1].get(1); ile "E" değerini String tipteki değişkenlere atamış olduk.
  • String str3 = stringLists[2].get(1); satırında, Integer tipteki değişkeni String tipteki değişkene atamak istediğimiz zaman "ClassCastException" Hatası alınmaktadır.

Dikkat edilmesi gereken birkaç durum vardır; (Etkili Java Kitabından Alıntı)

Bir hatırlatma olarak, genel bir varargs yöntemi aşağıdaki durumlarda güvenlidir:

  • Varargs parametre dizisinde hiçbir şey saklamamak/eklememek.
  • Diziyi (veya bir klonu) güvenilmeyen koda görünür yapmamak.

Bu iki koşul karşılandığında,  compiler'daki uyarıyı kaldırmak için @SafeVarargs  anotasyonu kullanılabilir ve yöntemlerinizin güvenli olduğunu gösterin.


varargs parametresinde Generic tiplerin  kullanıldığı metotlarda, varargs parametresine değer eklemek güvenli değildir.

3.2 - SafeVarArgs Anotasyonu - @SafeVarArgs

Anotasyonu heap pollutionu engellemez. Compiler tarafından bu uyarının verilmemesi için ilgili method işaretlenir.

// Olası Heap Pollution parametreleştirilmiş vararg tipi 
public static void method(List<Integer>... list) {
		//kodlar
	}
	@SafeVarargs // Compiler artık uyarı vermeyecektir
	public static void method(List<Integer>... list) {
		//kodlar
	}

Son bir örnekle bu konunun daha iyi anlaşılmasını sağlayalım

3.3 - Varargs Heap Pollution Örneği - 2

Generic Metotlarda VarArgs Kullanımı (Hatasız)

<T> T[] toArray(T... arguments) olarak bir metod ve değerleri ekrana yazdırabilmek için displayData metotlarını tanımlayalım.

    public static <T> T[] toArray(T... values) {
        System.out.println("Class type of values is: " + values.getClass());
        System.out.println("Class type of first value is: " + values[0].getClass());
        System.out.println("Class type of second value is: " + values[1].getClass());
        return values;
    }


    public static <T> void displayData(T... values) {
        System.out.println("[T] Number of arguments - [values.length]: " + values.length);
        for (T value : values) {
            System.out.println(value + " ");
        }
    }

Main metot'dan

    public static void main(String[] args) {
        String[] list = toArray("String Value 1", "String Value 2");
        displayData(list);
    }

Çıktı;

Class type of values is: class [Ljava.lang.String;
Class type of first value is: class java.lang.String
Class type of second value is: class java.lang.String
[T] Number of arguments - [values.length]: 2
String Value 1 
String Value 2 
  • toArray("String Value 1", "String Value 2"); metod çağrımıyla, iki değer argüman olarak gönderilmiş oldu. Varargs parametresi olduğu için de temelde bir diziye eklenmiş oldu.
  • Çıktıda görebileceğimiz üzere Varargs parameteresi olan values dizi değişkeninin sınıf tipi String'dir. Class type of values is: class [Ljava.lang.String;
  • Gönderilen iki değerin de String tipinde olduğunu konsola yazdırmış olduk.
  • values dizi tipinin String olmasından dolayı String[] list değerine sorunsuzca atanmış oldu. (No ClassCastException)
  • Daha sonrasında displayData(list); metot çağrımıyla bu değerleri de yazdırmış olduk.

Generic Metotlarda VarArgs Kullanımı (Hatalı - ClassCastException)

combineTwoAndReturnAsArray(T value1, T value2) ve <T> T[] toArray(T... values) metotlarını tanımlayalım.

    public static <T> T[] combineTwoAndReturnAsArray(T value1, T value2) {
        System.out.println("[combineTwoAndReturnAsArray] - Class type of value1 is: " + value1.getClass());
        System.out.println("[combineTwoAndReturnAsArray] - Class type of value2 is: " + value2.getClass());
        System.out.println();
        return toArray(value1, value2);
    }

    public static <T> T[] toArray(T... values) {
        System.out.println("[toArray] - Class type of values is: " + values.getClass());
        System.out.println("[toArray] - Class type of first value is: " + values[0].getClass());
        System.out.println("[toArray] - Class type of second value is: " + values[1].getClass());
        displayData(values);
        return values;
    }

    public static void displayData(String... values) {
        System.out.println("[STRING] Number of arguments - [values.length]: " + values.length);
        for (String value : values) {
            System.out.println(value + " ");
        }
    }
    public static <T> void displayData(T... values) {
        System.out.println("[T] Number of arguments - [values.length]: " + values.length);
        for (T value : values) {
            System.out.println(value + " ");
        }
    }

Main metod;

    public static void main(String[] args) {
        String[] list = combineTwoAndReturnAsArray("String Value 1", "String Value 2");
        displayData(list);
    }

Çıktı:

[combineTwoAndReturnAsArray] - Class type of value1 is: class java.lang.String
[combineTwoAndReturnAsArray] - Class type of value2 is: class java.lang.String

[toArray] - Class type of values is: class [Ljava.lang.Object;
[toArray] - Class type of first value is: class java.lang.String
[toArray] - Class type of second value is: class java.lang.String
[T] Number of arguments - [values.length]: 2
String Value 1 
String Value 2 
Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.String; ([Ljava.lang.Object; and [Ljava.lang.String; are in module java.base of loader 'bootstrap')
  1. combineTwoAndReturnAsArray("String Value 1", "String Value 2"); çağrımıyla, iki argüman gönderilmiş oldu.
  2. combineTwoAndReturnAsArray metodunda gönderilen değerlerin tipini yazdırmış olduk.
    combineTwoAndReturnAsArray] Class type of value1 is: class java.lang.String
    [combineTwoAndReturnAsArray] Class type of value2 is: class java.lang.String
  3. Bu değerler daha sonrasında toArray(T... values) metoduna gönderilmiş oldu.
  4. toArray metodu içinde, değerlerin tiplerini yazdırmak istediğimiz zaman;
    - [toArray] - Class type of values is: class [Ljava.lang.Object;
    - [toArray] - Class type of first value is: class java.lang.String
    - [toArray] - Class type of second value is: class java.lang.String

    - T... values metot parametresinin sınıf tipi Object tipinde olduğu görülebilir.
    - İçerdiği değeler ise String tiptedir.
    - Burada dizi olarak varargs parametresinin T[] türünde olması beklenir. Ancak bu bir T örneği değil, çalışma zamanında Object örneğidir.
  5. displayData(values) çağrımıyla, ilgili değerleri başarılı bir şekilde yazdırabildik.
  6. toArray metodunda return values; ile dönüş tipi Object sınıfından olduğu için, String[] list = combineTwoAndReturnAsArray("String Value 1", "String Value 2"); dönüşünde ClassCastException hatası alınacaktır. ->
    class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.String; ([Ljava.lang.Object; and [Ljava.lang.String;

Not: Java'da Generic tip parametresi türünde dizi oluşturmak imkansızdır new T[] { ... }. Bu yüzden çalışma anında, gönderilen T generic tipindeki değerlerin tutulabilmesi için java tarafından Object dizisi oluşturulmaktadır. Bu yüzden dolayı oluşturulmuş olan bu dizinin sınıf tipi soruna yol açabilmektedir (ClassCastException).

Dolayısıyla son iki örnek, "genel bir varargs parametre dizisine başka bir yönteme erişim vermenin güvenli olmadığını" gösteriyor.

3.4 - Varargs Heap Pollution - Dikkat Edilmesi Gereken Noktalar

VarArgs Heap pollution durumunu engellemek için dikkat edilmesi gerekilen birkaç nokta var;

  • Varargs parametresini generic tiplerle kullanmamak.
  • Object dizisini tipteki generic tipteki dizilere dönüştürmemek.
  • VarArgs parametresini (metodunu) kontrolümüzde olmayan başka metotların erişimine açmamak

4- Sonuç

Sonuç olarak, heap pollution ne olduğunu incelemiş olduk. Varargs kullanımında potansiyel heap pollution olasığını inceledik. Örneklerle göstermiş olduk.

Kaynaklar

https://en.wikipedia.org/wiki/Heap_pollution
https://docs.oracle.com/javase/tutorial/java/generics/nonReifiableVarargsType.html#heap_pollution