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ğumuz
listOfString
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ğeriniobjectArray[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')
combineTwoAndReturnAsArray("String Value 1", "String Value 2");
çağrımıyla, iki argüman gönderilmiş oldu.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- Bu değerler daha sonrasında
toArray(T... values)
metoduna gönderilmiş oldu. - 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 tipiObject
tipinde olduğu görülebilir.
- İçerdiği değeler iseString
tiptedir.
- Burada dizi olarak varargs parametresininT[]
türünde olması beklenir. Ancak bu bir T örneği değil, çalışma zamanındaObject
örneğidir. displayData(values)
çağrımıyla, ilgili değerleri başarılı bir şekilde yazdırabildik.toArray
metodundareturn values;
ile dönüş tipiObject
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