Java 8 Özellikleri ve Örneklerle Detaylı Anlatım
Java platformu, 2014 yılında yayınlanan Java 8 ile önemli bir dönüm noktasına ulaştı. Bu sürüm, dilin evriminde büyük bir sıçramayı temsil ederek, özellikle fonksiyonel programlama paradigmalarını Java dünyasına entegre etti. Geliştiricilerin kod yazma şeklini kökten değiştiren Java 8, daha temiz, daha özlü ve daha verimli kodlar yazma imkanı sundu. Bu makalede, Java 8’in getirdiği temel özellikleri, detaylı açıklamalar ve pratik örneklerle ele alacağız.
1. Lambda İfadeleri (Lambda Expressions)
Java 8’in en çığır açıcı özelliklerinden biri olan Lambda İfadeleri, fonksiyonel programlamanın temel taşlarından biridir. Tek bir soyut metodu olan arayüzlerin (yani fonksiyonel arayüzlerin) anonim olarak implementasyonunu sağlarlar. Bu sayede, daha az kodla daha işlevsel yapılar oluşturmak mümkün hale gelir.
Lambda İfadelerinin Yapısı ve Avantajları
Bir lambda ifadesi temelde üç bölümden oluşur:
1. Parametre Listesi: Metodun argümanları. Parantez içinde belirtilirler (örneğin, (a, b) veya (String s)). Parametre yoksa boş parantez () kullanılır. Tek bir parametre varsa ve tipi açıkça belirtilmiyorsa parantez kullanılmayabilir (örneğin, s ->).
2. Ok İşareti: Parametre listesi ile gövdeyi ayıran -> sembolü.
3. Gövde: Lambda ifadesinin çalıştıracağı kod bloğu. Tek bir ifade ise süslü parantez {} isteğe bağlıdır. Birden fazla ifade ise süslü parantezler zorunludur.
Avantajları:
* Daha Az Kod: Anonim iç sınıflara göre çok daha az kod yazmayı sağlar.
* Okunabilirlik: Kodun amacını daha net bir şekilde ifade eder.
* Fonksiyonel Programlama: Java’da fonksiyonel programlama stilini benimsemeyi kolaylaştırır.
Örnek: Runnable arayüzünü lambda ile kullanma
import java.util.Comparator;
public class LambdaOrnek {
public static void main(String[] args) {
// Geleneksel yöntemle Runnable kullanımı (anonim iç sınıf)
Runnable eskiRunnable = new Runnable() {
@Override
public void run() {
System.out.println("Eski yöntemle çalışan Runnable.");
}
};
new Thread(eskiRunnable).start();
// Lambda ifadesiyle Runnable kullanımı
Runnable yeniRunnable = () -> System.out.println("Lambda ile çalışan Runnable.");
new Thread(yeniRunnable).start();
// Comparator arayüzünü lambda ile kullanma
Comparator integerKarsilastirici = (a, b) -> a.compareTo(b);
System.out.println("10 ve 5 karşılaştırması: " + integerKarsilastirici.compare(10, 5)); // Pozitif değer döner
System.out.println("5 ve 10 karşılaştırması: " + integerKarsilastirici.compare(5, 10)); // Negatif değer döner
}
}
Bu örnekte, Runnable ve Comparator gibi fonksiyonel arayüzlerin lambda ifadeleriyle ne kadar daha sade bir şekilde yazılabileceği gösterilmiştir.
2. Fonksiyonel Arayüzler (Functional Interfaces)
Lambda ifadelerinin temelini oluşturan yapılar fonksiyonel arayüzlerdir. Bir arayüzün fonksiyonel arayüz olabilmesi için yalnızca bir tane soyut metodu (Single Abstract Method – SAM) olması gerekir. Java 8 ile birlikte java.util.function paketi altında birçok hazır fonksiyonel arayüz sunulmuştur. Bu arayüzler, yaygın fonksiyonel programlama kalıplarını desteklemek için tasarlanmıştır.
Hazır Fonksiyonel Arayüzler
* Predicate: Bir T tipindeki nesneyi alır ve boolean bir değer döndürür. Koşulları kontrol etmek için kullanılır.
* boolean test(T t);
* Consumer: Bir T tipindeki nesneyi alır ve bir işlem yapar, herhangi bir değer döndürmez.
* void accept(T t);
* Function: Bir T tipindeki nesneyi alır ve onu R tipindeki bir nesneye dönüştürür.
* R apply(T t);
* Supplier: Hiçbir argüman almaz ve T tipinde bir değer döndürür. Nesne üretmek için kullanılır.
* T get();
* UnaryOperator: Function‘nin özel bir durumudur; aynı tipte bir argüman alır ve aynı tipte bir değer döndürür.
* T apply(T t);
* BinaryOperator: BiFunction‘nin özel bir durumudur; aynı tipte iki argüman alır ve aynı tipte bir değer döndürür.
* T apply(T t1, T t2);
Örnek: Çeşitli fonksiyonel arayüzlerin kullanımı
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class FunctionalInterfaceOrnek {
public static void main(String[] args) {
// Predicate örneği: Bir sayının çift olup olmadığını kontrol etme
Predicate ciftSayiMi = sayi -> sayi % 2 == 0;
System.out.println("10 çift sayı mı? " + ciftSayiMi.test(10)); // true
System.out.println("7 çift sayı mı? " + ciftSayiMi.test(7)); // false
// Consumer örneği: Bir mesajı ekrana yazdırma
Consumer mesajYazdir = mesaj -> System.out.println("Mesaj: " + mesaj);
mesajYazdir.accept("Merhaba Java 8!");
// Function örneği: Bir String'i büyük harfe dönüştürme
Function buyukHarfeCevir = metin -> metin.toUpperCase();
System.out.println("Büyük harf: " + buyukHarfeCevir.apply("java 8")); // JAVA 8
// Supplier örneği: Rastgele bir sayı üretme
Supplier rastgeleSayiUret = () -> Math.random();
System.out.println("Rastgele sayı: " + rastgeleSayiUret.get());
}
}
3. Stream API
Java 8 Stream API, koleksiyonlar üzerinde fonksiyonel stil operasyonları gerçekleştirmek için güçlü ve esnek bir yol sunar. Koleksiyonlardaki verileri işlemek için deklaratif (ne yapılacağını belirten) bir yaklaşım sağlar, imperatif (nasıl yapılacağını belirten) yaklaşıma kıyasla daha okunabilir ve paralel işlemeye daha uygun kodlar yazmaya olanak tanır.
Stream API’nin Temel Bileşenleri
Bir Stream pipeline’ı genellikle şu adımlardan oluşur:
1. Kaynak (Source): Bir koleksiyon, dizi, I/O kanalı veya bir üretici fonksiyonu olabilir.
2. Ara İşlemler (Intermediate Operations): Bir stream’i başka bir stream’e dönüştüren işlemlerdir. Zincirlenebilirler (lazy evaluation) ve terminal bir işlem çağrılana kadar çalışmazlar. Örnekler: filter(), map(), distinct(), sorted(), peek(), limit(), skip().
3. Terminal İşlem (Terminal Operation): Stream’i sonlandıran ve bir sonuç üreten işlemlerdir. Stream’i tüketirler ve bir daha kullanılamaz hale getirirler. Örnekler: forEach(), collect(), reduce(), count(), min(), max(), anyMatch(), allMatch(), noneMatch(), findFirst(), findAny().
Örnek: Stream API ile veri işleme
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamApiOrnek {
public static void main(String[] args) {
List kelimeler = Arrays.asList("elma", "armut", "kiraz", "üzüm", "nar", "karpuz");
// 1. 'a' harfi içeren kelimeleri filtrele
// 2. Bu kelimeleri büyük harfe dönüştür
// 3. Sırala
// 4. Yeni bir List olarak topla
List islenmisKelimeler = kelimeler.stream()
.filter(kelime -> kelime.contains("a")) // Ara işlem: 'a' içerenleri filtrele
.map(String::toUpperCase) // Ara işlem: Büyük harfe çevir (Metot Referansı)
.sorted() // Ara işlem: Alfabetik sırala
.collect(Collectors.toList()); // Terminal işlem: Sonuçları listeye topla
System.out.println("İşlenmiş kelimeler: " + islenmisKelimeler);
// Çıktı: [ARMUT, ELMA, KARPUZ, NAR]
// Sayı listesi üzerinde çeşitli işlemler
List sayilar = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Çift sayıların karelerinin toplamını bulma
int toplamKare = sayilar.stream()
.filter(s -> s % 2 == 0) // Sadece çift sayıları filtrele
.map(s -> s * s) // Karelerini al
.reduce(0, Integer::sum); // Başlangıç değeri 0, toplama işlemi (Metot Referansı)
System.out.println("Çift sayıların karelerinin toplamı: " + toplamKare); // Çıktı: 220 (4+16+36+64+100)
// İlk 3 tek sayıyı bul ve ekrana yazdır
sayilar.stream()
.filter(s -> s % 2 != 0) // Tek sayıları filtrele
.limit(3) // İlk 3 tanesini al
.forEach(System.out::println); // Her birini ekrana yazdır (Metot Referansı)
// Çıktı:
// 1
// 3
// 5
// Paralel Stream kullanımı (büyük veri setleri için faydalıdır)
long baslangic = System.nanoTime();
long sayiAdedi = sayilar.parallelStream()
.filter(s -> s > 5)
.count();
long bitis = System.nanoTime();
System.out.println("5'ten büyük sayı adedi: " + sayiAdedi + ", Süre: " + (bitis - baslangic) + " ns");
}
}
Stream API, büyük veri setlerini işlerken ve paralel işlemleri kolayca uygulamak istediğinizde oldukça güçlü bir araçtır.
4. Arayüzlerde Varsayılan Metotlar (Default Methods in Interfaces)
Java 8 öncesinde, bir arayüze yeni bir metot eklemek, bu arayüzü implemente eden tüm sınıfların bozulmasına neden olurdu, çünkü bu sınıfların yeni metodu implemente etmesi gerekirdi. Varsayılan metotlar, bu sorunu çözmek için tanıtıldı. default anahtar kelimesi kullanılarak, arayüzlere metot gövdeleri eklenebilir. Bu sayede, arayüz genişletilirken mevcut implementasyonlar bozulmaz.
Ayrıca, arayüzlere static metotlar da eklenebilir. Bu metotlar, arayüzün bir implementasyonuna ihtiyaç duymadan doğrudan arayüz adı üzerinden çağrılabilir.
Örnek: Varsayılan metot ve statik metot kullanımı
interface Calisan {
void calis(); // Soyut metot
// Varsayılan metot
default void molaVer() {
System.out.println("Çalışan mola veriyor (varsayılan).");
}
// Statik metot
static void sirketBilgisi() {
System.out.println("Bu şirket 1990 yılında kuruldu.");
}
}
class Yazilimci implements Calisan {
@Override
public void calis() {
System.out.println("Yazılımcı kod yazıyor.");
}
// Varsayılan metodu override edebilir
@Override
public void molaVer() {
System.out.println("Yazılımcı kahve molası veriyor.");
}
}
class Muhasebeci implements Calisan {
@Override
public void calis() {
System.out.println("Muhasebeci defter tutuyor.");
}
// Muhasebeci molaVer metodunu override etmiyor, varsayılanı kullanacak
}
public class DefaultMethodOrnek {
public static void main(String[] args) {
Yazilimci yazilimci = new Yazilimci();
yazilimci.calis();
yazilimci.molaVer(); // Yazılımcı kendi override ettiği metodu çağırır
Muhasebeci muhasebeci = new Muhasebeci();
muhasebeci.calis();
muhasebeci.molaVer(); // Muhasebeci varsayılan metodu çağırır
Calisan.sirketBilgisi(); // Statik metot doğrudan arayüz üzerinden çağrılır
}
}
5. Optional Sınıfı
java.util.Optional sınıfı, bir değerin varlığını veya yokluğunu temsil etmek için kullanılır. Temel amacı, NullPointerException (NPE) hatalarını önlemektir. Geleneksel olarak, bir metot null döndürdüğünde, çağıranın bu null durumu kontrol etmesi ve uygun şekilde ele alması gerekirdi. Optional ile, bir değerin mevcut olup olmadığını açıkça belirtirsiniz ve bu durumu daha güvenli bir şekilde yönetmenizi sağlarsınız.
Optional Kullanımının Avantajları
* NPE Koruması: null kontrolleri yerine Optional metotlarını kullanarak daha güvenli kod yazılır.
* Kod Okunabilirliği: Bir metodun Optional döndürmesi, o metodun bir değer döndürmeyebileceğini açıkça belirtir.
* Deklaratif Yaklaşım: ifPresent(), orElse(), map(), flatMap() gibi metotlar sayesinde daha fonksiyonel ve deklaratif kod yazılır.
Örnek: Optional sınıfının kullanımı
import java.util.Optional;
public class OptionalOrnek {
public static Optional kullaniciAdiGetir(int id) {
if (id == 1) {
return Optional.of("ali"); // Değer mevcut
} else if (id == 2) {
return Optional.of("veli");
} else {
return Optional.empty(); // Değer mevcut değil
}
}
public static void main(String[] args) {
// ID 1 için kullanıcı adını getir
Optional kullanici1 = kullaniciAdiGetir(1);
kullanici1.ifPresent(ad -> System.out.println("Kullanıcı 1 Adı: " + ad)); // Eğer değer varsa yazdır
// ID 3 için kullanıcı adını getir (mevcut değil)
Optional kullanici3 = kullaniciAdiGetir(3);
System.out.println("Kullanıcı 3 Adı: " + kullanici3.orElse("Misafir")); // Eğer değer yoksa "Misafir" kullan
// Eğer değer yoksa hata fırlat
try {
Optional kullanici4 = kullaniciAdiGetir(4);
String ad4 = kullanici4.orElseThrow(() -> new IllegalArgumentException("Kullanıcı bulunamadı!"));
System.out.println("Kullanıcı 4 Adı: " + ad4);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
// Map metodu ile Optional içindeki değeri dönüştürme
Optional kullanici2 = kullaniciAdiGetir(2);
String buyukHarfAd = kullanici2.map(String::toUpperCase)
.orElse("BILINMIYOR");
System.out.println("Kullanıcı 2 Adı (Büyük Harf): " + buyukHarfAd);
}
}
6. Yeni Date ve Time API (java.time paketi)
Java 8’den önce java.util.Date ve java.util.Calendar sınıfları, tarih ve saat işlemleri için kullanılıyordu. Ancak bu sınıfların birçok problemi vardı: değiştirilebilir olmaları, thread-safe olmamaları, API’lerinin karmaşık ve tutarsız olması gibi. Java 8, bu sorunları çözmek için Joda-Time kütüphanesinden esinlenerek tamamen yeni bir Date ve Time API’si (JSR 310) tanıttı. java.time paketi altında yer alan bu yeni API, daha temiz, daha güvenli ve daha sezgisel bir kullanım sunar.
Temel Sınıflar
* LocalDate: Yıl, ay, gün gibi tarih bilgisini tutar, saat bilgisi içermez.
* LocalTime: Saat, dakika, saniye, nanosaniye gibi saat bilgisini tutar, tarih bilgisi içermez.
* LocalDateTime: Hem tarih hem de saat bilgisini tutar, zaman dilimi bilgisi içermez.
* ZonedDateTime: LocalDateTime‘a ek olarak zaman dilimi bilgisini de tutar.
* Instant: Unix epoch’tan (1 Ocak 1970 UTC) bu yana geçen zamanı nanosaniye hassasiyetinde temsil eder. Makine zamanı için idealdir.
* Duration: İki Instant veya LocalTime arasındaki zaman farkını saniye ve nanosaniye olarak ölçer.
* Period: İki LocalDate arasındaki zaman farkını yıl, ay ve gün olarak ölçer.
Tüm bu sınıflar immutabledır (değiştirilemez), bu da onları thread-safe yapar.
Örnek: Yeni Date ve Time API kullanımı
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
public class DateTimeApiOrnek {
public static void main(String[] args) {
// LocalDate kullanımı
LocalDate bugun = LocalDate.now();
System.out.println("Bugünün tarihi: " + bugun); // YYYY-MM-DD
LocalDate dogumGunu = LocalDate.of(1990, Month.MAY, 15);
System.out.println("Doğum günü: " + dogumGunu);
// LocalTime kullanımı
LocalTime suAnkiSaat = LocalTime.now();
System.out.println("Şu anki saat: " + suAnkiSaat); // HH:MM:SS.NNNNNNNNN
LocalTime belirliSaat = LocalTime.of(14, 30, 0);
System.out.println("Belirli saat: " + belirliSaat);
// LocalDateTime kullanımı
LocalDateTime suAnkiTarihSaat = LocalDateTime.now();
System.out.println("Şu anki tarih ve saat: " + suAnkiTarihSaat);
LocalDateTime gelecektekiTarihSaat = suAnkiTarihSaat.plusDays(5).plusHours(3);
System.out.println("5 gün 3 saat sonra: " + gelecektekiTarihSaat);
// ZonedDateTime kullanımı (Zaman dilimli tarih ve saat)
ZonedDateTime newYorkZamani = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println("New York zamanı: " + newYorkZamani);
// Tarih ve saatler arası fark hesaplama (Period ve Duration)
LocalDate tarih1 = LocalDate.of(2023, 1, 1);
LocalDate tarih2 = LocalDate.of(2024, 5, 10);
long gunFarki = ChronoUnit.DAYS.between(tarih1, tarih2);
System.out.println("İki tarih arası gün farkı: " + gunFarki);
// Tarih ve saat formatlama
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
String formatliTarihSaat = suAnkiTarihSaat.format(formatter);
System.out.println("Formatlı tarih ve saat: " + formatliTarihSaat);
// String'den tarih/saat parse etme
String tarihStr = "25/12/2023 10:00:00";
LocalDateTime parsedDateTime = LocalDateTime.parse(tarihStr, formatter);
System.out.println("Parse edilmiş tarih ve saat: " + parsedDateTime);
}
}
7. Metot Referansları (Method References)
Metot referansları, lambda ifadelerinin daha kısa ve okunabilir bir alternatifidir. Bir lambda ifadesi sadece mevcut bir metodu çağırıyorsa, bu lambda ifadesini bir metot referansı ile değiştirebiliriz. Bu, kodun daha da sadeleşmesini sağlar ve kodun ne yaptığını daha net bir şekilde ifade eder.
Metot Referansı Türleri
1. Statik Metot Referansı: ClassName::staticMethodName
* Örnek: Math::max (lambda karşılığı: (a, b) -> Math.max(a, b))
2. Belirli Bir Nesnenin Örnek Metodu Referansı: object::instanceMethodName
* Örnek: System.out::println (lambda karşılığı: s -> System.out.println(s))
3. Belirli Bir Türdeki Rastgele Bir Nesnenin Örnek Metodu Referansı: ClassName::instanceMethodName
* Örnek: String::length (lambda karşılığı: s -> s.length())
* Örnek: String::compareToIgnoreCase (lambda karşılığı: (s1, s2) -> s1.compareToIgnoreCase(s2))
4. Kurucu Metot Referansı: ClassName::new
* Örnek: ArrayList::new (lambda karşılığı: () -> new ArrayList())
Örnek: Metot referanslarının kullanımı
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
public class MethodReferenceOrnek {
public static void main(String[] args) {
List isimler = Arrays.asList("Ayşe", "Fatma", "Can", "Deniz");
// 1. Belirli bir nesnenin örnek metodu referansı (System.out::println)
System.out.println("İsimler (forEach ile):");
isimler.forEach(System.out::println);
// Lambda karşılığı: isimler.forEach(isim -> System.out.println(isim));
// 2. Statik metot referansı (Integer::sum)
List sayilar = Arrays.asList(10, 20, 30, 40, 50);
int toplam = sayilar.stream().reduce(0, Integer::sum);
System.out.println("Sayıların toplamı: " + toplam);
// Lambda karşılığı: sayilar.stream().reduce(0, (a, b) -> Integer.sum(a, b));
// 3. Belirli bir türdeki rastgele bir nesnenin örnek metodu referansı (String::toUpperCase)
List buyukHarfIsimler = isimler.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Büyük harf isimler: " + buyukHarfIsimler);
// Lambda karşılığı: isimler.stream().map(isim -> isim.toUpperCase()).collect(Collectors.toList());
// 4. Kurucu metot referansı (ArrayList::new)
// Bu örnek doğrudan listeyi kopyalamak için kullanılmaz, ancak Stream'in collect metoduyla birleştirilebilir.
// Örneğin, bir stream'den yeni bir ArrayList oluşturmak:
List yeniIsimListesi = isimler.stream()
.collect(Collectors.toCollection(java.util.ArrayList::new));
System.out.println("Yeni isim listesi (kurucu referansı ile): " + yeniIsimListesi);
// Lambda karşılığı: Collectors.toCollection(() -> new ArrayList());
}
}
8. Nashorn JavaScript Motoru
Java 8, Java uygulamalarının içinde JavaScript kodunu çalıştırmak için yeni bir JavaScript motoru olan Nashorn’u tanıttı. Önceki Rhino motorunun yerini alan Nashorn, daha iyi performans ve ECMAScript 5.1 uyumluluğu sunar. Bu özellik, Java uygulamalarına dinamik komut dosyası çalıştırma veya yapılandırma yeteneği eklemek isteyen geliştiriciler için faydalıdır.
Örnek: Nashorn ile JavaScript çalıştırma
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class NashornOrnek {
public static void main(String[] args) {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn"); // veya "javascript"
try {
// JavaScript kodunu çalıştırma
engine.eval("print('Merhaba Nashorn!');");
// Değişken tanımlama ve kullanma
engine.eval("var isim = 'Java';");
engine.eval("print('Hoş geldin ' + isim + ' 8');");
// Java objeleriyle etkileşim
engine.put("sayi1", 10);
engine.put("sayi2", 20);
engine.eval("var toplam = sayi1 + sayi2;");
System.out.println("JavaScript'ten gelen toplam: " + engine.get("toplam"));
} catch (ScriptException e) {
e.printStackTrace();
}
}
}
9. Paralel Dizi Sıralama (Parallel Array Sorting)
Java 8, java.util.Arrays sınıfına büyük dizileri sıralamak için yeni parallelSort() metotlarını ekledi. Bu metotlar, Fork/Join çerçevesini kullanarak diziyi paralel alt dizilere böler ve bu alt dizileri ayrı ayrı işler. Bu, özellikle çok çekirdekli işlemcilerde büyük veri setleri için önemli performans iyileştirmeleri sağlayabilir.
Örnek: Arrays.parallelSort() kullanımı
import java.util.Arrays;
import java.util.Random;
public class ParallelSortOrnek {
public static void main(String[] args) {
int[] sayilar = new int[10000000]; // 10 milyon elemanlı bir dizi
Random random = new Random();
// Diziyi rastgele sayılarla doldur
for (int i = 0; i < sayilar.length; i++) {
sayilar[i] = random.nextInt(1000000);
}
// Kopyasını oluşturarak karşılaştırma yapalım
int[] sayilarKopya = Arrays.copyOf(sayilar, sayilar.length);
// Geleneksel sıralama
long baslangicNormal = System.nanoTime();
Arrays.sort(sayilar);
long bitisNormal = System.nanoTime();
System.out.println("Normal sıralama süresi: " + (bitisNormal - baslangicNormal) / 1_000_000.0 + " ms");
// Paralel sıralama
long baslangicParallel = System.nanoTime();
Arrays.parallelSort(sayilarKopya);
long bitisParallel = System.nanoTime();
System.out.println("Paralel sıralama süresi: " + (bitisParallel - baslangicParallel) / 1_000_000.0 + " ms");
// Dizinin sıralanıp sıralanmadığını kontrol etmek için (ilk birkaç eleman)
// System.out.println("Sıralanmış dizinin ilk 10 elemanı: " + Arrays.toString(Arrays.copyOfRange(sayilar, 0, 10)));
// System.out.println("Paralel sıralanmış dizinin ilk 10 elemanı: " + Arrays.toString(Arrays.copyOfRange(sayilarKopya, 0, 10)));
}
}
Bu örnek, büyük bir dizi üzerinde Arrays.sort() ile Arrays.parallelSort() arasındaki performans farkını göstermektedir. Performans farkı, sistemin çekirdek sayısına ve dizinin boyutuna göre değişebilir.
Sonuç
Java 8, dilin gelişiminde devrim niteliğinde bir adımdı. Lambda ifadeleri, Stream API, fonksiyonel arayüzler ve metot referansları gibi özellikler, Java'ya fonksiyonel programlama yeteneklerini getirerek geliştiricilere daha özlü, okunabilir ve verimli kod yazma imkanı sundu. Optional sınıfı NullPointerException riskini azaltırken, yeni Date ve Time API'si tarih ve saat işlemlerini çok daha kolay ve güvenli hale getirdi. Arayüzlerdeki varsayılan metotlar, geriye dönük uyumluluğu korurken arayüzlerin evrimini kolaylaştırdı. Nashorn motoru ve paralel dizi sıralama gibi diğer iyileştirmeler de platformun genel yeteneklerini artırdı.
Java 8'in getirdiği bu yenilikler, modern Java uygulamalarının geliştirilmesinde temel taşlar haline gelmiş ve dilin gelecekteki evrimine yön veren önemli bir temel oluşturmuştur. Her Java geliştiricisinin bu özelliklere hakim olması, daha kaliteli ve sürdürülebilir yazılımlar üretmek için kritik öneme sahiptir.