Switch-case tanımı, if-else ile benzer bir şekilde bir variable’ın değerine göre farklı kod bloklarının çalışmasını sağlar. Java 7’ye kadar switch-case tanımlamalarında sadece integer değerde bir variable’ın koşulları kullanılabiliyorken, Java 8’de String ve Enum type’lar için de kullanılabilir hale geldi.
Java 8’den önce:
1
2
3
4
5
6
7
8
9
10
11
12
| int value = 5;
switch (value) {
case 1:
System.out.println("One");
break;
case 5:
System.out.println("five");
break;
default:
System.out.println("Unknown");
}
|
Java 8 ile birlikte:
String
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| String day = "Tuesday";
switch (day) {
case "Monday":
System.out.println("Week day");
break;
case "Tuesday":
System.out.println("Week day");
break;
case "Wednesday":
System.out.println("Week day");
break;
case "Thursday":
System.out.println("Week day");
break;
case "Friday":
System.out.println("Week day");
break;
case "Saturday":
System.out.println("Weekend");
break;
case "Sunday":
System.out.println("Weekend");
break;
default:
System.out.println("Unknown");
}
|
Enum
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| enum DAYS {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
DAYS days = DAYS.SUNDAY;
switch (days) {
case MONDAY:
System.out.println("Weekdays");
break;
case TUESDAY:
System.out.println("Weekdays");
break;
case WEDNESDAY:
System.out.println("Weekdays");
break;
case THURSDAY:
System.out.println("Weekdays");
break;
case FRIDAY:
System.out.println("Weekdays");
break;
case SATURDAY:
System.out.println("Weekends");
break;
case SUNDAY:
System.out.println("Weekends");
break;
default:
System.out.println("Unknown");
}
|
Java 14 sonrasında switch-case kullanımıyla ilgili bir çok değişiklik geldi. Bu değişiklikler inceleyecek olursak:
1- Switch statemen’ı artık bir switch expression’a dönüştü. Yani assign edilebilen, return edilebilen bir yapıya kavuştu. Fakat return edilecek ifadeden önce yield kullanılması gerekir:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| String type =
switch (day) {
case "Monday":
yield "Weekday";
case "Tuesday":
yield "Weekday";
case "Wednesday":
yield "Weekday";
case "Thursday":
yield "Weekday";
case "Friday":
yield "Weekday";
case "Saturday":
yield "Weekend";
case "Sunday":
yield "Weekend";
default:
yield "Unknown";
};
|
2- case statement’ı arrow operator olarak tanımlanabilir hale geldi. Yield yerine bu kullanım daha okunur olduğu için bu ifadenin tercih edilmesi daha faydalı olabilir:
1
2
3
4
5
6
7
8
9
10
| String type = switch (day) {
case "Monday"-> "Week day";
case "Tuesday"-> "Week day";
case "Wednesday"->"Week day";
case "Thursday"->"Week day";
case "Friday"->"Week day";
case "Saturday"-> "Weekend";
case "Sunday"-> "Weekend";
default->"Unknown";
};
|
3- switch içinde yer alan değerin farklı case’leri için aynı işlem yapılabilmesini sağlamak için tek bir case ifadesine farklı labellar yazılabilir hale geldi:
1
2
3
4
5
| String type = switch (day) {
case "Monday","Tuesday","Wednesday","Thursday","Friday" -> "Week day";
case "Saturday", "Sunday" -> "Weekend";
default->"Unknown";
};
|
Java 17 sonrasında ise, yukarıdaki özelliklere ek olarak aşağıdaki özellikler geldi:
1- Pattern matching: Diyelim ki elimizde bir değer var ve bu değerin tipine göre casting işlemi yaparak dönüştürdüğümüz tipin içerdiği metotları kullanarak belli işlemler yapmamız gerekiyor. Bunun için Java’nın pattern matching özelliğini kullanabiliriz. instanceOf keyword’ü ile bir objenin hangi sınıfa ait olduğunu boolean bir değer döndürerek kontrol edebiliriz. Ve bu sayede elimizdeki daha abstract (soyut) olan bir objeyi casting yöntemi ile belli subclass’lara çevirebiliriz:
1
2
3
4
5
6
7
8
9
10
11
12
| Object o = 5;
double result;
if (o instanceof Integer) {
result = ((Integer) o).doubleValue();
} else if (o instanceof Float) {
result = ((Float) o).doubleValue();
} else if (o instanceof String) {
result = Double.parseDouble(((String) o));
} else {
result = 0d;
}
System.out.println(result); // 5.0
|
- Yukarıdaki örnekte fark edebileceğiniz üzere objenin tipini anlamak ve onu o tipe cast edebilmek için 2 farklı kod satırı yazmamız gerekiyor. Bu da kodun okunmasını ve hataya açık olmasına sebebiyet veriyor. Java 17 ile birlikte case switch-case labellarına daha farklı tipte objeler ekleyebilmeye ve yine label expressionında cast edebilmeye başladık. Bu sayede pattern matching özelliğini okunurluğu daha yüksek ve daha kolay bir şekilde implement edebilmeye başladık:
1
2
3
4
5
6
7
8
| Object o = 5;
Object result = switch (o) {
case Integer i -> i.doubleValue();
case Float f -> f.toString();
case String s -> Double.parseDouble(s);
default -> 0d;
};
System.out.println(result); // 5.0
|
1. Guarded Pattern: Pattern matching işlemini yaparken ayrıca farklı koşullar eklememiz gerektiğinde aşağıdaki gibi case ifadesinin altında if-else ler eklememiz gerekiyor:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| Object o = "10";
Object result = switch (o) {
case Integer i -> i.doubleValue();
case Float f -> f.doubleValue();
case String s -> {
if (s.length() > 0) {
yield Double.parseDouble(s);
} else {
yield 0d;
}
}
default -> 0d;
};
System.out.println(result); //10.0
|
- Artık Guarded Patterns ile birlikte casting’den hemen sonra cast edilen sınıfın özelliklerini kullanan boolean expressionlar yazılabilir ve bu expressionları boolean operandlar ile casting işlemine bağlanabilir:
1
2
3
4
5
6
7
8
9
10
11
| Object o = "10";
Object result = switch (o) {
case Integer i -> i.doubleValue();
case Float f -> f.doubleValue();
case String s && (s.length() > 0 || s.length() < 5) -> {
yield Double.parseDouble(s);
}
default -> 0d;
};
System.out.println(result); //10.0
|
3-Null Cases: Java 17 öncesinde, switch-case’e geçmiş olduğunuz bir değerin asla null olmaması gerekiyordu. Artık Java 17 ile birlikte bu kısıt kaldırıldı ve null olması durumu case labelları ile ele alınabilir hale geldi:
1
2
3
4
5
6
7
8
| Object o = null;
Object result = switch (o) {
case Integer i -> i.doubleValue();
case Float f -> f.doubleValue();
case null -> 0d;
default -> 0d;
};
|
Dikkat Edilmesi Gerekilenler:
- Bir switch-case’de iki label her zaman tüm değerler ile eşleşir: default ve total type pattern. Total type pattern dediğimiz durum, switch’e geçtiğimiz objeden hiyerarşik olarak daha yukarda bir parent class için label bulunuyorsa, bu label her koşulu sağlamış olur. Eğer case labellarını tanımlarken bir total type pattern’in hemen ardından subclass’ına ait bir case eklenirse complier tarafından “this case label is dominated by a preceding case label” şeklinde hata atılır:
1
2
3
4
5
6
| Map o = new HashMap<>();
Object result = switch (o) {
case Map i -> i;
case HashMap i -> i;
}; // Compiler error: this case label is dominated by a preceding case label
|
- Bu durumdan kurtulmak için total type pattern’i bozmamız gerekir. Bunun için guarded pattern özelliğini kullanarak ekstra koşullar ekleyebiliriz (Artık Map label içeren case total matcher olmadığı için default bir case eklememiz gerekir yok compiler hatası olarka ‘switch’ expression does not cover all possible input values hatası alırız):
1
2
3
4
5
6
7
8
9
10
| Map o = new HashMap<>();
o.put(1, "TÜBİTAK");
Object result = switch (o) {
case Map i && i.size() == 0 -> i;
case HashMap i -> i;
default -> throw new IllegalStateException("Unexpected value: " + o);
};
System.out.println(result.getClass()); // class java.util.HashMap
|
- Veya total matcher’ı en sona ekleyebiliriz:
1
2
3
4
5
6
7
| Map o = new HashMap<>();
Object result = switch (o) {
case HashMap i-> i;
case Map i -> i;
};
System.out.println(result.getClass()); // class java.util.HashMap
|
- Bir switch-case’de birden fazla total matcher label’ı bulunamaz:
1
2
3
4
5
6
| HashMap o = new HashMap<>();
Object result = switch (o) {
case Map i -> i;
case HashMap i-> i; // Compiler error: Duplicate total pattern
};
|
- Değer null ise her zaman total matcher ile eşleşir:
1
2
3
4
5
6
7
8
| Map o = null;
Object result = switch (o) {
case HashMap i -> new HashMap<>();
case Map i -> i;
};
System.out.println(result); // null
|
- Total matcher her zaman null matcher’ı ezer. Bu yüzden bu tür bir tanımlama hata atar:
1
2
3
4
5
6
7
| Map o = null;
Object result = switch (o) {
case HashMap i -> new HashMap<>();
case Map i -> i;
case null -> 0; // Label is dominated by a preceding case label 'Map i'
};
System.out.println(result); // null
|
- Her ne kadar switch-case’e null değer geçebiliyor olsak bile, bu değeri her zaman initialize etmiş olmamız gerekir:
1
2
3
4
5
6
7
| Object o;
Object result = switch (o) { // Compiler error: Variable 'o' might not have been initialized
case Integer i -> i.doubleValue();
case Float f -> f.doubleValue();
case null -> 0d;
default -> 0d;
};
|
Referanslar: