Java 8 Hakkında Bilmeniz Gerekenler
Bu yazıda Java 8 hakkında bilinmesi gereken konulara göz atacağız. Bildiğiniz gibi geçtiğimiz günlerde Java 9 çıktı. Sektörde Java kullanımının bir versiyon geriden geldiğini söylemek yanlış olmaz. Örneğin çalıştığım kurumda(iyzico) uygulamaların %75’ine yakını Java 8 ile geliştiriliyor. Dolayısıyla Java 9’un duyurulmasının, Java 8’e geçişi ve kullanımı arttıracağını söylemek yanlış olmaz.
Java 8 ile Gelen Programlama Dili Yenilikleri
Java 8 ile birlikte hayatımıza giren yenilikleri genel olarak aşağıdaki şekilde listeleyebiliriz;
- Lambda expressions
- Functional interfaces
- Method references
- Stream API
- Optional class
- Concurrency Enhancements
- JDBC Enhancements etc.
Not : Yazı içerisinde her başlıkla ilgili detaylı bilgileri ele almaya çalışacağız ama çok detaylı kod örneklerini paylaşmayacağız. Detaylı kod örneklerini şuradaki Github reposundan bulabilirsiniz.
Lambda expressions
Lambda expressionlar, herhangi bir class’a ait olmadan iş yapabilen fonksiyonlardır. Lambda ile birlikte Java, funtional programming dünyasına da girmiş bulunmaktadır. Bu oldukça önemli bir gelişme, Java’nın ilerde gideceği yol hakkında da ipucu veriyor bizlere. Lambda sayesinde hem daha okunabilir kod üretiyor, hem de kod tekrarından kurtuluyoruz. Bir lambda ifadesini tekrar tekrar kullanabilir, parametre olarak başka bir yere iletebiliriz. Lambda syntax’ına bakalım;
(argument-list) -> {body}
- argument-list : empty yada birden fazla olabilir duruma göre.
- -> : arrow token, parametreler ile body statement’ı linkler.
- {body} : expression yani asıl kodu içeren kısımdır.
fn(a, b) -> { a+b; } gibi düşünebiliriz. Burada dikkat edebileceğiniz husulardan bir tanesi parameter type inference konusudur. Aslında şöyle de yazabiliriz;
fn(int a, int b) -> { a+b; } gibi. Burada dikkat edebileceğimiz bir diğer husus da missing return statemen’dır. Return ifadesi de bazı durumlarda gereksizdir. Aslında şöyle de yazabiliriz;
fn(int a, int b) -> { return a+b; } gibi. Canlı bir örnek ile görmeye çalışalım;
Arrays.asList(“dev”,”not”,”.com”).forEach(item -> System.out.print(item));
Burada ki item -> System.out.print(item) ifadesi bir lambda deyimidir.
Functional interfaces
Tek bir abstract(soyut) methodu bulunan interface’ler için kullanılan tanımdır. Lambda ifadeleri ile sıkı bir ilişki içerisindedir. Ayrıca Single Abstract Method Interfaces (SAM Interfaces) olarak da bilinir. Functional interface’ler default ve static methodlar içerebilir ancak tek bir tane abstract methodu olmalıdır. Bunun nedeni de lambda ifadeleri ile çalışabilmesini sağlamaktır. Örneğin;
@FunctionalInterface // Opsiyonel interface Foo{ int apply(int x, int y); }
Foo addition = (x,y) -> x + y Foo multiplication = (x,y) -> x * y
System.out.println("3 + 5 = " + addition(3,5)); System.out.println("3 * 5 = " + multiplication(3,5));
Lambda ifadelerini parametre olarak da kullanabiliriz.
Method references
Method references da yine lambda ve functional interface domaini ile gelen ve bir arada kullanılabilen özelliklerden biridir. Örneğin;
class MathOperation{ static int add(int x, int y){ return x + y; } public static void main(String[] args) { BiFunction<Integer, Integer, Integer> adder = MathOperation::add; Integer sum = adder.apply(1, 2); System.out.println(sum); } }
Bazen de lambda ifadeleri yerine kullanılabilirler. Örneğin, lambda ifadesinde objenin kendi methodlarından birini kullanıyor isek lambda ifadesi yerine direkt olarak method reference vererek daha kolay yapabiliriz. Şöyle ki, bir Person listesini age field’ına göre sort etmek isteyelim;
Syntax genelde şöyledir;
<ClassName>::methodName; -> static methodlar için.
<ObjectRef>::methodName; -> non-static methodlar için.
Arrays.sort(rosterAsArray, Person::compareByAge);
Stream API
Stream API, Collection’lar üzerinde bazı işlemleri yapmayı kolaylaştıran bir yapıdır. Stream API sayesinde sık kullanılan çeşitli operasyonları yapabilirsiniz. Bunlardan birkaçını şöyle sıralayabiliriz;
- filter (filtreleme)
- forEach (itere etme)
- map (dönüştürme)
- reduce (indirgeme)
- distinct (tekil hale getirme)
- limit (limitleme)
- collect (toplama)
- count (sayma)
- min / max (sıralama ile max-min eleman bulma)
Bir örnek ile şimdiye kadar ki yenilikleri kullanalım;
Arrays.asList("dev","not",".com").stream().forEach(System.out::println);
//dev
//not
//.com
Yukarıda Collection üzerinde stream() çağrısı ile bir stream oluşturduk ve forEach ile itere edip method reference ile de console’a yazdırdık. Burada System.out::println ifadesi method signature olarak forEach’e uygun olduğu için kullanabildik. forEach methodu Consumer arayüzünün bir örneğini bekliyor aslında. Consumer arayüzünde void accept(T t); şeklinde bir method bulunur. Kod bloğumuz buna uygun olduğu için kullanabildik. Liste üzerinde itere edilen her item accept methodunda pass edilir. Method içerisinde de println ile reference ettiğimiz kod çalışır. Diğer methodları kendiniz deneyip görebilirsiniz.
Optional Class
Java 8 ile birlikte gelen özelliklerden biri de bir objenin kullanılmadan önce yapılan null check’lerin daha okunabilir ve kontrol edilebilir olmasını sağlayan Optional yapısıdır. Optional class ile daha safe ve NPE almayan kod yazılabiliyor. Objenizi Optional ile wrap ederek eğer null değilse kullan, null ise başka birşey yap diyebiliyorsunuz. Örneğin;
// burada user objesi null ise exception fırlatılır Optional.of(user);
// burada user objesi null olabilir de olmayabilir de. Herhangi bir exception atmayacaktır Optional.ofNullable(user);
// burada user objesi null değil ise isPresent methodu boolean true dönecektir Optional.ofNullable(user).isPresent();
// burada user objesi null ise default bir user return etmesini sağladık Optional.ofNullable(user).orElseGet(User.EMPTY_USER);
// burada user objesi null ise exception fırlatmasını istedik Optional.ofNullable(user).orElseThrow(throw new UserValidationException("User coult not be null!"));
Concurrency Improvement
Java içerisinde belki de en can sıkıcı konulardan biri concurrency handling kısımlarıdır. Mevcut Java 8 öncesi sürümlerde kullanılan yöntemler JDK 5 ile gelen özellikler. Dolayısıyla mevcut yöntemler oldukça can sıkıcı ve hataya açık. Java 8 ile birlikte yeni Concurrency API geliştirildi ve concurrent/multitasking işlemler anlaşışır hale geldi. Java 8 ile birlikte artık açık olarak Thread nesneleri oluşturmak ve yönetmek zorunda kalmayacaksınız. Eski yöntemle bir örnek yapalım;
Runnable task = () -> {
String threadName = Thread.currentThread().getName();
System.out.println("Hello " + threadName);
};
task.run();
Thread thread = new Thread(task);
thread.start();
System.out.println("Done!");
Runnable bir task oluşturduk, Thread objesi oluşturup task atadık ve thread’i start ettik. Bu eski ve sıkıcı yöntem. Java 8 ile yazalım;
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("Hello " + threadName);
});
// => Hello pool-1-thread-1
Executor’ler Java 8 ile gelen yeniliklerden. Birçok factory method içeriyor, örnekte single bir thread pool içeren bir executor oluşturduk. Kod çıktısı iki kod içinde benzer olacaktır. Ancak Java processi yeni kodda hiçbir zaman durmayacaktır. Yani executor service arka planda çalışmaya ve task alıp işlemeyi beklemektedir. Açık olarak executor servisi stop edebiliriz.
executor.shutdown(); // executor üzerinde bir task var ise onu bitirmesini bekler.
executor.shutdownNow(); // executor üzerinde bir taskın olup olmadığna bakmaksızın stop eder.
executor.awaitTermination(5, TimeUnit.SECONDS); // executor'ın 5 sn sonra kapanmasını sağlar.
Executor örnekleri için yazının başında ki repoyu kullanabilirsiniz. Executor üzerinde task koşturmayı öğrendikten sonra periyodik olarak taskları nasıl tekrar çalıştırırız, belirli gecikmeler ile nasıl çalıştırırız ona bakalım. Java 8 ile gelen ScheduledExecutor bizim için bunu yapacaktır. Örnekleyelim, 3 saniye gecikmeli olarak başlasın;
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Scheduling: " + System.nanoTime());
ScheduledFuture<?> future = executor.schedule(task, 3, TimeUnit.SECONDS);
TimeUnit.MILLISECONDS.sleep(1337);
long remainingDelay = future.getDelay(TimeUnit.MILLISECONDS);
System.out.printf("Remaining Delay: %sms", remainingDelay);
Periyodik olarak tekrar edecek şekilde çalıştıralım;
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Scheduling: " + System.nanoTime());
int initialDelay = 0;
int period = 1;
executor.scheduleAtFixedRate(task, initialDelay, period, TimeUnit.SECONDS);
Not : Java 8 ile birlikte syncronization ve lock yapılarında da değişiklikler mevcut, bunlara da göz atabilirsiniz.
JDBC İyileştirmeleri
Java 8 ile birlikte artık JDBC-ODBC bridge desteklenmiyor. Oracle bu konuda database vendorün sağlayacağı JDBC-ODBC bridge’yi kullanmanızı öneriyor.
JDBCType, SQLType gibi birçok interface eklendi. Bazı güvenlik geliştirmeleri ile birlikte pure JDBC işlemleri Java 8 ile çalışacak hale getirildi.
Yukarıda bahsi geçen yenilikler dışında yine birçok yeni özellikler var Java 8’de. Yazıda geçen ve diğer özelliklerin çoğunu yazının başında belirttiğim repoda bulabilirsiniz. Yazıda bulunmayan ve ilginizi çeken diğer yeni özellikleri repo’ya ekleyebilirsiniz.
2 Comments
misafir
18 Ocak 2018 at 21:37Güzel bir anlatım olmuş, elinize sağlık.
misafir
1 Ağustos 2018 at 16:34ExecutorService java 5 itaberen JDK’da bulunan bir interface
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html
“Since” diye aratırsan görebilirsin. Yazının diğer bölümlerine de şöyle bir göz gezdirdim fakat cılız bir yazı olarak buldum. Derinlemesine bilgi sahibi olunduktan sonra bu tür yazıları yazmakta fayda var.