Skúšali ste niekedy spočítať počet MP3jok v adresári? Alebo, ak máte iTunes, M4Ačiek? V Jave je to, ehm, jednoduché:
File folder = new File("c:/Users/rn/Music/iTunes/iTunes Media/Music/David Carbonara/Mad Men_ After Hours (Music from the Ori");
int m4aCount = 0;
for (File file : folder.listFiles()) {
if(file.getName().endsWith(".m4a")) {
m4aCount++;
}
}
System.out.println("Found .m4a files: " + m4aCount);
(Áno, áno, toto nikto nikdy neurobí: v Bashi/Powershelli sú na to jednoriadkové skripty.)
Trieda pre súbory java.io.File
však ponúka aj alternatívnu možnosť: metódu list()
s parametrom FileFilter
. Vráti nám pole súborov File[]
, čo je očividné (listujeme súbory, nie?), ale čo ten parameter?
Kuk do dokumentácie a aha: je to interfejs s jedinou metódou:
public interface FileFilter {
boolean accept(File pathname);
}
Objekt tohto typu rozhodne, či sa konkrétny súbor akceptuje, teda zahrnie do výsledného zoznamu, alebo nie. Inak povedané, filter povie pre každý súbor, či ho berie alebo zamieta. Ak chceme začať filtrovať, potrebujeme dve veci:
- Naklepať vlastnú triedu, ktorá implementuje interfejs
FileFilter
, - Vytvoriť inštanciu tejto triedy.
Potrebujeme triediť M4Ačka? Toť trieda:
public class M4AFileFilter implements FileFilter {
public boolean accept(File pathname) {
return pathname.getName().endsWith(".m4a");
}
}
A toť jej vytváranie a použitie:
FileFilter m4aFilter = new M4AFileFilter();
System.out.println(m4aFilter.accept("01 A Beautiful Mine (Jazz Interpreta.m4a"));
System.out.println(m4aFilter.accept("Digital Booklet - Mad Men, After Hou.pdf"));
Keď máme inštanciu filtra, môžeme ho narvať do metódy list()
:
File folder = new File("c:/Users/rn/Music/iTunes/iTunes Media/Music/David Carbonara/Mad Men_ After Hours (Music from the Ori");
FileFilter m4aFilter = new M4AFileFilter();
int m4aCount = folder.list(m4aFilter).length;
System.out.println("Found .m4a files: " + m4aCount);
Vo vnútri sa stane toto:
Cpt. File: Vitajte, vážené súbory. Je vás tu šestnásť, zoraďte sa a ideme. Hej ty, prvý!
01 A Beautiful Mine (Jazz Interpreta.m4a
! Strč sa do metódyaccept()
!Súbor A Beautiful Mine: Áno, pane!
Cpt. File:
FileFilter
, čo si vrátil?FileFilter:
true
, pane!Cpt. File:: Výborne, Beautiful Mine, marš doľava do výsledného radu! Ďalší! Kto je ďalší?!
"Digital Booklet - Mad Men, After Hou.pdf", strč sa do metódy
accept()`!Súbor A Beautiful Mine: Áno, pane!
Cpt. File:
FileFilter
, čo si vrátil?FileFilter:
false
, pane!Cpt. File:: Marš preč, Digital Booklet atď, marš doprava, nech ťa už nevidím! Ďalší!
Chceme pre zmenu počítať MP3ky? Opäť: nová trieda, nová inštancia a jej použitie:
public class MP3FileFilter implements FileFilter {
public boolean accept(File pathname) {
return pathname.getName().endsWith(".mp3");
}
}
A potom:
FileFilter mp3Filter = new MP3FileFilter();
int mp3Count = folder.list(mp3Filter).length;
System.out.println("Found .mp3 files: " + mp3Count);
Takýchto filtrov môžeme navyrábať koľko chceme a používať podľa nálady, stavu programu a úloh v TODO zozname.
Anonymné vnútorné triedy
Čo bolo napísané vyššie o filtroch? Dva kroky: 1) vytvoriť triedu implementujúcu interfejs 2) vytvoriť jej inštanciu.
V Jave je možné oba kroky vybaviť naraz. Nejedno API totiž spolieha na návrhový vzor, ktorý bolo vidieť v metóde list()
: teda parameter typu interfejs. V prípade jednorazových použití by sa v kóde len kopili kratučké triedy, a čo je horšie, v prípadoch, keď si metóda a interfejsová trieda potrebujú vymieňať údaje, by vznikala kopa zbytočného kódu (čo bude vidieť nižšie).
Najprv si ukážme anonymnú vnútornú triedu filtra MP3jok:
FileFilter mp3Filter = new FileFilter() {
public boolean accept(File pathname) {
return pathname.getName().endsWith(".mp3");
}
};
int count = folder.list(mp3Filter).length;
Čo všetko sa stalo na prvých štyroch riadkoch?
Predovšetkým, vytvárame novú inštanciu FileFilter
a. To je ale interfejs a vieme, že vytvárať objekty interfejsov nie je možné — lebo interfejs je len zoznam hlavičiek metód, bez akéhokoľvek kódu. Keby bolo možné vytvoriť pomocou new
inštanciu interfejsu, úbohý objekt by nevedel, ako sa má správať.
Možno keby sme nejakým hackom vedeli dodať kód k jednotlivým metódam…
Ale to sa deje! Všimnime si kučeravé zátvorky:
new FileFilter() {
...
};
Medzi nimi je kód, ktorý plní rovnakú úlohu ako v deklarácii triedy implementujúcej interfejs! Je tam presne taká istá metóda ako vyššie v ukážke triedy MP3FileFilter
!
Hra “Nájdi dva rozdiely!”:
public class MP3FileFilter implements FileFilter {
public boolean accept(File pathname) {
return pathname.getName().endsWith(".mp3");
}
}
a
... = new MP3FileFilter();
vs:
... = new FileFilter() {
public boolean accept(File pathname) {
return pathname.getName().endsWith(".mp3");
}
};
Táto konštrukcia vytvorila inštanciu anonymnej vnútornej triedy.
Veľký pozor! pozor na to, že za poslednou kučeravou zátvorkou musí byť bodkočiarka! Tá totiž hovorí, že končí riadok, na ktorom sa vytvára nová inštancia. Je to presne ako v prípade
new NIEČONIEČONIEČO();
akurát, že NIEČONIEČONIEČO
je zložité vyjadrenie triedy. Dá sa to predstaviť aj takto:
Anonymná…
Naša trieda je anonymná, lebo nemá meno. Konštrukcia new FileFilter()
hovorí:
Vytvor triedu:
public class ??? implements FileFilter { public boolean accept(File pathname) { return pathname.getName().endsWith(".mp3"); } }
A hneď na to vytvor jej inštanciu:
FileFilter mp3Filter = new ???();
Oba kroky sa uskutočnia naraz. Otázniky ??? hovoria, že “neviem síce ako sa moja trieda volá, ale viem, že implementuje FileFilter
a chcem podľa nej vytvoriť objekt.
…vnútorná…
Trieda je vnútorná z jednoduchého dôvodu: deklarujeme ju vo vnútri inej triedy. Keby sme mali kompletný kód…
public class FileFilters {
public static void main(String[] args) {
FileFilter mp3Filter = new FileFilter() {
public boolean accept(File pathname) {
return pathname.getName().endsWith(".mp3");
}
};
int count = folder.list(mp3Filter).length;
System.out.println(count);
}
}
… videli by sme, že anonymná trieda je vo vnútri triedy FileFilters
.
Anonymná nie je anonymná
Inak, i anonymná vnútorná trieda má nejaké meno. Kompilátor ho priradí na základe mena vonkajšej triedy: napr. po skompilovaní tohto súboru FileFilters.java
vzniknú dva .class
súbory:
FileFilters.class
- a
FileFilters$1.class
: čo znamená „prvá anonymná trieda v triedeFileFilters
.
Ešte „zjednodušená“ syntax
Syntax sa dá ohnúť ešte viac! Deklaráciu premennej môžeme vynechať a všetko rovno napchať do vnútra metódy!
File[] files = folder.list(new FileFilter() {
public boolean accept(File pathname) {
return pathname.getName().endsWith(".mp3");
}
};
);
int count = files.length;
Na hromade to vyzerá katastroficky, ale môžeme to vymaľovať:
Toto rozhodne nie je nejaký hack: používa sa to úplne bežne, uvidíte príklady. Že z toho ide hlava dokola? Táto syntax je druhý najtemnejší kút zápisu v kóde. Permanentná vicemiss Zlokódu od roku 199x. (Prvá sú možno generiká.)
Ďalšie príklady anonymných vnútorných tried
Vytvorenie vlákien
Chcete vytvoriť nové vlákno, teda úlohu bežiacu na pozadí? Tá istá finta!
public class ThreadTest {
public static void main(String[] args) {
Runnable task = new Runnable() {
public void run() {
for(int i = 0; i < 20000; i++) {
System.out.println("Budem študovať Blocha!");
}
}
};
Thread thread = new Thread(task);
thread.start();
}
}
Samozrejme, dalo by sa to aj klasickým spôsobom:
public class HomeworkRunnable implements Runnable {
public void run() {
for(int i = 0; i < 20000; i++) {
System.out.println("Budem študovať Blocha!");
}
}
}
public class ThreadTest {
public static void main(String[] args) {
Runnable runnable = new HomeworkRunnable()
Thread thread = new Thread(runnable);
thread.start();
}
}
Poslucháči/listenery v swingáckych aplikáciách
Anonymná vnútorná trieda je denný chlieb v Swingu. Aha, jedno tlačidlo a jeho obsluha pomocou ActionListener
a:
public class TestFrame extends JFrame {
public TestFrame() {
JButton button = new JButton("Klik!");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Klik!");
}
});
add(button);
pack();
}
}
Interfejs ActionListener
má jedinú metódu, kde je kód obsluhy kliknutia na tlačidlo. Na jeden krok vytvoríme jeho inštanciu, prekryjeme metódu actionPerformed()
a dodáme demo kód.
A keď už hovoríme o Swingu…
Spúšťanie swingáckych aplikácií
Áno, je to divné, ale aj swingácka aplikácia sa musí spustiť v inom vlákne než v hlavnom. Trieda SwingUtilities
má metódu invokeLater()
, ktorá nainicializuje aplikáciu v tom správnom vlákne, ibaže to všetko musíme dodať v … anonymnej vnútornej triede.
Musíme totiž deklarovať objekt typu Runnable
, ktorý pošleme ako parameter:
public class TestFrame extends JFrame {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
public void run() {
TestFrame frame = new TestFrame();
frame.setVisible(true)
}
};
}
}
Opäť ten istý vzor.
Nabudúce
Nabudúce si ukážeme niekoľko trikov s anonymnými vnútornými triedami: ako možno pristupovať k premenným z vonkajšej triedy; finta, kde možno vytvárať podtriedy i z bežných tried a porovnáme to so syntaxou iných jazykov (lebo … vicemiss Zlokód.)
Osobne se s nimi stykam kazdy den. Jejich velke vyuziti je videt napriklad v: Apache Wicket.
Wicket je fakt manifest AVT, tam je to doslova na nich postavene a bez nich ani Hello World :-)
Android ich ma tiez pozehnane.