Scala Class Kullanımı

Class (Classes)

Scala ile basit bir class oluşturalım.

class OrnekClass {
    println("Hello")
}

Yukarıdaki kodu derlediğimiz zaman JVM arka planda otomatik olarak aşağıdaki java kodlarını oluşturur;

public class OrnekClass {
    public OrnekClass();
}

JVM bir adet boş constructor oluşturdu. OrnekClass isimli sınıfdan bir örnek oluşturduğumuz zaman “Hello” çıktısı alırız. Scala sınıfların kurucularını otomatik oluşturur. Parametreli kurucuya sahip bir class oluşturmak için ise;

class OrnekClass(ad: String, yas: Int) {
    println(ad + " " + yas.toString)
}

JVM derledikten sonra aşağıdakiler oluşur;

public class OrnekClass{
    public OrnekClass(java.lang.String, int);
}

Verdiğimiz parametrelere sahip bir constructor oluştu. new OrnekClass("Martin", 57)ile yeni bir örnek oluşturduğumuz zaman “Martin 57” çıktısını alırız.

class OrnekClass(var ad: String, val yas: Int) {
    println(ad + " " + yas.toString)
}

JVM derledikten sonra aşağıdakiler oluşur;

public class OrnekClass {
    public java.lang.String ad();         // getter
    public void ad_$eq(java.lang.String); // setter
    public int yas();                     // getter
    public OrnekClass(java.lang.String, int);
}

JVM nin oluşturduğu kodu incelersek; bir önceki örneğe kıyasla parametre oluştururken var ve val anahtar kelimelerini kullandık. Bu anahtar kelimeleri kullandığımız zaman JVM getter ve setter metodlarını otomatik oluşturdu. Burda dikkat etmemiz gereken yas değişkeni val ile tanımlandığı için yani sadece okunabildiği için sadece getter metodu oluştu. var ile tanımladığımız ad değişkeninin ise getter ve setter metodları oluştu. Bir önceki örnekte de görüldüğü gibi var veya val kullanılmadığı zaman değerler okunamaz ve değişemez.

Kurucular (Constructors)
Class tanımlarken kurucuyu ayrıca oluşturmaya gerek yoktur. Verdiğimiz parametrelere göre default olarak primary constructor oluşur. Ancak istenirse kurucu overload edilebilir. Farklı bir constructor oluşturalım.

Örnek 1:

class OrnekClass(ad: String, soyad: String) {
    var yas: Int = _

    def this(ad: String, soyad: String, yas: Int) {
        this(ad, soyad)
        this.yas = yas
    }

    def yazdir(): String = {
        "Adınız: " + ad + " Soyadınız: " + soyad + " Yaşınız: " + yas.toString
    }
}

var Ornek1 = new OrnekClass("Martin", "Odersky", 57)
var Ornek2 = new OrnekClass(ad = "Martin", soyad = "Odersky", yas = 57)
Ornek1.yazdir() // Adınız: Martin Soyadınız: Odersky Yaşınız: 57
 
//Kurucu parametreleri girerken iki şekilde de girilebilir ikiside aynı şeydir.

İlk olarak “yas” isminde bir değişken oluşturduk ve _ ile default değer atadık her değişken türünün kendi default değeri vardır. Sonra this özel ismine sahip bir metod oluşturarak kurucu oluşturduk ve parametre olarak yas değişkeni ekledik. Metodun içinde primary constructor çalıştırdık. “yazdir” isminde bir String metod oluşturarak ad, soyad ve yaşı yazdırdık.

Örnek 2:

class Pizza() { 
    var fiyat: Int = 0 
    def this(fiyat: Int) {
        this()
        this.fiyat = fiyat
    }
}
var pizza = new Pizza()
var pizza2 = new Pizza(23)

Getter ve Setter Metodları
Class parametreleri kendi getter ve setter metodlarını otomatik oluştururlar. Eğer get ve set metodlarında farklı işlemler yapmamız gerekiyorsa bu metodları ezebiliriz.

class Pizza(private var fiyat: Int)

var pizza0 = new Pizza(34)
pizza0.fiyat = 30     //Hata 
println(pizza0.fiyat) //Hata 

class Pizza(private var _fiyat: Int) {
    def fiyat = _fiyat
    def fiyat_=(fiyat: Int) { _fiyat = fiyat * 2 }
}
 
var pizza1 = new Pizza(23)
pizza1.fiyat = 26
println(pizza1.fiyat) // 52

Get ve set metodlarını oluşturduk. Görüldüğü gibi set metoduna ufak bi ekleme yaparak girilen fiyatın 2 katını aldık. Burda dikkat edilmesi gereken şey _= operatörüdür. Javadaki gibi klasik getFiyat, setFiyat şeklinde metod da yapabilirdik. Burda direk değişkene ulaşır gibi kullanım yapabilmek için bu özel operatörü kullandık.

Java Araba Sınıfı

public class Araba {
    private String marka;
    private Int    model;

    public Araba(String marka, Int model) {
        this.marka = marka;
        this.model = model;
    }

    public String getModel() {
        return this.model;
    }

    public void setModel(String m) {
        this.model = m;
    }

    public int getMarka() {
        return this.marka;
    }

    public void setMarka(int m) {
        this.marka = m;
    }
}

Scala Araba Sınıfı

class Araba(var marka: String, var model: Int)

Scala ile oluşturduğumuz araba sınıfından bir örnek yaratalım.

var araba1 = new Araba("BMW", 2015)
println(araba1.marka) // BMW
println(araba1.model) // 2015

araba1.marka = "Mercedes"
araba1.model = 2012

println(araba1.marka) // Mercedes
println(araba1.model) // 2012

Class oluştururken parametreleri var veya val anahtar kelimeleri ile oluşturursak parametrelere direk ulaşabiliriz. Eğer ikisinden birini kullanmadan oluşturursak  oluşturduğumuz örnek üzerinden direk ulaşamayız. Onun için özel olarak get ve set metodları oluşturmamız gerekir.

Default Değerler
Oluşturduğumuz classlara default parametre değerleri atayabiliriz. Eğer değer girilmezse bizim atadığımız değer kullanılır.

class Araba(var marka: String, var model: Int = 2010, var renk: String = "Sarı")
var araba1 = new Araba("Fiat")
println(araba1.marka) // Fiat
println(araba1.model) // 2010

var araba2 = new Araba("Renault", renk = "Kırmızı")
println(araba2.marka) // Renault
println(araba2.model) // 2010
println(araba2.renk)  // Kırmızı

araba2 örneğinde görüldüğü gibi default değer kullanımında değerin ismini girerek sırayla girme zorunluluğunu ortadan kaldırdık. renk parametresini girmeden önce model parametre değerini girmemiz gerekiyordu ama parametre ismi kullanarak model girmeden örneğimizi yarattık.

Kalıtım
Scala ile nesnelerde kalıtımın nasıl olduğuyla ilgili bir örnek;

class Araba(renk: String, model: Int) {
    var fiyat: Int = 40000
    def bilgiler(): Unit = println("Renk: " + renk + "Model: " + model.toString)
}

class Bmw(renk: String, model: Int, km: Int) extends Araba(renk, model) {
    def kilometre(): Unit = println("Kilometre: " + km.toString + "Fiyat: " + fiyat.toString)
}

var araba = Bmw("Kırmızı", 2016, 12000)
araba.bilgiler()     // Renk: Kırmızı Model: 2016
araba.kilometre()    // Kilometre: 12000 Fiyat: 40000
araba.fiyat = 30000  // Kilometre: 12000 Fiyat: 30000

Apply Methodu
Classlar ile apply metodunun (default veya injektör metod) kullanımına örnek verelim;

class Kutu(kutuAdi: String) {
    var elemanlar: List[String] = List()
    def apply(e: String): Unit = {
        elemanlar = e :: elemanlar
    }
}

var oyuncakKutusu = new Kutu("Oyuncak")
oyuncakKutusu("Araba")  oyuncakKutusu.apply("Araba")
oyuncakKutusu("Bebek")  oyuncakKutusu.apply("Bebek")

Örnekte görüldüğü gibi apply metodu ile sınıftan oluşturduğumuz örnekte metod ismi kullanmadan direk olarak işlem yapabiliyoruz. İstersek metodun ismini kullanarak da metodu çağırabiliriz. Daha iyi anlaşılması için bir örnek verelim. Scala’nın collections nesnelerinden List i kullanarak zaten kullandığımız bu özelliği daha iyi anlayalım.

var arabalar: List[String] = List("BMW", "MERCEDES")
println(arabalar(0))        // BMW
println(arabalar.apply(0))  // BMW

Yukarıda iki örnekte aynı işi yapıyor gördüğünüz gibi sınıfa apply metodu ile default metod atamış oluyoruz.

Lazy Val
Lazy Val scala ile gelen en önemli özelliklerden birisi. Bu şekilde tanımlanan değişkenler kullanılana kadar bellekte bir yer kaplamazlar. Normalde değişken tanımladığımız zaman kullanmasak bile bellekte boyutu kadar yer kaplarlar. Bir örnek ile açıklayalım;

class LazyOrnek{
  lazy val ad = "Ahmet"
  lazy val soyad = { println("Soyad Tanımlandı"); "Yılmaz" }
}

var ornek = new LazyOrnek
println(ornek.ad)     // Ahmet
println(ornek.soyad)  // Soyad Tanımlandı Yılmaz
println(ornek.soyad)  // Yılmaz

Örnekte gördüğümüz gibi ad değişkenini yazdırınca “Ahmet” değerini aldık. soyad değişkenini yazdırdığımız zaman println fonksiyonu çalıştı ve “Yılmaz” değeri soyad değişkenine atandı ve ekrana yazdırıldı. İkinci çalıştırmada sadece “Yılmaz” değerini aldık. Lazy val ile süslü parantez içinde işlemler yapmak mümkün ancak sadece ilk kullanımda çalışırlar ve atanan değer değişkene eşitlenir. Bundan sonraki kullanımlarda değişken aldığı değeri gösterir başka bir işlem yapmaz.