2011. március 18., péntek

Kódértelmezési példa

Egy pici elgondolkodtató feladat egy interjúról:

Tyre.java:

public class Tyre {

    private int size;

    Tyre(int s) {
        size = s;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

}

Car.java:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Car {

    private List<Tyre> tires = new ArrayList<Tyre>();

    public void setTires(List<Tyre> tires) {
        this.tires.clear();
        this.tires.addAll(tires);
    }

    public void addTire(Tyre tire) {
        this.tires.add(tire);
    }

    public List<Tyre> getTires() {
        return Collections.unmodifiableList(tires);
    }

}

CarTest.java:

import java.util.List;

public class CarTest {

    /**
     * Question: what will be the output?
     */
    public static void main(String[] args) {
        Car car = new Car();
        car.addTire(new Tyre(10));
        car.addTire(new Tyre(11));

        List<Tyre> carTires = car.getTires();
        System.out.println("Before: " + carTires.size());
        car.setTires(carTires);
        System.out.println("After (1): " + car.getTires().size());
        System.out.println("After (2): " + carTires.size());
    }

}

Kérdés: Mi lesz a kimenete a programnak?

Osztályok típusai

A legtöbbet olyan osztályokat használunk, amelyek nem másik osztályba vagy interfészbe vannak beágyazva. Az ilyen osztályt legfelső szintű osztálynak nevezzük (top level class).

A Java nyelv megengedi osztályok deklarálását más osztályokban is, ezeket beágyazott osztályoknak nevezzük (nested class). Ezeknek több fajtája van.

Egy beágyazott osztály kétféle lehet: statikus vagy nem statikus. Az első fajtát egyszerűen csak statikus beágyazott osztálynak nevezzük (static nested class), a második neve pedig belső osztály (inner class). A beágyazott osztály tagja az őt tartalmazó osztálynak, azaz a nem statikus beágyazott osztályok közvetlenül elérik a tartalmazó osztály más tagjait is, a statikusak azonban nem. Míg egy normális, azaz felső szintű osztály csak public vagy package private lehet, addig a beágyazott osztályok mind a négy féle hozzáféréssel rendelkezhetnek: public, protected, private és package private.

Beágyazott osztályok használata

  • logikailag összetartozó osztályok (ha egy osztályt csak egyetlen másik használ, a kettőt egy helyen lehet deklarálni és egyiket a másikba ágyazni)
  • növeli az enkapszulációt (példa: van két osztályunk, az egyiknek el kell érnie a másik adattagjait, melyek egyébként privátként lennének deklarálva - ebben az esetben ha beágyazzuk az osztályt, az adattagok elérhetőek maradnak a beágyazott osztály számára, viszont nem lesznek láthatóak más osztályok számára)
  • könnyebben fenntartható (olvasható) kód

    Statikus beágyazott osztályok

    Mint normális esetben, a statikus beágyazott osztály nem érheti el a nem statikus tagokat (csak ha példányreferencián keresztül teszi azt) és osztályreferencián keresztül hivatkozunk rá.

    TopLevelClass.StaticNestedClass instance = new TopLevelClass.StaticNestedClass();
    

    Belső osztályok

    Egy belső osztály egy példánya a beágyazó osztály példányával együtt - azon belül - él, azaz maga nem hivatkozhat semmilyen statikus tagra, de bármilyen példányváltozóra igen.

    TopLevelClass top = new TopLevelClass();
    TopLevelClass.InnerClass myObject = top.new InnerClass();
    

    Két speciális belső osztályról kell még beszélni, ezek a név nélküli (anonymous classes) és lokális osztályok (local classes).

    Lokális osztályok

    Deklarálhatunk egy belső osztályt egy metóduson belül is. Ezek a lokális (belső) osztályok.

    • a lokális osztály csak azon a blokkon belül használható, ahol definiálták
    • lokális osztály deklarálásakor nem használhatóak a public, protected, private, static kulcsszavak (ezek csak az osztály tagjainál használhatóak, lokális változó vagy osztály deklarálásakor nem)
    • a fentiek miatt statikus tagot / metódust / osztályt sem tartalmazhatnak a lokális osztályok, csak konstansokat, ha az static final - ként van deklarálva
    • a lokális osztály eléri a lokális változókat, metódus paramétereket, kivétel paramétereket is, de akkor és csak akkor, ha azok final - ként vannak deklarálva (Erre az a magyarázat, hogy mivel a lokális osztályok élettartama hosszabb lehet, mint magának a metódusnak a lefutási ideje, a fordító a lokális változókról egy másolatot hoz létre, amit a lokális osztály használni fog. A másolat és a lokális változó közötti azonosságot csak úgy garantálhatjuk, hogy a lokális változó final - ként van deklarálva.)

    Név nélküli osztályok

    Az előzőt megtehetjük úgy is, hogy nem adunk nevet az osztályunknak, az ilyeneket nevezzük anoním / név nélküli belső osztályoknak, ilyenkor az osztály példányosítása és definiálása egyszerre, egy helyen történik.

    • mivel az anoním osztálynak nincs neve, konstruktora sem lehet; ha szükség van konstruktorra, akkor vagy lokális osztályt kell használni, vagy inicializáló blokkot, amit pont erre a helyzetre találtak ki
    • hasonlóan a lokális osztályokhoz, nem használhatóak a public, protected, private, static kulcsszavak és statikus mezőket stb. sem lehet definiálni benne, kivéve ha az final is
    • akkor ajánlott használni, ha az osztályt, amit megadunk, rögtön használjuk is vagy csak egyszer kell használni, esetleg az osztálynak nagyon rövid a megvalósítása.

    Példa név nélküli osztály használatára

    Vegyük pl. a java.io.File osztály list() metódusát, ami kilistázza a könyvtárban lévő fájlokat. Mielőtt azonban visszaadja a listát, egy szűrőn futtatja át azokat, amit nekünk kell megadni paraméterként. Amikor egy ilyen fájl szűrőt adunk meg, akkor gyakorlatilag egy adapter osztályt definiálunk, amely olyan kódot definiál, amit egy másik objektum hív meg. Adapter osztályokat általában célszerű anoním osztályként megadni.

    File myFile = new File("/src");
    String[] listOfFiles = myFile.list(new FilenameFilter() {
      public boolean accept(File f, String s) { return s.endsWith(".java"); }
    });
    



    Tagosztályok (member classes)

    A tagosztály olyan nem statikus osztály,mely közvetlenül egy másik osztályban vagy interfészben van. A tagosztály neve nem lehet ugyanaz, mint a befoglaló osztály neve és nem tartalmazhat statikus változót / metódust / osztályt sem, kivéve, ha az static final.



    2011. március 17., csütörtök

    Absztrakt osztályok és interfészek

    Egy osztályt az abstract kulcsszóval absztrakt osztályként lehet deklarálni, ami annyit jelent, hogy tartalmazhat absztrakt metódusokat. Absztrakt osztályt nem lehet példányosítani, csak más osztályt származtatni belőle, aminek meg kell adnia az esetleges absztrakt metódusok implementációját (ha nem adja meg az implementációkat, akkor a származtatott osztályt is absztraktként kell deklarálni).

    Egy metódust szintén az abstract kulcsszó használatával lehet absztrakt metódusként deklarálni és nem adhat meg implementációt, azaz csak deklarálni kell.

    Absztrakt osztály tartalmazhat static mezőket és/vagy metódusokat is, ezeket ugyanúgy osztályreferencián keresztül lehet elérni, mint normális esetben.

    Absztrakt osztály és interfész közötti különbségek

    • az absztrakt osztály hasonló az interfészhez, de megadhat részleges implementációt, amit a származtatott osztályok egészíthetnek ki
    • ha ansztrakt osztályunk csak absztrakt metódusokat tartalmaz, akkor azt inkább interfészként kell deklarálni
    • az interfészek biztosítják a Java nyelvben a többszörös öröklődést; egy osztály több interfészt is implementálhat egyszerre, függetlenül attól, hogy azok mennyire állnak kapcsolatban egymással (pl. Serializable, Comparable, Cloneable stb.)
    • az absztrakt osztály leszármazott osztályai közös implementációt is tartalmaznak (az absztrakt osztályban nem absztraktként definiált metódusok), de egyedi viselkedést is megadnak (ezek az absztrakt metódusok)
    • absztrakt osztály tartalmazhat nem static és final mezőket is
    • interfészekben minden metódus implicit absztraktként van deklarálva
    Absztrakt osztály mint interfész implementáció

    Ha egy osztály implementál egy interfészt, akkor annak minden egyes metódusát implementálnia kell. Ez alól egyetlen kivétel az absztrakt osztály. A fordító akkor sem fog panaszkodni, ha az interfész metódus meg sem jelenik, mint absztrakt metódus az absztrakt osztályban. Ezeket majd a származtatott osztályokban kell csak implementálni.