B0B36PJV Webové stránky předmětu
Většinu programovacích jazyků je možné rozdělit do dvou kategorií:
Javu je možné zařadit do obou kategorií - program se první zkompiluje do bytecode, který je obecný - není specifický pro danou platformu. Pro tento proces je potřeba JDK (java development kit).
Tento bytecode je možné následně pustit s JRE (java runtime environment- subset JDK), který bytecode za běhu interpretuje.
Proces kompilace a spuštění v Javě:
Java tedy umožňuje program zkompilovat pouze jednou do bytecode a následně spustit na jekékoliv platformě s JRE (i když v praxi to obecně tak jednoduché není - compile once, debug everywhere). Tento přístup vede k rychlosti blížící se kompilovaným jazykům bez nutnosti kompilovat pro každou platformu zvlášť.
JVM (java virtual machine) je obsažen v JRE i JDK a je zodpovědný za překlad bytecode do nativních procesorových instrukcí.
JAR (Java ARchive) je archivní formát pro sdružení více tříd (*.class*) a dalších souborů do jednoho balíčku. Používá se k distribuci a spouštění Java aplikací.
Součástí může být také soubor `MANIFEST.MF`, ve kterém lze určit hlavní třídu programu:
Malý paměťový prostor, ve kterém se typicky ukládají lokální proměnné (což obsahuje i reference, atd). Funguje na principu last-in first-out. Při zavolání funkce se na stacku alokuje místo pro všechny lokální proměnné dané funkce. Implementováno přes stack-pointer, který ukládá adresu konce stacku.
Pro každé volání metody vzniká tzv. aktivní záznam, který obsahuje:
Po ukončení metody se tento záznam odstraní.
Velký paměťový prostor, dynamicky alokovaná paměť. Typicky se zde ukládají objekty a větší datové struktury. Alokována pomocí new. O dealokaci se nestará programátor, ale Garbage Collector (GC).
Heap je sdílený mezi všemi vlákny programu. Objekty zde zůstávají tak dlouho, dokud na ně existuje reference – poté je odstraní GC.
Garbage Collector běží na pozadí JVM. Běží ve vlastním vlákně (nebo i více vláknech), takže neblokuje běh samotného programu (asynchronní). Drží si seznam všech referencí na data na heap a jakmile zjistí, že už žádné reference neexistují, tak data uvolní (dealokuje).
Garbage Collector sice nebude nikdy tak efektivní jako korektní manuální dealokace jako např. v C/C++, ale zjednodušuje psaní programu, jeho udržitelnost a značně snižuje riziko memory leaků kvůli špatným manuálním dealokacím.
JDK a z části i JRE implementuje několik nástrojů pro profiling programu, jako třeba vytížení procesoru a jednotlivých vláken, využití paměti, stav GC.
JDK provádí mnoho optimalizací již při kompilaci bytecode, jako např. eliminace nepoužitého kódu, inlining, atd… Následně probíhá i několik optimalizací při interpretaci programu, jako např. cachování kompilace do nativních procesorových instrukcí.
Java nabízí několik nástrojů pro analýzu výkonu:
Tyto nástroje umožňují najít místa s největším využitím CPU, paměti nebo nejdelším časem běhu.
Objektově orientované programování (OOP) je paradigma, ve kterém modelujeme svět pomocí objektů – entit, které kombinují data a chování. Základními principy OOP jsou: abstrakce, zapouzdření, dědičnost a polymorfismus.
Třída – abstraktní popis objektu, definuje jeho strukturu (atributy) a chování (metody). Objekt – konkrétní instance třídy, která nese data a reaguje na operace definované třídou.
Třída je šablona pro vytváření tříd, objekt je konkrétní instance třídy.
Například:
// class definition public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } } // object instantiation Person person = new Person("John", 30);
Abstrakce – organizujeme koncepty do tříd, skrýváme vnitřní implementaci a vystavujeme jen potřebné rozhraní. Zapouzdření – každý objekt chrání svůj stav a komunikuje s okolím přes veřejné metody.
Modifikátory přístupu v Javě:
Dědičnost – třídám lze předat chování a atributy z nadřazené třídy (`extends`), vytváří hierarchii typů („is-a“ vztah).
Kompozice – třída obsahuje jiné objekty jako své atributy („has-a“ vztah), vhodná pro sdružování funkcionality.
Příklad rozdílu:
// dědičnost class Animal { void speak() { System.out.println("..."); } } class Dog extends Animal { void speak() { System.out.println("Woof"); } } // kompozice class Engine { void start() { ... } } class Car { private Engine engine = new Engine(); void start() { engine.start(); } }
Polymorfismus – schopnost objektu zareagovat různě podle svého konkrétního typu, i když je používán přes obecný typ.
Např. metoda `draw()` může mít jinou implementaci ve třídách `Circle`, `Square`, ale lze je volat skrze rozhraní `Shape`.
Dynamická vazba (late binding) – konkrétní metoda se určuje až v době běhu na základě typu objektu.
Rozdíl:
Rozhraní (interface) definuje sadu metod, které třída musí implementovat (`implements`). Třída může implementovat více rozhraní.
Abstraktní třída (`abstract`) může obsahovat jak implementované, tak neimplementované (abstraktní) metody. Třída ji může pouze dědit (`extends`).
Rozdíly:
Java neumožňuje vícenásobnou dědičnost tříd, aby se předešlo problémům jako je diamond problem – tedy situace, kdy by podtřída dědila stejnou metodu z více nadtříd a nebylo by jasné, kterou implementaci použít.
Místo toho umožňuje vícenásobnou dědičnost rozhraní:
interface A { void doA(); } interface B { void doB(); } class C implements A, B { public void doA() { System.out.println("A"); } public void doB() { System.out.println("B"); } }
Single dispatch znamená, že metoda, která se má zavolat, se vybírá podle *dynamického typu objektu*, na kterém je metoda volána:
class Animal { void speak() { System.out.println("..."); } } class Dog extends Animal { void speak() { System.out.println("Woof"); } } Animal a = new Dog(); a.speak(); // zavolá se Dog.speak() – podle skutečného typu objektu
Double dispatch znamená, že metoda je vybrána na základě *dynamických typů dvou objektů*. V Javě se běžně simuluje pomocí návrhového vzoru Visitor:
interface Visitor { void visit(Dog d); void visit(Cat c); } interface Animal { void accept(Visitor v); } class Dog implements Animal { public void accept(Visitor v) { v.visit(this); } } class Cat implements Animal { public void accept(Visitor v) { v.visit(this); } }
Zde metoda `visit(…)` závisí jak na typu návštěvníka (`Visitor`), tak na konkrétním typu zvířete (`Dog`, `Cat`).
Výčtový typ (angl. enumeration) je datový typ, který může nabývat pouze předem daných hodnot. Zvyšuje čitelnost a typovou bezpečnost programu – místo čísel nebo řetězců použijeme symbolická jména.
Například:
public enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } Day today = Day.MONDAY;
Výčtové typy jsou ve skutečnosti speciální typ třídy – lze do nich přidat:
public enum Suit { CLUBS(Color.BLACK), DIAMONDS(Color.RED), HEARTS(Color.BLACK), SPADES(Color.RED); private Color color; Suit(Color c) { this.color = c; } public Color getColor() { return color; } public boolean isRed() { return color == Color.RED; } }
Java Collections Framework (JCF) je sada rozhraní a tříd pro práci s obecnými datovými strukturami.
Základní vlastnosti:
Základní typy kolekcí:
Příklad:
List<String> names = new ArrayList<>(); names.add("Alice"); names.add("Bob");
Iterátor je objekt, který umožňuje bezpečně procházet kolekce bez znalosti jejich vnitřní struktury.
Použití:
Iterator<String> it = names.iterator(); while (it.hasNext()) { System.out.println(it.next()); }
Zkrácený zápis pomocí for-each:
for (String name : names) { System.out.println(name); }
Rozhraní Iterable umožňuje použít kolekci ve for-each smyčce.
Poznámka: Kolekce nelze bezpečně měnit během iterace jinak než metodou it.remove(). Jinak hrozí ConcurrentModificationException.
Generické typy (generika) umožňují psát obecný, opakovatelný a typově bezpečný kód, který lze aplikovat na různé datové typy bez nutnosti přetypování.
Deklarace:
List<Integer> numbers = new ArrayList<>(); numbers.add(10);
Výhody generik:
Generická metoda:
public static <T> void printAll(List<T> list) { for (T item : list) { System.out.println(item); } }
Poznámka: Generika jsou v Javě implementována pomocí type erasure – typová informace se za běhu ztrácí (např. nelze napsat new T()).
Vnitřní třída (inner class) je třída, která je definovaná uvnitř jiné třídy. Má přístup ke všem členům (včetně privátních) vnější třídy a typicky slouží jako pomocná třída.
Je-li vnitřní třída označena jako static, jedná se o statickou vnitřní třídu, která nemá přístup k instanci vnější třídy.
Výhodou je lepší zapouzdření a přehlednost kódu – vnitřní třídy se používají tam, kde nemá smysl jejich existence samostatně.
class OuterClass { int x = 10; class InnerClass { int y = 5; } }
Anonymní vnitřní třída je bezejmenná třída, která je definována a zároveň instanciována na místě. Používá se tam, kde potřebujeme jednorázově implementovat nějaké rozhraní nebo rozšířit třídu.
Typicky se využívá např. pro callbacky nebo posluchače událostí.
Anonymní třída je třída vnitřní bez názvu, pro kterou je vytvořen pouze jediný objekt.
// Interface interface Age { int x = 21; void getAge(); } class AnonymousDemo { // Main driver method public static void main(String[] args) { // A hidden inner class of Age interface is created // whose name is not written but an object to it // is created. Age oj1 = new Age() { @Override public void getAge() { // printing age System.out.print("Age is " + x); } }; oj1.getAge(); } }
Imutabilní objekt je takový objekt, jehož stav po vytvoření již nelze změnit. To znamená:
Výhody:
Singleton je návrhový vzor, který zajišťuje, že daná třída má právě jednu instanci. Toho se docílí:
public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }
Výjimka (exception) je neočekávaná událost během běhu programu. Znemožňuje dokončení běžného toku programu, pokud není správně ošetřena. Výjimky se typicky používají pro chybové stavy, jako je dělení nulou nebo přístup mimo rozsah pole.
* Všechny výjimky jsou potomky třídy `Throwable`.
catch
, nebo předat dál klíčovým slovem throws
(např. FileNotFoundException
).RuntimeException
a také Error
; kompilátor jejich ošetření nevynucuje (např. NullPointerException
).Výjimky se ošetřují pomocí konstrukcí `try-catch-finally`.
try { // rizikový kód } catch (IOException | SQLException ex) { // multi-catch od Java 7 e.printStackTrace(); // ošetření výjimky log(ex); throw ex; // volitelné předání dál } finally { cleanup(); // provede se vždy }
Od Javy 7 existuje `try-with-resources`, který automaticky zavře prostředky (např. soubory, streamy) implementující `AutoCloseable`.
try (BufferedReader br = Files.newBufferedReader(Path.of("data.txt"))) { return br.readLine(); }
Vlastní výjimky se vytvářejí děděním ze třídy `Exception` nebo `RuntimeException`.
public class MyException extends Exception { public MyException() {} public MyException(String msg) { super(msg); } public MyException(String msg, Throwable cause) { super(msg, cause); } } }
RuntimeException
).* `Throwable` – kořenová třída, zachytitelná, ale neměla by se běžně používat. * `Exception` – standardní ošetřitelné výjimky.
* `Error` – závažné chyby – většinou se nezachytávají (`OutOfMemoryError`, `StackOverflowError`).
Soubor je množina údajů uložená ve vnější paměti. Přístup k němu probíhá pomocí proudů (streamů), které umožňují čtení a zápis dat sekvenčně nebo náhodně. Dělíme je podle typu dat na textové a binární.
try (InputStream in = new FileInputStream("logo.png"); OutputStream out = new FileOutputStream("copy.png")) { byte[] buf = new byte[8192]; int n; while ((n = in.read(buf)) != -1) { out.write(buf, 0, n); } } // oba proudy se zavřou automaticky (try-with-resources)
Nejčastější (a dodnes plně dostačující) kombinací je dvojice ``FileReader`` + ``BufferedReader``:
try (BufferedReader br = new BufferedReader(new FileReader("soubor.txt"))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } // try-with-resources zajistí zavření souboru
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("state.bin"))) { oos.writeObject(gameState); }
Serializable
. serialVersionUID
).Exception
; loguj a přeposílej konkrétní typy. java.nio.file.Path
) – ošetří limity File
a umí asynchronní I/O, ale porozumění java.io
je nutný základ.Soket je objekt, který propojuje aplikaci se síťovým protokolem. Umožňuje síťovou komunikaci mezi dvěma koncovými body (např. klientem a serverem) prostřednictvím definovaných API. V Javě se sokety používají pro komunikaci přes protokoly TCP a UDP.
Třída | Protokol | Povaha | Typická použití |
————————- | —————— | ————————– | ——————————- |
Socket / ServerSocket | TCP | spojované, spolehlivé | chat, přenos souborů |
DatagramSocket | UDP | nespojované, best-effort | streaming, multiplayer hry |
MulticastSocket | UDP multicast | skupinový přenos | discovery, video broadcast |
TCP (Transmission Control Protocol)
UDP (User Datagram Protocol)
// === Server === try (ServerSocket srv = new ServerSocket(9000)) { while (true) { try (Socket s = srv.accept(); BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream())); PrintWriter out = new PrintWriter(s.getOutputStream(), true)) { String line = in.readLine(); out.println("Echo: " + line); } } } // === Klient === try (Socket s = new Socket("localhost", 9000); BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream())); PrintWriter out = new PrintWriter(s.getOutputStream(), true)) { out.println("Ahoj světe"); System.out.println(in.readLine()); }
new ServerSocket(port, backlog)
nastavíš frontu nepřijatých spojení. setSoTimeout()
→ ochrana proti zablokování čtení. java.nio.channels
+ selektory (non-blocking I/O). SSLSocket
/ SSLServerSocket
nebo moderním klientovi java.net.http
. try-with-resources
nebo finally
.Paralelismus umožňuje efektivnější využití systémových prostředků (více jader CPU, paralelní zpracování vstupů, vyšší výkon, reakce na události). V Javě se používá vícevláknové programování pomocí třídy `Thread`, rozhraní `Runnable`, případně thread poolu (`ExecutorService`).
Nutné dodržet pravidla synchronizace, aby se zabránilo deadlocku.
Vlákno lze vytvořit buď děděním od třídy `Thread`, nebo implementací rozhraní `Runnable`:
Objekty jsou odvozené od třídy Thread
, tělo nezávislého výpočtu definujeme v metodě run()
.
public class Worker extends Thread { private final int numberOfJobs; public Worker(int id, int jobs) { super("Worker " + id); myID = id; numberOfJobs = jobs; stop = false; System.out.println("Worker id: " + id + " has been created threadID:" + getId()); } public void run() { doWork(); } } Worker thread = new Worker(1, 10); thread.start(); //new thread is created System.out.println("Program continues here");
Pokud nelze použít dědění od Thread
implementujeme rozhraní Runnable
:
public class WorkerRunnable implements Runnable { private final int id; private final int numberOfJobs; public WorkerRunnable(int id, int jobs) { this.id = id; numberOfJobs = jobs; } public String getName() { return "WorkerRunnable " + id; } @Override public void run() { ... } } WorkerRunnable worker = new WorkerRunnable(1, 10); Thread thread = new Thread(worker, worker.getName()); thread.start(); public void run() { Thread thread = Thread.currentThread(); for (int i = 0; i < numberOfJobs; ++i) { System.out.println("Thread name: " + thread.getName()); } }
Při práci s více vlákny vznikají typické problémy:
Používá se místo ručního vytváření velkého počtu vláken. Efektivnější správa a recyklace.
ExecutorService executor = Executors.newFixedThreadPool(4); executor.submit(new WorkerRunnable(1, 10)); executor.shutdown();