Tuesday, April 14, 2020

Java 9+'da Modüler Uygulama Geliştirmek

   Java Development Kit (JDK), Java uygulaması geliştirmek, geliştirdiğimiz uygulamaları çalıştırmak ve en nihayetinde uygulamamızın dağıtımını yapmak için çeşitli araçlar (javac, java, javadoc, jar, jlink gibi) sunuyor. Uygulamaların dağıtımını yapmak için jar komutundan ve jar formatından yararlanıyoruz. jar komutu projede yer alan .class uzantılı dosyaları tek bir dosyaya arşivleyip, bu arşiv dosyasını jar uzantılı bir dosyaya sıkıştırıyor. Ağırlıklı olarak Linux sistemlerde yer alan tar ve zip komutunun yaptığı işlemi bize sunuyor. Daha sonra bu jar uzantılı uygulamayı java komutu ile çalıştırıyoruz. jar uzantılı dosyada bir uygulama olabileceği gibi bir çok projede ortak olarak kullanılan bir kütüphane de olabilir. 

MODÜL


   Yazılım Mimarisi yazılımı parçalara ayırmakla ilgilidir. Yazılımı öyle birimlere ya da parçalara ayıralım ve daha sonra bu birimleri öyle birleştirelim ki işlevsel olmayan özellikleri sağlasın. İşlevsel olmayan özellikler ölçeklenebilirlik, her zaman erişebilirlik, değiştirilebilirlik gibi yazılımın verdiği hizmetin kalitesine yönelik tanımlar içerir. Örneğin İstemci-Sunucu mimarisinde bu parçalar ya da birimler İstemci ve Sunucudur. Örnek olarak MySQL İlişkisel Veritabanını ele alalım. MySQL İstemci-Sunucu mimarisine sahiptir. Sunucu mysqld prosesi olarak bir makinada çalışırken, istemciler ise Java, C/C++, .Net, PHP, Python ya da node.js platformarından birinde yazılmış birer uygulama olabilir. mysqld sunucusu ise Katmanlı Mimariye (=Layered Architecture) sahiptir. Bu katmanlar, mysqld örneğinde aşağıda verildeği şekildedir: i. Bağlantı katmanı, ii. Ayrıştırıcı (=Parser) ve Sorgu Cep Belleği, iii. En İyileyici (=Optimizer) iv. Depolama Motorları (=Storage Engines)

MySQL'in Mimarisi
   Bu katmanların her birini diğerinden ayırt etmek üzere bir birim ya da modül olarak tanımlamak  isteriz. Böylelikle uygulamayı geliştirmek, test etmek, değiştirmek, bakımını yapmak daha kolay olacaktır. Bir uygulama Yekpare Mimaride (=Monolithic Architecture) olarak geliştirilmiş olsa bile modüler bir yapıda olmasını yeğleriz. Bu birimleri Windows'ta DLL (Dynamic Link Library) olarak, Linux'da SO (=Shared Object) dosyası olarak ve Java'da ise JAR dosyası olarak tanımlıyoruz.

Java 9 ve MODÜLER PROGRAMLAMA

   Java 9'un modüller programlama açısından bakıldığında iki hedefi bulunuyor:
  • İlk hedef JDK'nın modüler bir yapıya kavuşturmak getirmek. 
  • İkinci hedef JDK üzerinde çalışan onu kullanan uygulamanızın modüler yazılabilmesini sağlamak. 

MODÜLER JDK

Java'da Java 9'a kadar modülü ayırt edecek herhangi bir gösterim bulunmuyordu. Jar dosyası kavramsal olarak bir modül içerse bile JVM'nin (Java Virtual Machine) ve Class Loader'ın bunu ayırt etmesini sağlayacak herhangi bir tanımlama jar dosyası içinde yer almaz. Üstelik JVM'nin kendisi modüler değildir: rt.jar ve tools.jar. Platform devasa bu iki jar dosyasından ibarettir ve her yeni sürümde bu jar dosyasının boyutu artmaktadır. Java 9 hem platformun hem de bu platformun üzerinde çalışacak uygulamanın modüler olarak geliştirilmesini sağlayacak yenilikler içeriyor. Bu yazıda bu yenilikler tanıtılmaya çalışılacaktır.
Makinanıza JDK 9+ kurduğunuzda artık içinden rt.jar çıkmadığını göreceksiniz. rt.jar içinde sağlanan işlevler modüller halinde yerinden jmods dizini altında düzenlendiğini görüyoruz:

c:\stage\opt\jdk-9.0.4>dir
 Directory of c:\stage\opt\jdk-9.0.4

01/19/2020  08:01 AM    <DIR>          .
01/19/2020  08:01 AM    <DIR>          ..
01/19/2020  08:00 AM    <DIR>          bin
01/19/2020  08:00 AM    <DIR>          conf
12/19/2017  06:31 PM             3,244 COPYRIGHT
01/19/2020  08:00 AM    <DIR>          include
01/19/2020  08:00 AM    <DIR>          jmods
01/19/2020  08:00 AM    <DIR>          legal
01/19/2020  08:01 AM    <DIR>          lib
11/04/2019  12:40 PM               158 README.html
11/04/2019  12:40 PM             1,950 release

jmods dizini içinde 98 adet jmod uzantılı dosya yer alıyor. Bu modüllerin her birinin tek bir sorumluluğu olacak şekilde düzenlendiğini görüyoruz:

c:\stage\opt\jdk-9.0.4\jmods>dir

 Directory of c:\stage\opt\jdk-9.0.4\jmods

01/19/2020  08:00 AM    <DIR>          .
01/19/2020  08:00 AM    <DIR>          ..
11/04/2019  12:40 PM            61,160 java.activation.jmod
11/04/2019  12:40 PM        16,673,580 java.base.jmod
11/04/2019  12:40 PM           111,467 java.compiler.jmod
11/04/2019  12:40 PM         2,689,556 java.corba.jmod
11/04/2019  12:40 PM            51,910 java.datatransfer.jmod
11/04/2019  12:40 PM        13,681,645 java.desktop.jmod
11/04/2019  12:40 PM           127,281 java.instrument.jmod
11/04/2019  12:40 PM            18,864 java.jnlp.jmod
11/04/2019  12:40 PM           120,151 java.logging.jmod
11/04/2019  12:40 PM           880,278 java.management.jmod
.
.
.
11/04/2019  12:40 PM            78,541 jdk.security.auth.jmod
11/04/2019  12:40 PM            24,052 jdk.security.jgss.jmod
11/04/2019  12:40 PM           527,308 jdk.snmp.jmod
11/04/2019  12:40 PM            18,490 jdk.unsupported.jmod
11/04/2019  12:40 PM         1,940,012 jdk.xml.bind.jmod
11/04/2019  12:40 PM            42,434 jdk.xml.dom.jmod
11/04/2019  12:40 PM           741,290 jdk.xml.ws.jmod
11/04/2019  12:40 PM            87,995 jdk.zipfs.jmod
11/04/2019  12:40 PM             2,624 oracle.desktop.jmod
11/04/2019  12:40 PM             8,938 oracle.net.jmod

jmod dosyası aslında bir jar dosyasıdır. İçinde modül bilgisi, modülü oluşturan class dosyaları yer alıyor. Modül bilgisi ve modüller arasındaki bağımlılıklar module-info.class dosyası içinde tanımlıdır. Modül listesini Java Sanal Makinasından list-modules seçeneği ile öğrenebilirsiniz:

c:\stage\opt\jdk-9.0.4>java --list-modules
java.activation@9.0.4
java.base@9.0.4
java.compiler@9.0.4
java.corba@9.0.4
java.datatransfer@9.0.4
java.desktop@9.0.4
java.instrument@9.0.4
java.jnlp@9.0.4
java.logging@9.0.4
java.management@9.0.4

.
.
.
jdk.security.auth@9.0.4
jdk.security.jgss@9.0.4
jdk.snmp@9.0.4
jdk.unsupported@9.0.4
jdk.xml.bind@9.0.4
jdk.xml.dom@9.0.4
jdk.xml.ws@9.0.4
jdk.zipfs@9.0.4
oracle.desktop@9.0.4
oracle.net@9.0.4

Modül dosyası oluşturmak, modül dosyası içindeki dosyaları listelemek ve açmak ve modüller arasındaki bağımlılığı listelemek için jmod komutunu kullanabilirsiniz. Aşağıda jmod komutunun kullanımları örneklenmiştir:

c:\stage\opt\jdk-9.0.4>jmod describe jmods\java.sql.jmod
java.sql@9.0.4
exports java.sql
exports javax.sql
exports javax.transaction.xa
requires java.base mandated
requires java.logging transitive
requires java.xml transitive
uses java.sql.Driver
platform windows-amd64

c:\stage\opt\jdk-9.0.4>jmod list jmods\java.sql.jmod
classes/module-info.class
classes/java/sql/Array.class
classes/java/sql/BatchUpdateException.class
classes/java/sql/Blob.class
classes/java/sql/CallableStatement.class
classes/java/sql/ClientInfoStatus.class
classes/java/sql/Clob.class
classes/java/sql/Connection.class
classes/java/sql/ConnectionBuilder.class
classes/java/sql/DatabaseMetaData.class
.
.
.
classes/javax/sql/RowSetReader.class
classes/javax/sql/RowSetWriter.class
classes/javax/sql/StatementEvent.class
classes/javax/sql/StatementEventListener.class
classes/javax/sql/XAConnection.class
classes/javax/sql/XAConnectionBuilder.class
classes/javax/sql/XADataSource.class
classes/javax/transaction/xa/XAException.class
classes/javax/transaction/xa/XAResource.class
classes/javax/transaction/xa/Xid.class
legal/COPYRIGHT
legal/LICENSE

JDK'nın bu yeni modüler yapısı sayesinde uygulamanıza özel bir JDK oluşturabilirsiniz. Bunun için öncelikle uygulamanızın ihtiyacı olan modüllerin bir listesini çıkarmanız gerekir. Ardından jlink komutu ile sadece tanımladığınız bu listede yer alan modüllerden oluşan bir JDK oluşturabilirsiniz:

c:\stage\opt\jdk-9.0.4>jlink --module-path jmods --add-modules java.sql,java.sql.rowset,java.xml --output c:\tmp\jdk9-app42

Sadece ihtiyacımız olan modüllerden oluşan JDK c:\tmp\jdk9-app42 dizininde oluştu:

c:\tmp\jdk9-app42>dir
 Directory of c:\tmp\jdk9-app42

04/11/2019  10:49 PM    <DIR>          .
04/11/2019  10:49 PM    <DIR>          ..
04/11/2019  10:49 PM    <DIR>          bin
04/11/2019  10:49 PM    <DIR>          conf
04/11/2019  10:49 PM    <DIR>          include
04/11/2019  10:49 PM    <DIR>          legal
04/11/2019  10:49 PM    <DIR>          lib
04/11/2019  10:49 PM               121 release

Bu yeni JDK bir taraftan uygulamanızın daha hızlı açılmasını sağlarken diğer taraftan uygulamanızı kullanmadığı modüllerde oluşabilecek güvenlik açıklarından korumanızı sağlayacaktır. Uygulamanızı çalıştırmak için eskiden olduğu gibi java komutunu kullanacaksınız. Ancak bu kez java komutu olarak yeni JDK'daki bin dizininde yer alan java komutunu kullanacaksınız: 

c:\tmp\jdk9-app42\bin>java -jar c:\tmp\app42.jar


MODÜLER JAVA UYGULAMASI GELİŞTİRMEK


Java 9 öncesinde en üst paketleme yapısı sınıftır. Sınıfı detaylarını gizler dışarıya vereceği hizmetleri ise açarız. Bunu üyeleri public, private ya da protected  tanımlayarak sağlarız. private tanımladığınız üyelere dışarıdan bir referans üzerinden ya da türetilmiş sınıftan erişemezsiniz. public tanımladığınız bir üyeye her yerden erişebilirsiniz. Ancak bir projede ya da jar dosyası içinde yer alan bir sınıfın import edilmesini engelleyemezsiniz. Bir sınıf istediği ihtiyaç duyduğu herhangi bir paketteki sınıfı kullanır. Bir paketi paketteki sınıfları nasıl gizleyebilirsiniz? Java 9 ile birlikte bir üst paketleme seviyesine kavuştuk: modül. Varsayılan davranış olarak bir modül içindeki tüm sınıflar gizlenmiştir. Modülden dışarıya açmak istediğiniz paketleri, module-info.java dosyasında exports anahtar kelimesini kullanarak tanımlamanız gerekir. Her bir modülün dağıtımı bir jar dosyası olarak gerçekleştirilir. Şimdi modül yapısını ve özelliklerini incelemek için örnek bir uygulama olarak üç modülden oluşan bir Sayısal Loto uygulaması geliştirelim. Her bir modülün tek bir sorumluluğu olacak şekilde parçalıyoruz: 
  • Random Module: Sözde rastgele sayı üretmekten sorumlu modül
  • Lottery Module: Sayısal Loto için sayı üretmekten sorumlu modül
  • Application Module: Uygulama modülü


Random Module


Random modülü sözde rastgele sayı üretmekten sorumlu bir modül olarak tasarlanmıştır. Modülün vereceği bu hizmeti dışarıya bir Java arayüzü (com.example.random.service.RandomService) üzerinden açacağız. Bu arayüzün gerçeklemelerine ait detaylarını (com.example.random.service.business.SimpleRandomService ve com.example.random.service.business.SecureRandomService sınıfları) ise modül içinde gizliyoruz.

module-info.java:
module com.example.random {
    exports com.example.random.service;
    provides com.example.random.service.RandomNumberService with com.example.random.service.business.SimpleRandomNumberService, com.example.random.service.business.SecureRandomNumberService;
}

QualityLevel.java:
package com.example.random.service;

public enum QualityLevel {
 SECURE, SIMPLE
}

Quality.java:
package com.example.random.service;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Documented
@Retention(RUNTIME)
@Target({ TYPE, METHOD })
public @interface Quality {
 QualityLevel value();
}

SecureRandomNumberService.java:
package com.example.random.service.business;

import java.security.SecureRandom;
import java.util.Random;

import com.example.random.service.Quality;
import com.example.random.service.QualityLevel;
import com.example.random.service.RandomNumberService;

@Quality(QualityLevel.SECURE)
public class SecureRandomNumberService implements RandomNumberService {
 private Random rand = new SecureRandom();

 public SecureRandomNumberService() {
  System.err.println("SecureRandomNumberService is created!");
 }

 @Override
 public int generate(int min, int max) {
  System.err.println("SECURE implementation is used!");
  return rand.nextInt(max - min) + min;
 }

}

SimpleRandomNumberService.java:
package com.example.random.service.business;

import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

import com.example.random.service.Quality;
import com.example.random.service.QualityLevel;
import com.example.random.service.RandomNumberService;

@Quality(QualityLevel.SIMPLE)
public class SimpleRandomNumberService implements RandomNumberService {
 private Random rand = ThreadLocalRandom.current();

 public SimpleRandomNumberService() {
  System.err.println("SimpleRandomNumberService is created!");
 }

 @Override
 public int generate(int min, int max) {
  System.err.println("CHEAP implementation is used!");
  return rand.nextInt(max - min + 1) + min;
 }

}

LotteryService.java:
package com.example.random.service;

public interface RandomNumberService {
 int generate(int min,int max);
}

Lottery Module


Lottery modülü Sayısal Loto için 6 tane birbirinden farklı 1 ile 49 arasında sıralı sayı üretmekten sorumlu bir modül olarak tasarlanmıştır. Lottery modülü rastgele sayı üretmekten sorumlu olan Random Modülünü kullanıyor. Bu durumu module-info.java dosyasında requires anahtar kelime ile ifade ediyoruz: 

module-info.java:
import com.example.random.service.RandomNumberService;

module com.example.lottery {
 requires com.example.random;
 exports com.example.lottery.service;
 uses RandomNumberService;
 provides com.example.lottery.service.LotteryService with com.example.lottery.service.business.SimpleLotteryService;
}

LotteryService.java:
package com.example.lottery.service;

import com.example.random.service.RandomNumberService;

import java.util.List;

public interface LotteryService {
 List<Integer> draw();
 List<List<Integer>> draw(int n);
 void setRandomNumberService(RandomNumberService randomNumberService);
}


SimpleLotteryService.java:
package com.example.lottery.service.business;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import com.example.lottery.service.LotteryService;
import com.example.random.service.RandomNumberService;

public class SimpleLotteryService implements LotteryService {
    private RandomNumberService randomNumberService;

    @Override
    public void setRandomNumberService(RandomNumberService randomNumberService) {
        this.randomNumberService = randomNumberService;
    }

    @Override
    public List<Integer> draw() {
        return IntStream.generate(() -> randomNumberService.generate(1, 50))
                .distinct()
                .limit(6)
                .sorted()
                .boxed()
                .collect(Collectors.toList());
    }

    @Override
    public List<List<Integer>> draw(int n) {
        return IntStream.range(0, n)
                .mapToObj(i -> this.draw())
                .collect(Collectors.toList());
    }

}

Application Module

Uygulama modülü hem Lottery modülüne hem de Random Modülüne bağımlılığı bulunuyor. Bu durumu module-info.java dosyasında requires anahtar kelimesi ile tanımlıyoruz. Uygulamamız ayrıca bu modüllerdeki LotteryService ve RandomNumberService arayüzlerini gerçekleyen servis sınıflarını kullanacağını ise uses anahtar kelimesi ile tanımlıyoruz: 

module-info.java:
import com.example.lottery.service.LotteryService;
import com.example.random.service.RandomNumberService;

module com.example.lottery.app {
    requires com.example.lottery;
    requires com.example.random;
    uses LotteryService;
    uses RandomNumberService;
}

application.properties:
random.number.service.level=SECURE

LotteryApp.java:
package com.example.app;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.function.Consumer;


import com.example.lottery.service.LotteryService;
import com.example.random.service.Quality;
import com.example.random.service.QualityLevel;
import com.example.random.service.RandomNumberService;

public class LotteryApp {

 public static void main(String[] args) throws FileNotFoundException, IOException {
  Properties props = new Properties();
  props.load(new FileInputStream(new File("src","application.properties")));
  QualityLevel level = QualityLevel.valueOf(props.getProperty("random.number.service.level"));
  LotteryService sls = ServiceLoader.load(LotteryService.class).findFirst().get();
  sls.setRandomNumberService(extractService(level));
  Consumer<List<Integer>> println = System.err::println;
  sls.draw(10).forEach(println);
 }

 private static RandomNumberService extractService(QualityLevel level) {
  ServiceLoader<RandomNumberService> loader = ServiceLoader.load(RandomNumberService.class);
  RandomNumberService randomNumberService = null;
  for (RandomNumberService rns : loader) {
   Class<?> clazz = rns.getClass();
   if(clazz.isAnnotationPresent(Quality.class)) {
    Quality quality = clazz.getAnnotation(Quality.class);
    if (quality.value() == level) {
     randomNumberService = rns;
     break;
    }
   }
  }
  return randomNumberService;
 }

}

Örnek uygulamaya https://github.com/deepcloudlabs/modular-programming-example-java9 adresinden erişebilirsiniz.

Saturday, April 11, 2020

Java 14'de instanceof Kullanımı

Çok şekillilik, Nesneye Dayalı Programlamanın en önemli mekanizmasıdır. Aşağıdaki örnekte çok şekilli bir yapı oluşturulmuştur:

class A {
}

class B extends A {
}

class C extends A  implements X {
}

class D extends B implements  Z {
}

interface X {}
interface Y {}
interface Z extends X,Y {}

Bu oluşturulan yapıyı UML sınıf diyagramında aşağıdaki şekilde gösterebiliriz:
Şimdi bu yapıyı kullanarak bir kaç nesne yaratalım ve bu nesneleri kullanmak üzere referans değişkenler tanımlayalım:

A a1 = new A();
A a2 = new B();
A a3 = new C();
A a4 = new D();
B b1 = new B();
B b2 = new D();
C c = new C();
D d = new D();

Çok şekilliliğin kuralları gereğince
  • A tipinden bir referans A, B, C ve D tiplerinden nesnelere referans edebilir.
  • B tipinden bir referans B ve D tiplerinden nesnelere referans edebilir.
  • C tipinden bir referans ancak C tipinden bir nesneye referans edebilir.
  • D tipinden bir referans ancak D tipinden bir nesneye referans edebilir.
Çok şekillilik çalışma zamanı ile ilgili bir mekanizmadır. Dolayısı ile referanslar arasında atama yaparken derleyici herhangi bir statik kontrol yapmaz, yapamaz:

A a1 = new A();
A a2 = new B();
A a3 = new C();
A a4 = new D();
D d1= (D) a1;
D d2= (D) a2;
D d3= (D) a3;
D d4= (D) a4;

Yukarıdaki örnekte aslında derleyici statik bir analiz yapmış olsaydı d1, d2 ve d3 değişkenlerine olan atamaların çalışma zamanında ClassCastException fırlatacağını ve sorunlu olduğu uyarısında bulunabilirdi. Ancak derleyici bu analizi yapmaz çünkü gerçekte nesnenin tipi çalışma zamanında bir veri tabanı sorgusu, kullanıcı seçimi ya da web servisi çağrısı gibi işlemlerin sonucunda dinamik olarak belirlenir. Yürütme zamanında, derleyicinin ürettiği kod, atama işlemlerinin uygun olup olmadığını, tip dönüşümünün geçerliliğini sınar ve dönüşüm uyumlu tipler arasında değilse ClassCastException fırlatır. ClassCastException bir RuntimeException sınıfıdır ve fırlatıldığında uygulamanın sonlanmasına neden olur. Bu tür bir hatadan kaçınmak için atamanın öncesinde tip dönüşümünün güvenli olup olmadığının testini yapmak uygun olur. Bu amaçla instanceof operatöründen yararlanılır:

D d1 = null;
if(a1 instanceof D)
  d1 = (D) a1;

Java 14 yukarıdaki kod ile yapılmak istenen işlemi daha yalın bir şekilde ifade etmemize olanak sağlıyor:

if(a1 instanceof D d1) {
          
}

Burada referans değişkeni kullanarak daha karmaşık koşullar oluşturulabilinir:

if(a1 instanceof D d1 && Objects.nonNull(d1) && d1 instanceof X) {

 }

Şimdi instanceof operatörünün bir başka kullanımını görelim:
public class Customer {
    private String identity;
    private String fullname;
    private String email;
    private String phone;

    public Customer(String identity, String fullname, String email, String phone) {
        this.identity = identity;
        this.fullname = fullname;
        this.email = email;
        this.phone = phone;
    }

    @Override
    public boolean equals(Object o) {
        if (o == null) return false;
        if(o instanceof Customer){
            Customer cust = (Customer) o;
            if (!cust.identity.equals(identity)) return false;
            if (!cust.fullname.equals(fullname)) return false;
            if (!cust.email.equals(email)) return false;
            if (!cust.phone.equals(phone)) return false;
        }
        return true;
} . . . }

Customer sınıfı içindeki equals() metodunu instanceof operatörünü kullanarak yeniden tanımlayalım:

public class Customer {
    private String identity;
    private String fullname;
    private String email;
    private String phone;

    public Customer(String identity, String fullname, String email, String phone) {
        this.identity = identity;
        this.fullname = fullname;
        this.email = email;
        this.phone = phone;
    }

    @Override
    public boolean equals(Object o) {
        return o instanceof Customer cust && Objects.nonNull(cust)
                && cust.identity.equals(identity)
                && cust.fullname.equals(fullname)
                && cust.email.equals(email)
                && cust.phone.equals(phone);
    }

    . 
    .
    .

}

Friday, April 10, 2020

Java 14'de Record Kullanımı

Java 14'de gelen yeniliklerden biri Record yapısıdır. Record özünde bir sınıf tanımlamaktadır. Dolayısı ile record tipinden bir nesne yaratabilirsiniz. Ama bu nesnenin durumu değiştirilemez! Bu Java'da yabancısı olmadığımız bir durum. String sınıfı, basit tiplere karşı düşen sınıflarımız (örneğin Integer, Character, Double gibi), BigInteger, BigDecimal sınıflarının hepsi bu türden durumu değiştirilemez (=immutable) nesneler yaratabildiğimiz sınıflardır.

IntelliJ IDEA 2020.1'de yeni bir programlama elemanı yaratmak istediğimizde listede artık Record'da ön izleme kullanımında bir özellik olarak yerini alıyor:

Java 14'de yeni gelen Ön İzlemede bir özellik olarak Record.

package com.example;

import java.awt.*;

public record Point3D(double x, double y, double z,
                      Color color) implements Comparable<Point3D>, Translateable<Point3D> {
    public static final Point3D ORIGIN = Point3D.of(0, 0, 0, Color.BLACK);

    public static Point3D of(double x, double y, double z, Color color) {
        return new Point3D(x, y, z, color);
    }

    public static double l2Distance(Point3D p1, Point3D p2) {
        return Math.sqrt(Math.pow(p1.x - p2.y, 2.) +
                Math.pow(p1.y - p2.y, 2.) +
                Math.pow(p1.z - p2.z, 2.));
    }

    public double l2Distance(Point3D other) {
        return l2Distance(this, other);
    }

    public double l2Distance() {
        return l2Distance(this, ORIGIN);
    }

    @Override
    public int compareTo(Point3D other) {
        return Double.compare(this.l2Distance(), other.l2Distance());
    }

    @Override
    public Point3D move(Point3D t) {
        return Point3D.of(this.x + t.x, this.y + t.y, this.z + t.z, this.color);
    }
}

package com.example;

public interface Translateable<T> {
    T move(T t);
}

Yukarıda Point3D isimli bir record tanımlanmış olduk. record, final sınıf özelliğindedir. Dolayısı ile record kullanarak yeni bir sınıf türetemezsiniz. record tanıtımı bir kurucu fonksiyon tanımı içerir. Bu kurucu fonksiyonun parametreleri aynı zamanda öznitelikleri tanımlar ve ilklendirir. Bu öz nitelikler de final tanımlıdır. Onları da nesne yaratıldıktan sonra değerlerini değiştiremezsiniz.  Ayrıca record içinde kurucuda verilen parametrelerin belirlediği öz nitelikler dışında başka bir öz nitelik tanımlayamazsınız! Bu üyeler final bile olsa bunu yapamazsınız! Öz niteliklerin değerini dönecek metotlar otomatik olarak üretiliyor. Ancak bu metotların isimlendirilmesinde Java Beans isimlendirme kuralından farklı olarak doğrudan öz niteliğin adı kullanılır: x(), y(), z() ve color(). Ayrıca Object sınıfından gelen equals, hashCode ve toString metotlarına  özel olarak bu yapı için özel işlev yüklenmiştir. Bu işlevleri aşağıdaki örnek uygulama üzerinden görmeye ve anlamaya çalışalım:

package com.example;

import java.awt.*;
import java.util.List;

public class StudyRecord {
    public static void main(String[] args) {
        var p1 = new Point3D(1, 2, 3, Color.BLUE);
        var p2 = new Point3D(1, 2, 3, Color.GREEN);
        var p3 = new Point3D(1, 2, 3, Color.BLUE);
        var points = List.of(p1, p2, p3);
        points.forEach(p -> {
            System.out.println(p.x());
            System.out.println(p.y());
            System.out.println(p.z());
            System.out.println(p.color());
            System.out.println(p.toString());
            System.out.println(p.hashCode());
        });
        System.out.println(p1.equals(p2));
        System.out.println(p1.equals(p3));
        System.out.println(p2.equals(p3));
    }
}

Yukarıdaki uygulama çalıştırıldığında aşağıdaki çıktıyı üretecektir:

1.0
2.0
3.0
java.awt.Color[r=0,g=0,b=255]
Point3D[x=1.0, y=2.0, z=3.0, color=java.awt.Color[r=0,g=0,b=255]]
2047344895
1.0
2.0
3.0
java.awt.Color[r=0,g=255,b=0]
Point3D[x=1.0, y=2.0, z=3.0, color=java.awt.Color[r=0,g=255,b=0]]
2047409920
1.0
2.0
3.0
java.awt.Color[r=0,g=0,b=255]
Point3D[x=1.0, y=2.0, z=3.0, color=java.awt.Color[r=0,g=0,b=255]]
2047344895
false
true
false

recordDDD'deki Value Object'i gerçeklemek için kullanılabilir. Nesnenin kimliği yoktur ya da nesnenin kimliği tüm öz niteliklerinden oluşur. İki Point3D nesnesinin eşitliği için tüm üyelerinin eşit olmasına bakılır. 

record içinde static üye tanıtabilirsiniz. Yukarıdaki Point3D örneğinde olduğu gibi record bir ya da daha fazla arayüzü (örneğimizde Comparable ve Translateable) gerçekleyebilir. 

record içinde bir sınıf içinde yazabildiğiniz türden sıradan metotlar yazabilirsiniz. Ancak bu metotlar nesnenin durumunuzu doğal olarak değiştiremeyecektir.

Şimdi JDK içinden çıkan javap komutunu kullanarak Point3D sınıfının tüm üyelerine bir göz atalım:

javap -classpath . com.example.Point3D
Compiled from "Point3D.java"
public final class com.example.Point3D extends java.lang.Record implements java.lang.Comparable<com.example.Point3D>, com.example.Translateable<com.example.Point3D> {
  public static final com.example.Point3D ORIGIN;
  public com.example.Point3D(double, double, double, java.awt.Color);
  public static com.example.Point3D of(double, double, double, java.awt.Color);
  public static double l2Distance(com.example.Point3D, com.example.Point3D);
  public double l2Distance(com.example.Point3D);
  public double l2Distance();
  public int compareTo(com.example.Point3D);
  public com.example.Point3D move(com.example.Point3D);
  public java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public double x();
  public double y();
  public double z();
  public java.awt.Color color();
  public int compareTo(java.lang.Object);
  static {};
}

Komutun ürettiği çıktıdan Point3D sınıfının, Java 14 ile gelen java.lang.Record soyut sınıfından türetildiği anlaşılıyor:

package java.lang;

@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS, essentialAPI=true)
public abstract class Record {
    /**
     * Constructor for record classes to call.
     */
    protected Record() {}

    /**
     * Indicates whether some other object is "equal to" this one.  In addition
     * to the general contract of {@link Object#equals(Object) Object.equals},
     * record classes must further obey the invariant that when
     * a record instance is "copied" by passing the result of the record component
     * accessor methods to the canonical constructor, as follows:
     * <pre>
     *     R copy = new R(r.c1(), r.c2(), ..., r.cn());
     * </pre>
     * then it must be the case that {@code r.equals(copy)}.
     *
     * @implSpec
     * The implicitly provided implementation returns {@code true} if
     * and only if the argument is an instance of the same record type
     * as this object, and each component of this record is equal to
     * the corresponding component of the argument; otherwise, {@code
     * false} is returned. Equality of a component {@code c} is
     * determined as follows:
     * <ul>
     *
     * <li> If the component is of a reference type, the component is
     * considered equal if and only if {@link
     * java.util.Objects#equals(Object,Object)
     * Objects.equals(this.c(), r.c()} would return {@code true}.
     *
     * <li> If the component is of a primitive type, using the
     * corresponding primitive wrapper class {@code PW} (the
     * corresponding wrapper class for {@code int} is {@code
     * java.lang.Integer}, and so on), the component is considered
     * equal if and only if {@code
     * PW.valueOf(this.c()).equals(PW.valueOf(r.c()))} would return
     * {@code true}.
     *
     * </ul>
     *
     * The implicitly provided implementation conforms to the
     * semantics described above; the implementation may or may not
     * accomplish this by using calls to the particular methods
     * listed.
     *
     * @see java.util.Objects#equals(Object,Object)
     *
     * @param   obj   the reference object with which to compare.
     * @return  {@code true} if this object is equal to the
     *          argument; {@code false} otherwise.
     */
    @Override
    public abstract boolean equals(Object obj);

    /**
     * Obeys the general contract of {@link Object#hashCode Object.hashCode}.
     *
     * @implSpec
     * The implicitly provided implementation returns a hash code value derived
     * by combining the hash code value for all the components, according to
     * {@link Object#hashCode()} for components whose types are reference types,
     * or the primitive wrapper hash code for components whose types are primitive
     * types.
     *
     * @see     Object#hashCode()
     *
     * @return  a hash code value for this object.
     */
    @Override
    public abstract int hashCode();

    /**
     * Obeys the general contract of {@link Object#toString Object.toString}.
     *
     * @implSpec
     * The implicitly provided implementation returns a string that is derived
     * from the name of the record class and the names and string representations
     * of all the components, according to {@link Object#toString()} for components
     * whose types are reference types, and the primitive wrapper {@code toString}
     * method for components whose types are primitive types.
     *
     * @see     Object#toString()
     *
     * @return  a string representation of the object.
     */
    @Override
    public abstract String toString();
}

Dilerseniz record sınıfına istediğiniz kadar kurucu fonksiyon yazabilirsiniz:

public record Point3D(double x, double y, double z, Color color) implements Comparable<Point3D>, Translateable<Point3D> {
    public Point3D(double x, double y, double z, Color color) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.color = color;
    }

    public Point3D(double x, double y, double z) {
        this(0, 0, z, Color.BLACK);
    }

    public Point3D(double x, double y) {
        this(0, 0, 1, Color.BLACK);
    }

    public Point3D() {
        this(0, 0, 0);
    }
   
   .
   .
   .

}

Point3D(double x, double y, double z, Color color) kurucusunu eğer parametreleri ilgili üyelere atamak dışında bir denetim yapmayacaksanız yazmanıza gerek bulunmuyor:

public record Point3D(double x, double y, double z, Color color) implements Comparable<Point3D>, Translateable<Point3D> {

    public Point3D(double x, double y, double z) {
        this(0, 0, z, Color.BLACK);
    }

    public Point3D(double x, double y) {
        this(0, 0, 1, Color.BLACK);
    }

    public Point3D() {
        this(0, 0, 0);
    }
   
   .
   .
   .

}

Standart kurucu fonksiyonu, değişmezleri doğrulamak için kod eklemeniz gerektiğinde yeniden tanımlayın:

public record Point3D(double x, double y, double z, Color color) implements Comparable<Point3D>, Translateable<Point3D> {

    public Point3D(double x, double y, double z, Color color) {
        if(Double.isFinite(x) && Double.isFinite(y) && Double.isFinite(z)){
            this.x = x;
            this.y = y;
            this.z = z;
            this.color = color;
            return;        
        }
        throw new IllegalArgumentException("Point3D should be finite!");
    }

    public Point3D(double x, double y, double z) {
        this(0, 0, z, Color.BLACK);
    }

    public Point3D(double x, double y) {
        this(0, 0, 1, Color.BLACK);
    }

    public Point3D() {
        this(0, 0, 0);
    }
   
   .
   .
   .

}

Java 14'de Record tipini nerede kullanabileceğimize bir bakalım:

var p1 = new Point3D(1, 2, 3, Color.BLUE);
var p2 = new Point3D(1, 2, 3, Color.GREEN);
var p3 = new Point3D(1, 2, 3, Color.BLUE);
var c1 = new Customer("1","Jack Bauer","jack.bauer@example.com","555-555-5555");
var c2 = new Customer("2","Kate Austen","kate.austen@example.com","555-555-5123");
var c3 = new Customer("3","James Sawyer","james.sawyer@example.com","555-555-5432");
List<Record> records = List.of(p1, p2, p3,c1,c2,c3);
records.forEach(rec -> {
    System.out.println(rec.toString());
    System.out.println(rec.hashCode());
});