Java apstraktne klase: jedan članak je dovoljan da vam sve bude jasno
U ovom članku upoznaćemo se sa apstraktnim klasama u Javi.
O apstraktnim klasama možemo razmišljati kao o „nedovršenom” pisanju, poput nacrta.
„Haha, jeste, želim svim mlađim kolegama i koleginicama da pružim celokupan sistem učenja Jave. Zapamtite naš slogan: za učenje Jave idite na Ergeovu stazu napredovanja u Javi“, bio sam pun poverenja u budućnost.
Pa da krenemo.
01. Definisanje apstraktne klase
Prilikom definisanja apstraktne klase potrebno je koristiti ključnu reč abstract, koja se stavlja ispred ključne reči class, kao u primeru ispod.
abstract class AbstractPlayer {
}Što se tiče imenovanja apstraktnih klasa, u „Alijevom priručniku za Java razvoj“ se ističe: „Imena apstraktnih klasa treba da počinju sa Abstract ili Base“. Ovo pravilo je vredno poštovanja kako bi ime zaista odražavalo suštinu.
02. Karakteristike apstraktne klase
Apstraktna klasa ne može da se instancira. Ako pokušate da je instancirate ključnom reči new, kompajler će prijaviti grešku i obavestiti vas da „je klasa apstraktna i ne može da se instancira“.

Iako se apstraktna klasa ne može instancirati, može imati potklase. Potklasa nasleđuje apstraktnu klasu ključnom reči extends, kao u primeru ispod.
public class BasketballPlayer extends AbstractPlayer {
}Ako jedna klasa definiše jednu ili više apstraktnih metoda, onda ta klasa mora biti apstraktna.
Kada pokušamo da definišemo apstraktnu metodu u običnoj klasi, kompajler prijavljuje greške na dva mesta. Prva greška je na nivou klase, sa porukom da „ova klasa mora biti definisana ključnom reči abstract“, kao na slici ispod.

Druga greška je na pokušaju definisanja metode sa modifikatorom abstract, sa porukom da „klasa u kojoj se nalazi apstraktna metoda nije apstraktna“, kao na slici ispod.

U apstraktnoj klasi mogu se definisati kako apstraktne metode, tako i obične metode, kao u primeru ispod:
public abstract class AbstractPlayer {
abstract void play();
public void sleep() {
System.out.println("I sportisti moraju da odmaraju, a ne da uvek testiraju granice");
}
}Potklasa izvedena iz apstraktne klase mora implementirati apstraktne metode definisane u nadklasi. Na primer, u apstraktnoj klasi AbstractPlayer definisana je metoda play(), pa je u potklasi BasketballPlayer moramo implementirati.
public class BasketballPlayer extends AbstractPlayer {
@Override
void play() {
System.out.println("Ja sam Wilt Chamberlain, postigao sam 100 poena na košarkaškom terenu");
}
}Ako je ne implementirate, kompajler će prijaviti da „potklasa mora implementirati apstraktnu metodu“, kao na slici ispod.

03. Scenariji upotrebe apstraktne klase
Apstraktne metode su jasne, a kada da ih koristimo? Slede scenariji upotrebe.
„Pitanje je postavljeno baš kako treba!“ popravio sam naočare i nastavio.
01) Prvi scenario
Kada želimo da neku opštu funkcionalnost koristi više potklasa, možemo upotrebiti apstraktnu klasu. Na primer, u apstraktnoj klasi AbstractPlayer postoji obična metoda sleep() koja ukazuje na to da svi sportisti moraju da odmaraju — takvu metodu potklase mogu da koriste.
abstract class AbstractPlayer {
public void sleep() {
System.out.println("I sportisti moraju da odmaraju, a ne da uvek testiraju granice");
}
}Potklasa BasketballPlayer nasleđuje klasu AbstractPlayer:
class BasketballPlayer extends AbstractPlayer {
}Time dobija i metodu sleep(). Objekat klase BasketballPlayer može neposredno pozvati metodu nadklase sleep():
BasketballPlayer basketballPlayer = new BasketballPlayer();
basketballPlayer.sleep();Potklasa FootballPlayer nasleđuje klasu AbstractPlayer:
class FootballPlayer extends AbstractPlayer {
}I ona dobija metodu sleep(), pa objekat klase FootballPlayer takođe može neposredno pozvati metodu nadklase sleep():
FootballPlayer footballPlayer = new FootballPlayer();
footballPlayer.sleep();Zar nije na taj način ostvareno ponovno iskorišćavanje koda?
02) Drugi scenario
Kada u apstraktnoj klasi želimo da definišemo API, a u potklasama da proširimo implementaciju, možemo upotrebiti apstraktnu klasu. Na primer, u apstraktnoj klasi AbstractPlayer definisana je apstraktna metoda play() koja ukazuje na to da svaki sportista može da se bavi nekim sportom, ali odgovarajuća potklasa treba da proširi implementaciju — košarkaš igra košarku, fudbaler igra fudbal.
abstract class AbstractPlayer {
abstract void play();
}BasketballPlayer nasleđuje klasu AbstractPlayer i proširuje sopstvenu metodu play().
public class BasketballPlayer extends AbstractPlayer {
@Override
void play() {
System.out.println("Ja sam Wilt Chamberlain, postigao sam 100 poena na košarkaškom terenu");
}
}FootballPlayer nasleđuje klasu AbstractPlayer i proširuje sopstvenu metodu play().
public class FootballPlayer extends AbstractPlayer {
@Override
void play() {
System.out.println("Ja sam Cristiano Ronaldo, mogu da primim loptu glavom sa bilo koje visine");
}
}Da bismo dodatno prikazali osobine apstraktne klase, pogledajmo još jedan konkretan primer.
PS: U komentaru na sajtu se navodi da primer obuhvata čitanje i pisanje fajlova i nove funkcije Jave 8, pa nije pogodan za apsolutne početnike. Ako vam deluje preteško, preskočite ga i vratite mu se nakon što savladate IO tokove. Ako pak ponavljate osnove Jave, neće biti problema.
Pretpostavimo da postoji fajl čiji je sadržaj veoma jednostavan — samo „Hello World“. Sada nam treba čitač koji će pročitati sadržaj iz fajla, najbolje tako da može čitati velikim ili malim slovima.
U tom slučaju najbolje je definisati apstraktnu klasu BaseFileReader:
/**
* Apstraktna klasa koja definiše osnovni okvir za čitanje fajla; mapFileLine je apstraktna metoda čiju konkretnu implementaciju treba da obezbedi potklasa
*/
abstract class BaseFileReader {
protected Path filePath; // definisan je protected objekat tipa Path koji predstavlja putanju fajla koji se čita
/**
* Konstruktor kojem se prosleđuje putanja fajla koji se čita
* @param filePath putanja fajla koji se čita
*/
protected BaseFileReader(Path filePath) {
this.filePath = filePath;
}
/**
* Metoda koja čita fajl i vraća listu stringova
* @return lista stringova koja predstavlja sadržaj fajla
* @throws IOException ako čitanje fajla ne uspe, baca ovaj izuzetak
*/
public List<String> readFile() throws IOException {
return Files.lines(filePath) // metodom lines klase Files čita se svaka linija fajla
.map(this::mapFileLine) // na svaku liniju se primenjuje metoda mapFileLine, čime se pretvara u željeni format
.collect(Collectors.toList()); // obrađene linije se prikupljaju u listu stringova koja se vraća
}
/**
* Apstraktna metoda koju potklasa treba da implementira; pretvara svaku liniju fajla u željeni format
* @param line jedna linija fajla
* @return transformisani string
*/
protected abstract String mapFileLine(String line);
}filePath je putanja fajla i modifikovana je sa protected, što znači da joj potklase po potrebi mogu pristupiti.
Metoda
readFile()služi za čitanje fajla; u telu metode poziva se apstraktna metodamapFileLine()— potklase treba da prošire njenom implementacijom radi različitih načina čitanja (velika ili mala slova).
Po mom mišljenju, klasa BaseFileReader je vrlo razumno projektovana i laka za proširenje — potklasa se može usredsrediti isključivo na konkretnu implementaciju velikih ili malih slova.
Način sa malim slovima:
class LowercaseFileReader extends BaseFileReader {
protected LowercaseFileReader(Path filePath) {
super(filePath);
}
@Override
protected String mapFileLine(String line) {
return line.toLowerCase();
}
}Način sa velikim slovima:
class UppercaseFileReader extends BaseFileReader {
protected UppercaseFileReader(Path filePath) {
super(filePath);
}
@Override
protected String mapFileLine(String line) {
return line.toUpperCase();
}
}Kod za čitanje sadržaja iz fajla liniju po liniju ponovo iskorišćen je u potklasama. Istovremeno, potklasa se bavi samo svojim poslom — LowercaseFileReader čita sadržaj malim slovima, a UppercaseFileReader velikim slovima.
Pogledajmo test klasu FileReaderTest:
public class FileReaderTest {
public static void main(String[] args) throws URISyntaxException, IOException {
URL location = FileReaderTest.class.getClassLoader().getResource("helloworld.txt");
Path path = Paths.get(location.toURI());
BaseFileReader lowercaseFileReader = new LowercaseFileReader(path);
BaseFileReader uppercaseFileReader = new UppercaseFileReader(path);
System.out.println(lowercaseFileReader.readFile());
System.out.println(uppercaseFileReader.readFile());
}
}U direktorijumu resource projekta napravite tekstualni fajl pod imenom helloworld.txt, čiji je sadržaj „Hello World“. Pozicija fajla prikazana je na slici ispod — razvojno okruženje koje koristim jeste IntelliJ IDEA.

Fajlove u direktorijumu resource možete dobiti preko ClassLoader.getResource() u obliku URI putanje, a zatim pristupiti tekstualnom sadržaju.
Izlaz je sledeći:
[hello world]
[HELLO WORLD]04. Rezime apstraktnih klasa
Dobro, da ukratko sumiramo apstraktne klase:
- Apstraktna klasa ne može da se instancira.
- Apstraktna klasa treba da ima barem jednu apstraktnu metodu, inače nema nikakvog smisla.
- Apstraktna metoda u apstraktnoj klasi nema telo metode.
- Potklasa apstraktne klase mora pružiti konkretnu implementaciju apstraktnih metoda iz nadklase, osim ako i sama potklasa nije apstraktna.
„Gotovo?“ još je bilo zadubljeno u radost slušanja.
Jeste, ovaj put smo sistematski naučili apstraktne klase i mogu se reći da smo obuhvatili sve aspekte. Možeš da otkucaš kod kako bi utvrdio utisak.
„Hmmm...“ veliki oblak dima se raširio — opet jedan lep dan.
