Java - Access Modifiers, Static , Method Overloading,Encapsulating

Java - Access Modifiers, Static , Method Overloading,Encapsulating

Arkadaşlar merhaba, bugün kısaca Java'da kapsülleme , static , method overloadin gibi konulara göz atacağız. Umarım faydalı olur.

Encapsulation (Kapsülleme ) Nedir ?

Kısaca bahsetmek gerekirse bilgi ayrıntılarını gizleme ve nesnenin verilerini ve davranışını koruma sürecidir.

Access Modifiers (Erişim Belirleyiciler)

public : Field veya metodlara her yerden erişilir.

private :Dışarıdan erişime tamamen kapalıdır.Sadece içiden bulunduğu sınıftan erişilir.

Default(Package-Private): Sadece paketteki sınıflara açıktır.

protected: Paketteki sınıflar da erişebilir, veya devralan child classlara erişime açıktır.

Private

Private access kolaydır.Yalnızca aynı sınıftaki kodlar private methodları çağırabilir veya private fields erişebilir

public class FatherDuck {

    private String noise = "quack";

    private void quack() {
        System.out.println(noise);
    }

    private void makeNoise() {
        quack();
    }

}

public class Duckling {

    public void makeNoise() {
        FatherDuck duck = new FatherDuck();
        // duck.quack()// DOES NOT COMPILE
    }

}

Default (Package- Private)

Erişim değiştiricisi olmadığında, Java, package private erişimi olan varsayılanı kullanır. Bu, üyenin ayrı paketteki sınıflara "private" olduğu anlamına gelir. Başka bir deyişle, yalnızca paketteki sınıflar buna erişebilir.

package AccessModifiers.DefaultAccess;

public class Duck {

    String noise = "quack";

    void quack() {
        System.out.println(noise); // default access is ok
    }

    private void makeNoise() {
        quack(); // default access is ok
    }

}


package AccessModifiers.DefaultAccess;

public class GoodDuckling {

    public void makeNoise() {
        Duck duck = new Duck();
        duck.quack(); // default access

        System.out.println(duck.noise); // default access
    }

}

Farklı pakette ki bir sınıftan erişmeye çalıştığımız da ise ;

package  AccessModifiers.DefaultAccessExample;

import  AccessModifiers.DefaultAccess.Duck;

public class BadCygnet {

    public void makeNoise() {
        Duck duck = new Duck();
        // duck.quack(); // DOES NOT COMPILE

        // System.out.println(duck.noise); // DOES NOT COMPILE
    }

}

Protected

Paketteki sınıflar erişebilir, veya devralan child classlara erişime açıktır.

package  AccessModifiers.ProtectedExample;

public class Bird {

  protected String text = "floating";

    protected void floatInWater() {
        System.out.println(text);
    }

}
package  AccessModifiers.ProtectedExample;

public class Gosling extends Bird {

    public void swim() {
        floatInWater();
        System.out.println(text);
    }

}
package  AccessModifiers.ProtectedExample;

/// same package as Bird
public class BirdWatcher {

    public void watchBird() {
        Bird bird = new Bird();
        bird.floatInWater(); // calling protected member
        System.out.println(bird.text); // accessing protected member
    }

}

Bird ile aynı pakette değildir ve Bird'den miras almaz.

package  AccessModifiers.ProtectedExample2;

import  AccessModifiers.ProtectedExample.Bird;

public class BirdWatcherFromAfar {

    public void watchBird() {
        Bird bird = new Bird();
        // bird.floatInWater(); // DOES NOT COMPILE
        // System.out.println(bird.text); // DOES NOT COMPILE
    }

}

helpGooseSwim() methodunda Goose, Bird'ü extend eder. Goose alt sınıfında olduğumuz ve bir Goose referansına atıfta bulunduğumuz için, protected üyelere erişebilir.

package  AccessModifiers.ProtectedExample2;

import    AccessModifiers.ProtectedExample.Bird;

public class Goose extends Bird {

    public void helpGooseSwim() {
        Goose other = new Goose();
        other.floatInWater();
        System.out.println(other.text);

    }

    public void helpOtherGooseSwim() {
        Bird other = new Goose();
        // other.floatInWater();
        // System.out.println(other.text); // DOES NOT COMPILE

    }

}

helpOtherGooseSwim methodunda ise nese bir Goose olsa da, Bird referansında saklanır. Aynı pakette olmadığımız ve other'ın referans tipi Goose'un bir alt sınıfı olmadığı için Bird sınıfının üyelerine atıfta bulunmamıza izin verilmemektedir.

Static

Statik anahtar sözcük bir variable'a, method'a veya class'a uygulandığında,sınıfın belirli bir instance'ı yerine sınıfa uygulanır.

Statik methodlar için , sınıfın bir instance'ı gerektirmez. Sınıfın tüm kullanıcıları arasında paylaşılırlar. Statik bir değişkeni, o sınıfın herhangi bir instance'dan bağımsız olarak var olan tek bir sınıf nesnesinin üyesi olarak düşünebilirsiniz.

  • static değişkenler nesnelerin ortak durumunu oluştururlar.
  • static bir değişkenin tek bir kopyası dolayısıyla da tek bir değeri vardır.
  • static değişkenlere ulaşma için nesneye ihtiyaç yoktur , sınıfın üzerinden ulaşılırlar.

ClassName.staticAttribute

nesne ile ulaşabiliriz

reference.staticAttribute

public class Employee {

    public static int count = 0;

    public static void main(String[] args) {
        System.out.println(count);

        Employee e = new Employee();
        System.out.println(e.count);
        e = null;
        System.out.println(e.count); // e is still a Employee

        e.count = 5;
        Employee e1 = new Employee();
        System.out.println(e1.count);

        e.count = 6;
        System.out.println(count);

        Employee e2 = new Employee();
        e2.count = 7;

        System.out.println(Employee.count);
    }
}

Instance variables erişmeye gerek olmadığından, statik metodlara sahip olmak, kullanıldığında yalnızca methodu çağırmak için bir nesneyi başlatma ihtiyacını ortadan kaldırır.

Static ve Instance

Statik bir üye, sınıfın bir instance'na başvurmadan bir instance üyeyi çağıramaz.

public class Static {

private String name = "Static class";

public static void first() {
}

public static void second() {
}

public void third() {
    System.out.println(name);
    // static eklenir veya
     new Static().third(); //olarak çağırılabilir.
}

public static void main(String[] args) {
    first();
    second();
    // third(); // DOES NOT COMPILE
}

}

Statik methodlar kullanmak için bir nesne gerektirmediğinden,statik bir method veya instance method statik bir methodu çağırabilir.

instance method bir nesne gerektirdiğinden, yalnızca bir instance methodu, bir reference variable kullanmadan aynı sınıfta başka bir instance methodu çağırabilir.

Benzer mantık, instance ve statik değişkenler için de geçerlidir.

public class Lion {

    public void eat(Lion l) {
    }

    public void drink() {
    }

    public static void allLionGoHome(Lion l) {
    }

    public static void allLionComeOut() {
    }

    public static void main(String[] args) {
        Lion l  = new Lion();

        allLionComeOut();
        allLionGoHome(l);

        l.drink();
        l.eat(l);
    }
}

statik bir variable bir instance değişkeni kullanamaz.

public int total;
public static double average = total / count; // DOES NOT COMPILE

Hem statik hem de instance methodlar statik bir değişkene başvurabileceğinden incrementAge ve age count'u çağırabiliyor.

public static int age = 0;

public static void incrementAge(){
        age++;
}

public void age(){
        age++;
}

instance methodu statik bir methodu çağırabilir.

public void babyLion(Lion l){
        allLionComeOut();
        allLionGoHome(l);
    }

static bir method bir instance methodunu çağıramaz.

public static void allLionGoHome(Lion l) {
        eat(); // DOES NOT COMPILE

 }

Static Değişkenler

Sınıf ilk yüklendiğinde çalıştırılmaları gerektiğini belirtmek için static anahtar sözcüğünü eklenir.

Tüm statik başlatıcılar, sınıfda tanımlandıkları sırayla ilk kez kullanıldığında çalışır.

Bazı statik değişkenler, program çalışırken değişmek içindir. counters bunun yaygın bir örneğidir. Zamanla sayının artmasını istiyoruz. instance variables'da olduğu gibi, bildirildiği satırda statik bir değişken başlatabilirsiniz.

private static int counter = 0; // initialization

Bazı static üyeler program çalıştığında asla değişmemek içindir ve final olarak tanımlanır.Bunlara Constant denir.

private static final int NUM_BUCKETS = 45;

// NUM_BUCKETS = 5; // DOES NOT COMPILE
private static final ArrayList<String> values = new ArrayList<>();
values.add("changed");

Değerler bir referans değişkeni olduğu için aslında derleme yapar. Referans değişkenler üzerinde metot çağırmamıza izin verilir. Derleyicinin yapabileceği tek şey, farklı bir nesneye işaret etmek için final değerleri yeniden atamaya çalışmadığımızı kontrol etmektir.

private static final int NUM_SECONDS_PER_MINUTE;
private static final int NUM_MINUTES_PER_HOUR;
private static final int NUM_SECONDS_PER_HOUR;

static {
  NUM_SECONDS_PER_MINUTE = 60;
  NUM_MINUTES_PER_HOUR = 60;
}

static {
        NUM_SECONDS_PER_HOUR = NUM_SECONDS_PER_MINUTE * NUM_MINUTES_PER_HOUR;
}

one : final olmayan statik bir değişken bildirir. İstediğimiz kadar atanabilir.

two :final değişken bildirir Bu, statik bir blokta tam olarak bir kez başlatabileceğimiz anlamına gelir.

four : Hiçbir zaman başlatılmayan bir final değişken bildirir. Derleyici, değişkenin başlatılabileceği tek yerin statik bloklar olduğunu bildiği için derleyici hatası verir.

private static int one; 

private static final int two; 

private static final int three = 3;

private static final int four; // DOES NOT COMPILE

static {
 one = 1;
 two = 2;
 three = 3; // final bir değişken bildirir ve aynı anda onu başlatır. 
//Onu yeniden atamamıza izin verilmiyor, bu nedenle satır derlenmiyor.

 two = 4; // satır derlenmiyor çünkü bu ikinci deneme.
}

Static Imports

Statik import adı verilen başka bir içe aktarma türü vardır. Statik importlar sınıfların statik üyelerini içe aktarmak içindir.

import static java.util.Arrays; // DOES NOT COMPILE 
//Statik importların yalnızca statik üyeleri içe aktarmak için olduğunu unutmayın.

import static java.util.Arrays.asList; // static import

static import java.util.Arrays.*;     // DOES NOT COMPILE

Aynı ada sahip iki methodun veya aynı ada sahip iki statik değişkenin statik içe aktarımını açıkça yapmaya çalışırsanız, derleyici şikayet edecektir.

import static statics.A.TYPE;
import static statics.B.TYPE; // DOES NOT COMPILE

Pass By Value

Java bir “pass-by-value” dilidir. Bu,değişkenin bir kopyasının yapıldığı ve methodun bu kopyayı aldığı anlamına gelir. Method'da yapılan atamalar (assigments) çağıranı etkilemez.

public static void main(String[] args) {
    int num = 4;  // num'a 4 assing edildi.
    newNumber(num);  // method çağrıldı

  System.out.println(num); // 4
}

public static void newNumber(int num) {
  num = 8;
}

Method içindeki parametre 8'e ayarlandı.Main methodu içindeki num değişkeni asla değişmez çünkü ona hiçbir atama yapılmaz.

public static void main(String[] args) {
        String name = "Umut";
        speak(name);
        System.out.println(name);
}

public static void speak(String name) {
        name = "Sparky";
}

// Umut

Doğru cevap Umut'dur. primitive örneğinde olduğu gibi, değişkenin atanması (variable assignment) yalnızca method parametresine yapılır ve çağrı yapanı etkilemez.

public static void main(String[] args) {
    StringBuilder name = new StringBuilder();
    speak(name);
    System.out.println(name); // Webby
}

public static void speak(StringBuilder s) {
        s.append("Webby");
}

// Webby

s değişkeni, değişken adının bir kopyasıdır. Her ikisi de aynı StringBuilder'a işaret eder; bu,StringBuilder'da yapılan değişikliklerin her iki referansta da mevcut olduğu anlamına gelir.

name'i farklı bir nesneye yeniden atamaz.

Return Value

Bir methodda veri geri almak daha kolaydır.

İlkel(primitive) veya referansın bir kopyası yapılır ve method'dan döndürülür.

Çoğu zaman, bu döndürülen değer kullanılır. Örneğin, bir değişkende saklanabilir.

public static void main(String[] args) {

  int number = 1; // number 1
  String letters = "abc";
  number(number);
  letters = letters(letters);

 System.out.println(number + letters);

}

public static int number(int number) {
        number++; // sayı 1 arttırılır fakat main method'da hala 1'dir.
        return number;
}

public static String letters(String letters) {
        letters += "d";
        return letters;
}

letters = letters(letters); method çağrısı sonucu yok saymaz, bu nedenle harfler "abcd" olur.

Bunun, method parametresi değil, döndürülen değer nedeniyle olduğunu unutmayın.

Method Overloading

Method aşırı yüklemesi (Method Overloading), methodlar aynı isme ancak farklı method imzalarına sahip olduğunda meydana gelir

Bu method parametrelerine göre farklılık gösterdikleri anlamına gelir.

Overloading ayrıca farklı sayıda parametreye izin verir.

Method adı dışındaki her şey aşırı yüklenmiş methodlar için değişebilir.

Bu farklı access modifiers(erişim belirteçleri , statik gibi), return type ve exception list olabileceği anlamına gelir.

Hem int hemde Integer olarak overload method var ise; Java, int numMiles methodu ile eşleşir.

Java bulabildiği en spesifik parametre listesini kullanmaya çalışır.

primitive int tipi mevcut değilse, autobox'ing yapılır. Ancak, primitive int tipi sağlandığında, Java'nın fazladan autoboxing işi yapması için hiçbir neden yoktur.

public void fly(int numMiles) {
        System.out.println("int");
}

public void fly(Integer numMiles) {
}

public void fly(short numFeet) {
        System.out.println("short");
}

public boolean fly() {
        return false;
}

void fly(int numMiles, short numFeet) {
}

public void fly(short numFeet, int numMiles) throws Exception {
}

Aşağıdaki örnekte Java'nın varargs'lara bir diziymiş gibi davrandığını unutmayın.

Bu, method imzasının her iki method için de aynı olduğu anlamına gelir.

Aynı parametre listesi ile metotları aşırı yüklemeye iznimiz olmadığı için bu kod derlenmez. Kod aynı görünmese de aynı parametre listesine derlenir.

public void fly(int[] lengths) { }

public void fly(int... lengths) {} // DOES NOT COMPILE
public static void main(String[] args) {
    Primitive p = new Primitive();
    p.fly(123);
    System.out.print("-");
    p.fly(123L);

    // int-long
}
public void fly(int i) {
        System.out.print("int");
}

public void fly(long l) {
        System.out.print("long");
}

public void fly(int numMiles) {}
public int fly(int numMiles) {} // DOES NOT COMPILE
// parametre listesi aynı.

Reference Type

Cevap string-object'dir İlk çağrı bir String'dir doğrudan bir eşleşme bulur.

Sadece çağrılmayı bekleyen güzel bir String parametre listesi olduğundan nesne tipini kullanmak için hiçbir neden yoktur.

İkinci çağrı bir int parametre listesi arar. Bir tane bulamayınca otomatik olarak Integer'a geçer. Hala bir eşleşme bulamadığı için Object'ye gidiyor.

OverloadingReferenceTyoe r = new OverloadingReferenceTyoe();
r.fly("test");
 System.out.print("-");
 r.fly(56);


public void fly(String s) {
        System.out.print("string");
}

public void fly(Object o) {
        System.out.print("object");
}
public static void print(Iterable i) {
        System.out.print("I");
}

public static void print(CharSequence c) {
        System.out.print("C");
}

public static void print(Object o) {
        System.out.print("O");
}

print("abc");
print(new ArrayList<>());
print(LocalDate.of(2022 Month.AUGUT, 13));

Cevap CIO'dur. ilk çağrı bir String iletir.

String ve StringBuilder, CharSequence arayüzünü uygular.

print() için yapılan ikinci çağrı bir ArrayList'i iletir.

print() için yapılan son çağrı bir LocalDate iletir.Açıkça bir karakter dizisi ya da içinden geçilecek bir şey değil. Bu,Object method imzası kullanıldığı anlamına gelir.

Generics

public void walk(List<String> strings) {}
public void walk(List<Integer> integers) {} // DOES NOT COMPILE

Java, jeneriklerin yalnızca derleme zamanında kullanıldığı tür silme (erasure) adı verilen bir konsepte sahiptir. Bu, derlenmiş kodun şöyle göründüğü anlamına gelir:

public void walk(List strings) {}
public void walk(List integers) {} // DOES NOT COMPILE

Aynı method imzasına sahip iki methodumuz olamaz, bu nedenle bu derlenmez. Method overloading'de, method parametrelerinden en az birinde farklı olması gerektiğini unutmayın.

Arrays

Dizilerin gerçek türlerini belirttiğimiz için tip silme işlemi geçerli değildir.

public static void walk(int[] ints) {
}

public static void walk(Integer[] integers) {
}

Örnek


public static String glide(String s) {
        return "1";
}

public static String glide(String... s) {
        return "2";
}

public static String glide(Object o) {
        return "3";
}

public static String glide(String s, String t) {
        return "4";
}

public static void main(String[] args) {
        System.out.print(glide("a"));
        System.out.print(glide("a", "b"));
        System.out.print(glide("a", "b", "c"));

        // 142
}

142 yazdırır. İlk çağrı, en belirgin eşleşme olduğu için tek bir String alarak imzayla eşleşir.

İkinci çağrı, tam bir eşleşme olduğu için iki String parametresi alarak imzayla eşleşir.

Daha iyi eşleşme olmadığı için varargs sürümünün kullanılması üçüncü çağrıya kadar olmaz.

public static void play(Long l) {
}

public static void play(Long... l) {
}

public static void main(String[] args) {
        // play(4); // DOES NOT COMPILE
        play(4L); // calls the Long version
}

Java, int 4'ü bir long 4'e veya bir Integer 4'e dönüştürmekten mutluluk duyar.

Bir long'a ve ardından bir Long'a dönüştürmeyi kaldıramaz.

Eğer public static void play(Object o) {} olsaydı, eşleşirdi çünkü int'den Integer'a sadece bir dönüşüm gerekli olurdu.

Bir değişken primitive değilse,bir Object'dir.

Kapsülleme (Encapsulating)

Örnekte ki instance variable'ı private olarak kullanarak sadece bu sınıf içinden çağırılmasını sağladık.

instance variable'a get ve set methodları üzerinden ulaşılabilir kıldık ve yeni bir numberEggs ekleneceği zaman bunun kontrolünü sağlayarak geçersiz herhangi bir değer olmaması için kontrol ettik.

private int numberEggs; // instance variable

public int getNumberEggs() {
        return numberEggs;
}

public void setNumberEggs(int newNumber) {

        if (newNumber >= 0) // guard condition
            numberEggs = newNumber;
}

get vet set methodları zorunlu değildir.İyi bir kapsülleme için data'nın dışarıdan direkt olarak değiştirilmemesi konusunda dikkatli olmak gerekir.

private int numEggs;

public void layEgg() {
        numEggs++;
}

public void printEggCount() {
        System.out.println(numEggs);
}