Sunday, April 21, 2019

Java 12'de switch Kullanımı

Bir değişkenin belirli değerleri için uygulamanın akışını değiştirmek amacıyla switch ifadesinden yararlanıyoruz. switch ifadesi parametre olarak bir değişken alır. Bu değişkenin byte, short, int, char, enum tiplerinden biri olması gerekir. Java 7 ile birlikte bu listeye String sınıfı da eklenmiştir. Geleneksel switch kullanımına ilişkin bir örneğini aşağıda bulabilirsiniz:

int weekDay = 3;  
String status;
switch(weekDay) {
 case 1: 
 case 2: 
 case 3: 
 case 4: 
 case 5: status = "working";
         break;
 case 6: 
 case 7: status = "resting";
  break;
 default: status = "unknown";
}

Burada int tipinden weekDay değişkeninin 1 ile 7 arasındaki değerleri için String tipinden status değişkenine farklı değerler atamak istiyoruz: "working", "resting" ve "unknown". switch'in bu kullanımı ile ilgili iki temel problem bulunuyor:
  • switch değişkeninin (weekDay) bir aralıktaki değerleri için özel bir gösterim bulunmuyor. O nedenle, yukarıda weekDay değişkeninin [1-5] aralığındaki değerlerini karşılaştırmak üzere aralıktaki her bir değer için case tanımlamak zorunda kaldık. 
  • switch değişkeninin değeri, case ile verilen sabitler ile olan eşitliği sırayla sınanır. Eğitlik olduğunda tüm case ifadeleri eşitliğine bakılmaksızın break ile karşılaşıncaya kadar çalıştırılır. switch yapısında break kullanımı tercihe bağlıdır. Unutulmuş olması ya da yerinde kullanılmaması yanlış bir akışa yol açacaktır ve okumayı güçleştirmektedir.
Java 12 ile birlikte yukarıdaki problemleri çözmeyi hedefleyen yeni bir dizi switch gösterimi gelmiştir. Bu yazıda bu gösterimlere bakacağız. İlk gösterimde case sabitlerini virgüllerle ayrılmış bir liste olarak tanımlayabiliyoruz:

int weekDay = 3;  
String status;
switch(weekDay) {
 case 1,2,3,4,5: status = "working";
      break;
 case 6,7: status = "resting";
      break;
 default: status = "unknown";
}

Bu gösterimde break kullanımına dikkat edilmesi gerekiyor. İkinci gösterimde ise break kullanımına ihtiyaç duymuyoruz:

int weekDay = 3;  
String status;
switch (weekDay) {
 case 1, 2, 3, 4, 5 -> {
  status = "working";
 }
 case 6, 7 -> {
  status = "resting";
 }
 default -> {
  status = "unknown";
 }
}

Bu gösterimde lambda ifadesi gösteriminden yararlanıldığını görüyoruz. Eğer amacınız yukarıdaki örneklerde olduğu gibi case değerlerine göre bir değişkenin değerini belirlemek ise bunu daha yalın bir gösterimle yapabilirsiniz:

var weekDay = 3;  
var status = switch (weekDay) {
 case 1, 2, 3, 4, 5 -> "working";
 case 6, 7 -> "resting";
 default -> "unknown";
};

Bu gösterimde lambda ifadesi değişkenin değerini dönüyor. Lambda ifadesinin kullanıldığı yukarıdaki gösterim yerine break ile değeri döndüğümüz gösterimi de kullanabilirsiniz:

var weekDay = 3;  
var status = switch (weekDay) {
 case 1, 2, 3, 4, 5 : break "working";
 case 6, 7 : break "resting";
 default : break "unknown";
};

Yukarıdaki gösterimin bir başka yazılımı aşağıda verilmiştir:

var weekDay = 3;  
var status = switch (weekDay) {
 case 1, 2, 3, 4, 5 -> { break "working"; }
 case 6, 7 -> { break  "resting" ;}
 default -> { break "unknown"; }
};

Bu yazıda Java 12 ile birlikte gelen yeni switch gösteriminin dört farklı yüzünü inceledik. Lambda ifadesi gösteriminin kullanıldığı switch ifadelerinin daha okunabilir olduğu söylenebilir.

Saturday, April 20, 2019

Java 10+'da var Kullanımı


Java 7 ile birlikte derleyicimiz (javac) daha akıllı ve bize üretkenliğimizi arttıracak kolaylıklar sunuyor. Örnek olarak aşağıdaki gibi bir kodu ele alalım:

Map<String,Function<List<String>,String>> functionLookup = 
        new HashMap<String,Function<List<String>,String>>();

functionLookup bir yerel değişken ve tipi Map<String,Function<List<String>,String>> olarak tanımlanmış. Yerel bir değişkeni kullanmadan önce mutlaka ilklendirmelisiniz. Yukarıda bunu new HashMap<String,Function<List<String>,String>>() ataması ile gerçekleştiriyoruz. Ancak kodda soysal (=generic) parametrelerin bolca tekrarı var. Java 7 bu tekrarın önüne geçiyor:

Map<String,Function<List<String>,String>> functionLookup = new HashMap<>();

Java 10 ile gelen yeniliklerden sadece bir tanesi dil ile ilgili: var. var yeni bir anahtar kelime değil, yeni bir tip tanımlıyor. Dolayısı ile var kelimesini bir değişken ismi olarak kullanabilirsiniz! var tipini yerel değişkenleri tanımlarken kullanıyoruz:

var var = 42;

Ama siz yine de var ismini değişken ismi olarak kullanmayın! Java 10 ile birlikte yerel değişkene atama yaparak tanımlıyorsanız değişken isminin sol tarafında tipini söylemenize gerek yok. Derleyecimiz az bir çabayla değişkene yaptığınız atamada kullanılan değerin tipinden otomatik olarak tip bilgisini çıkarabiliyor. Bunu sadece yerel değişkenler için yapabilirsiniz ve mutlaka atama yapmanız gerekir. Yukarıdaki basit örnekte var değişkeninin tipi int olacaktır.

Şimdi aşağıdaki gibi kodu inceleyelim:

public class Exercise {

 public static void main(String[] args) {
  Comparator<String> orderByStringLengthDesc = (x, y) -> y.length() - x.length();
  Comparator<String> orderByStringLengthAsc = orderByStringLengthDesc.reversed();
  Function<List<String>, Optional<String>> longest = list -> list.stream().sorted(orderByStringLengthDesc)
    .findFirst();
  Function<List<String>, Optional<String>> shortest = list -> list.stream().sorted(orderByStringLengthAsc)
    .findFirst();
  List<Function<List<String>, Optional<String>>> funs = List.of(longest, shortest);
  final List<String> names = List.of("Jack Shephard", "Kate Austen", "Ben Linus", "James Ford", "Hugo Reyes",
    "John Locke", "Sayid Jarrah", "Richard Alpert", "Desmond Hume", "Daniel Faraday", "Shannon Rutherford",
    "Danielle Rousseau");
  List<String> processed = funs.stream().map(fr -> fr.apply(names)).filter(Optional::isPresent).map(Optional::get)
    .collect(Collectors.toList());
  processed.forEach(System.out::println);
 }

}

Bu kodda tip tanımının çokça tekrar edilerek yapıldığı yer bulunuyor. Yukarıdaki kodu var kullanarak herhangi bir belirsizliğe yok açmadan yeniden tanımlayabiliriz:

public class SmartCompiler {

 public static void main(String[] args) {
  Comparator<String> orderByStringLengthDesc = (x, y) -> y.length() - x.length();
  var orderByStringLengthAsc = orderByStringLengthDesc.reversed();
  Function<List<String>, Optional<String>> longest = list -> list.stream().sorted(orderByStringLengthDesc)
    .findFirst();
  Function<List<String>, Optional<String>> shortest = list -> list.stream().sorted(orderByStringLengthAsc)
    .findFirst();
  var funs = List.of(longest, shortest);
  final var names = List.of("Jack Shephard", "Kate Austen", "Ben Linus", "James Ford", "Hugo Reyes", "John Locke",
    "Sayid Jarrah", "Richard Alpert", "Desmond Hume", "Daniel Faraday", "Shannon Rutherford",
    "Danielle Rousseau");
  var processed = funs.stream().map(fr -> fr.apply(names)).filter(Optional::isPresent).map(Optional::get)
    .collect(Collectors.toList());
  processed.forEach(System.out::println);
 }

}

Evet, var kullanımı tip tanımlarken yapılan tekrarlardan kurtulmamızı sağladı. Burada var kullanımı ile ilgili dikkat edilmesi gereken birkaç nokta bulunuyor: var ile Lambda ifadesi yazamazsınız. Değişkenin tipini mulaka bir @FunctionalInterface ile ilişkilendirmelisiniz. Yukarıdaki örnekte orderByStringLengthDesc değişkenini var orderByStringLengthDesc = (x, y) -> y.length() - x.length() biçiminde tanımlayamazsınız. Derleyici lambda ifadesinin hangi @FunctionalInterface ile ilişkili olduğunu çıkarsayamaz! Derleyiciden imkansızı istemeyin! Ancak var orderByStringLengthAsc = orderByStringLengthDesc.reversed(); tanımlamasında bir sorun yok. Çünkü bu kez sağ taraftaki lambda ifadesinin hangi @FunctionalInterface ile ilişkili olduğu belirli: Comparator<String>.

var'ın kullanılabildiği yerleri özetleyen bir başka örnek daha inceleyelim:

@Override
public Optional<Country> findOne(String code) {
 try (Connection connection = ds.getConnection();) {
  PreparedStatement st = connection.prepareStatement(SELECT_COUNTRY_BY_CODE);
  st.setString(1, code);
  ResultSet rs = st.executeQuery();
  if (rs.next()) {
   Country country = new Country();
   country.setCode(code);
   country.setName(rs.getString(COLUMN_NAME));
   country.setContinent(rs.getString(COLUMN_CONTINENT));
   country.setPopulation(rs.getInt(COLUMN_POPULATION));
   country.setSurfaceArea(rs.getDouble(COLUMN_SURFACE_AREA));
   return Optional.of(country);
  }
 } catch (Exception e) {
  LOGGER.error(e.getMessage());
 }
 return Optional.empty();
}

var'ı blok içinde yerel değişken tanımlarken, Java 7'de try değişkenini tanımlarken ve for döngü değişkenini tanımlarken kullanabiliriz:

@Override
public Optional<Country> findOne(String code) {
 try (var connection = ds.getConnection();) {
  var st = connection.prepareStatement(SELECT_COUNTRY_BY_CODE);
  st.setString(1, code);
  var rs = st.executeQuery();
  if (rs.next()) {
   var country = new Country();
   country.setCode(code);
   country.setName(rs.getString(COLUMN_NAME));
   country.setContinent(rs.getString(COLUMN_CONTINENT));
   country.setPopulation(rs.getInt(COLUMN_POPULATION));
   country.setSurfaceArea(rs.getDouble(COLUMN_SURFACE_AREA));
   return Optional.of(country);
  }
 } catch (Exception e) {
  LOGGER.error(e.getMessage());
 }
 return Optional.empty();
}

Genel olarak kodun okunabilirliği için değişkenlere isim verirken özenli davranmak gerekir. var ile tanımlanan değişkenler için ise bu pratiğe daha da sıkı sarılmak gerekir:

@Override
public Optional<Country> findOne(String code) {
 try (var connection = ds.getConnection();) {
  var prepStatement = connection.prepareStatement(SELECT_COUNTRY_BY_CODE);
  prepStatement.setString(1, code);
  var resultSet = prepStatement.executeQuery();
  if (resultSet.next()) {
   var country = new Country();
   country.setCode(code);
   country.setName(resultSet.getString(COLUMN_NAME));
   country.setContinent(resultSet.getString(COLUMN_CONTINENT));
   country.setPopulation(resultSet.getInt(COLUMN_POPULATION));
   country.setSurfaceArea(resultSet.getDouble(COLUMN_SURFACE_AREA));
   return Optional.of(country);
  }
 } catch (Exception e) {
  LOGGER.error(e.getMessage());
 }
 return Optional.empty();
}

Ancak var kulanımında her tip çıkarsaması yukarıda verilen örneklerde olduğu kadar açık ve yalın olmayabilir:

var numbers = List.of(4,8,15,16,23);

Yukarıdaki örnekte numbers değişkeninin tipi List<Integer> olacaktır. Şimdi ufak bir değişiklik yapalım:

var numbers = List.of(4,8,15,16,23.);

Dikkatli bakılırsa 23 değerinin 23. olarak değiştirildiği görülebilir. Evet, bir nokta koymak numbers değişkeninin tipini epey değiştirdi: List<Number & Comparable<?>>. Şimdi biraz daha büyük bir değişiklik yapalım:

var numbers = List.of(4,8,15,16.,23,"forty two");

Bu durumda numbers değişkeninin tipi List<Object & Serializable & Comparable<?>> olacaktır.