Seminář 2 KMI/ZP3JV Základy programování 3 (Java)

Objety a třídy

Materiály

Třídy

Každá veřejná třída je umístěna v samostatném souboru, který nese stejné jméno jako třída, tj. třída Foo, je umístěna v souboru Foo.java. Název třídy začíná vždy velkým písmenem. V názvech se nepoužívají "_" (podtržítka).

Jednotlivé třídy jsou umísťovány do balíčků, které představují:

  1. cesty ve struktuře adresářů, např. cz/upol/zp3jv/lecture02/Foo.java
  2. jmenné prostory oddělující jednotlivé třídy
    • plný název třídy je pak cz.upol.zp3jv.lecture01.Foo
    • dvě třídy stejného jména mohou být umístěny v různých jmenných prostorech, např. cz.upol.zp3jv.lecture01.Foo a edu.mit.Foo jsou odlišné třídy

Deklarace a struktura třídy

public class Foo {	
  // deklarace atributu (clenskych promennych) 

  // konstruktory

  // metody
}

Atributy

<viditelnost> <modifikátory> <typ> <jméno> [ = inicializační hodnota ];
Viditelnost:
Modifikátory:
Jméno:
private int foo;
public double answerToUltimateQuestionOfLife = 42.0;
public static final int SPEED_OF_LIGHT = 299792458;
Poznámka:

Metody

<viditelnost> <modifikátory> <návratový-typ> <jméno-metody>([typ-1 argument-1, [ typ-2 argument-2, ... [ typ-N arugment-N ]]]) {
  // tělo metody
}
Viditelnost:
Modifikátory:

Poznámka: Statické metody by měly být používány pouze v opodstatněných případech. Tedy pouze tehdy pokud má smysl volat danou metodu, bez existující instance!

private int add(int a, int b) {
  return a + b;
}

public void printMsg(String msg, int count) {
  for (int i = 0; i < count; i++) {
    System.out.println(msg);
  }
}

Konstruktory

<viditelnost> NazevTridy([typ-1 argument-1, [ typ-2 argument-2, ... [ typ-N arugment-N ]]]) {
  // zavolani rodicovskeho konstruktoru
  super(...); 
	
  // tělo konstruktoru
  // typicky inicializace proměnných
}	

Poznámka: Třída může mít více metod a konstruktorů stejného jména. V takovém případě se rozhodne o tom, která metoda/konstruktor bude zavolána na základě předaných typů argumetů.

public void print(int number) {
  System.out.println("Int: " + number);
}

public void print(String str) {
  System.out.println("String: " + str);
}

Použití tříd a objektů v programu

Vytvoření nové instance třídy se provádí pomocí operátoru new. Např.:

Foo foo = new Foo();

K metodám a proměnným dané třídy se přistupuje pomocí operátoru . Např.:

foo.bar = 42;
foo.baz(42);

Příklad třídy, představující nějaké obecné zvíře:

public class Animal {
	
  private int legs;
  private int age;
	
  public Animal(int legs, int age) {
    super();
    this.legs = legs;
    this.age = age;
  }

  public int getAge() {
    return age;
  }
	
  public void setAge(int age) {
    this.age = age;
  }
	
  public int getLegs() {
    return legs;
  }
}

public class Example1 {
  public static main(String[] args) {
    Animal dog = new Animal(4, 6);
    Animal penguin = new Animal(2, 1);

    System.out.println(dog.getLegs() + penguin.getLegs());
  }
}

Dědičnost

V Javě může třída dědit pouze z jednoho předka. Z pohledu dědičnosti všechny třídy tvoří souvislou hierarchii, kde na vrcholu je třída Object. Dědění se deklaruje pomocí klíčového slova extends a názvu třídy, např.:

public class Bar extends Foo { /*...*/ }.

public class Dog extends Animal {

  public static final int OLD_DOG = 10;
	
  // predpokladame, ze kazdy pes ma ctyri nohy
  public Dog(int age) {
    super(4, age);
  }
	
  // nove pridana metoda
  public void fetch() {
    if (getAge() < OLD_DOG) System.out.println("Your dog is fetching a stick");
    else  System.out.println("Your dog is ignoring you.");
  }
}

public class Example2 {
  public static main(String[] args) {
    Dog dog = new Dog(6);
    Animal penguin = new Animal(2, 1);

    System.out.println(dog.getLegs() + penguin.getLegs());
  }
}

Rozhraní

Absence vícenásobné dědičnosti je řešena pomocí tzv. rozhraní, "tříd", jejichž všechny metody jsou abstraktní. Každá třída pak může implementovat libovolné množství těchto rozhranní. Jednotlivá rozhranní se deklarují podobně jako třídy, s tím rozdílem, že místo klíčového slova class je použito klíčové slovo interface a jednotlivé metody nejsou deklarovány jako abstract. Např.
public interface Foo {
  public void bar();
  public int baz(int a, int b);
}
Implementace daného rozhraní se deklaruje pomocí kličového slova implements.
public class Bar implements Foo { /* ... */ }
Příklad:
public interface Noisy {
  public void makeSound();
}

public class BigDog extends Dog implements Noisy {

  public BigDog(int age) {
    super(age);
  }

  public void makeSound() {
    System.out.println("Haf Haf\n");
  }
}

public class Duck extends Animal implements Noisy {
	
  public Duck(int age) {
    super(2, age);
  }

  public void makeSound() {
    System.out.println("Ga Ga\n");
  }
}

Poznámka: Díky polymorfismu můžeme ke každému objektu přistupovat, jako by se jednalo o některého z jeho rodičů, potažmo by se jednalo o objekt implementující dané rozhranní.

BigDog bigDog = new BigDog(10);
Duck duck = new Duck(3);

bigDog.fetch();
bigDog.makeSound();
duck.makeSound();
 
Noisy noisyAnimal = new BigDog(10);
noisyAnimal.makeSound();

Ověření, že daný objekt je instance nějaké třídy nebo že implementuje nějaké rozhranní jde pomocí operátoru instanceof. Často se používá v kombinaci s přetypováním.

if (noisyAnimal instanceof Dog) 
  ((Dog) noisyAnimal).fetch();

Proměnný počet argumentů

public static int max(int first, int... rest) {

}

Kompilátorem převedeny na public static int max(int first, int[] rest) {}. V praxi je tedy volání max(1,2,3) přeloženo na max(1, new int[] {2, 3}).

Rozhraní v Javě 8

Přestože rozhraní má představovat pouze ,,kontrakt'', jaké metody daná implementace nabízí a nemělo by obsahovat žádné implementované metody, od Javy 8 je možné v rozhraní implementovat metody označené jako static a metody označené jako default. Metody označené jako default představují implicitní implementaci dané metody a mohou být v implementujících třídách překryty. Tyto metody narušují základní myšlenku rozhraní, ale byly zavedeny proto, aby mohlo dojít k rozšíření existujících rozhraní bez narušení kompatibility s existujícími implementacemi.

Úkoly

Soubory neformátované dle konvencí budou vráceny k přepracování!

  1. Implementujte třídu Point představující bod v rovině určený dvěma souřadnicemi. V tříde Point implementujte metodu double distance(Point p) vracející vzdálenost od daného bodu.

  2. Implentujte třídu Line představující úsečku. Implementujte metody: double getLength() a double distance(Point p). Vzdálenost bodu od úsečky berte v tomto případě jako nejmenší vzdálenost mezi bodem a všemi body ležícími na úsečce.

  3. Implementujte třídu Rectangle představující obdelník, který má strany vodorovné s osami X a Y. Třída by měla mít dva konstruktory, jeden vytvářející obdelník pomocí dvou bodů, další vytvářející obdelník na základě bodu, výšky a šířky. Implementujte metodu double getArea() vracející obsah daného obrazce. Implementujte metodu double distance(Point p) vracející vzdálenost od daného bodu. Vzdálenost bodu od obdelníku berte v tomto případě jako nejmenší vzdálenost mezi bodem a všemi body ležícími na stranách obdelníku.

  4. Implementujte třídu Square představující čtverec. Třída by měla mít konstruktor vytvářející objekt na zákládě souřadnice a délky strany. Implementujte metodu double getArea(). Implementujte metodu double distance(Point p) vracející vzdálenost od daného bodu.

  5. Implementujte třídu Circle představující kružnici. Třída by měla mít konstruktor vytvářející objekt na zákládě souřadnice a poloměru. Implementujte metodu double getArea(). Implementujte metodu double distance(Point p) vracející vzdálenost od daného bodu. Vzdálenost bodu od kružnice berte v tomto případě jako nejmenší vzdálenost mezi bodem a všemi body ležícími na kružnici. Vzdálenost nemůže mít zápornou hodnotu!

  6. Třídy z předchozích příkladů upravte tak, aby se díky dědičnosti omezila redundance kódu. Pokud to má opodstatnění, použijte rozhraní.

© Martin Trnečka