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.

No comments:

Post a Comment