Interfețe
Mecanismul de moștenire este foarte convenabil, dar are limitările sale. În special, putem moșteni doar de la o singură clasă, spre deosebire de limbajul C++, de exemplu, unde există moștenire multiplă.
În limbajul Java, această problemă este parțial rezolvată prin interfețe. Interfețele definesc o funcționalitate fără o implementare concretă, care este apoi implementată de clasele ce aplică aceste interfețe. O clasă poate aplica mai multe interfețe.
Pentru a defini o interfață, se folosește cuvântul cheie interface. De exemplu:
interface Printable {
void print();
}
Această interfață se numește Printable. Interfața poate defini constante și metode, care pot avea sau nu implementare. Metodele fără implementare sunt similare cu metodele abstracte din clasele abstracte. În acest caz, este declarată o metodă fără implementare.
Toate metodele unei interfețe nu au modificatori de acces, dar, de fapt, implicit accesul este public, deoarece scopul interfeței este să definească funcționalitatea pentru a fi implementată de o clasă. Prin urmare, întreaga funcționalitate trebuie să fie deschisă pentru implementare.
Pentru ca o clasă să aplice o interfață, trebuie folosit cuvântul cheie implements:
public class Program {
public static void main(String[] args) {
Book b1 = new Book("Java. Complete Reference", "H. Shildt");
b1.print();
}
}
interface Printable {
void print();
}
class Book implements Printable {
String name;
String author;
Book(String name, String author) {
this.name = name;
this.author = author;
}
public void print() {
System.out.printf("%s (%s) \n", name, author);
}
}
În acest exemplu, clasa Book implementează interfața Printable. Dacă o clasă aplică o interfață, aceasta trebuie să implementeze toate metodele interfeței, ca în exemplul de mai sus unde metoda print este implementată. Dacă o clasă nu implementează toate metodele interfeței, atunci acea clasă trebuie definită ca fiind abstractă, iar clasele derivate neabstracte trebuie să implementeze aceste metode.
În același timp, nu putem crea direct obiecte de interfețe, astfel codul următor nu va funcționa:
Printable pr = new Printable();
pr.print();
Unul dintre avantajele utilizării interfețelor este că ele adaugă flexibilitate aplicației. De exemplu, pe lângă clasa Book, vom defini o altă clasă care implementează interfața Printable:
class Journal implements Printable {
private String name;
String getName() {
return name;
}
Journal(String name) {
this.name = name;
}
public void print() {
System.out.println(name);
}
}
Clasa Book și clasa Journal sunt legate prin faptul că implementează interfața Printable. Prin urmare, în program putem crea dinamic obiecte Printable ca instanțe ale ambelor clase:
public class Program {
public static void main(String[] args) {
Printable printable = new Book("Java. Complete Reference", "H. Shildt");
printable.print(); // Java. Complete Reference (H. Shildt)
printable = new Journal("Foreign Policy");
printable.print(); // Foreign Policy
}
}
interface Printable {
void print();
}
class Book implements Printable {
String name;
String author;
Book(String name, String author) {
this.name = name;
this.author = author;
}
public void print() {
System.out.printf("%s (%s) \n", name, author);
}
}
class Journal implements Printable {
private String name;
String getName() {
return name;
}
Journal(String name) {
this.name = name;
}
public void print() {
System.out.println(name);
}
}
Interfețe în conversia tipurilor
Tot ceea ce s-a spus despre conversia tipurilor este valabil și pentru interfețe. De exemplu, deoarece clasa Journal implementează interfața Printable, variabila de tip Printable poate stoca o referință la un obiect de tip Journal:
Printable p = new Journal("Foreign Affairs");
p.print();
// Interfața nu are metoda getName, este necesară o conversie explicită
String name = ((Journal)p).getName();
System.out.println(name);
Dacă dorim să accesăm metodele clasei Journal, care nu sunt definite în interfața Printable, trebuie să facem o conversie explicită de tip: ((Journal)p).getName();.
Metode implicite
Până la JDK 8, atunci când implementam o interfață, trebuia să implementăm obligatoriu toate metodele ei în clasă. Interfața putea conține doar definiții ale metodelor fără o implementare concretă. În JDK 8 a fost introdusă funcționalitatea metodelor implicite.
Acum, interfețele pot avea metode cu implementare implicită, care sunt utilizate dacă o clasă ce implementează acea interfață nu implementează metoda respectivă. De exemplu, să creăm o metodă implicită în interfața Printable:
interface Printable {
default void print(){
System.out.println("Undefined printable");
}
}
Metoda implicită este o metodă normală, fără modificatori, marcată cu cuvântul cheie default. În clasa Journal, nu este obligatoriu să implementăm această metodă, deși o putem suprascrie:
class Journal implements Printable {
private String name;
String getName(){
return name;
}
Journal(String name){
this.name = name;
}
}
Metode statice
Începând cu JDK 8, interfețele pot conține metode statice, similare cu metodele unei clase:
interface Printable {
void print();
static void read(){
System.out.println("Read printable");
}
}
Pentru a apela o metodă statică a unei interfețe, la fel ca în cazul claselor, se folosește numele interfeței și numele metodei:
public static void main(String[] args) {
Printable.read();
}
Metode private
Implicit, toate metodele dintr-o interfață au modificatorul public. Însă, începând cu Java 9, putem defini în interfețe metode cu modificatorul private. Acestea pot fi statice sau non-statice, dar nu pot avea o implementare implicită.
Astfel de metode pot fi utilizate doar în interiorul interfeței în care sunt definite. De exemplu, dacă avem nevoie să efectuăm anumite acțiuni repetitive într-o interfață, le putem extrage în metode private:
public class Program {
public static void main(String[] args) {
Calculatable c = new Calculation();
System.out.println(c.sum(1, 2));
System.out.println(c.sum(1, 2, 4));
}
}
class Calculation implements Calculatable {}
interface Calculatable {
default int sum(int a, int b) {
return sumAll(a, b);
}
default int sum(int a, int b, int c) {
return sumAll(a, b, c);
}
private int sumAll(int... values) {
int result = 0;
for(int n : values){
result += n;
}
return result;
}
}
Constante în interfețe
Pe lângă metode, interfețele pot defini și constante statice:
interface Stateable {
int OPEN = 1;
int CLOSED = 0;
void printState(int n);
}
Deși aceste constante nu au modificatori expliciți, ele au implicit modificatorii public static final, deci valoarea lor este accesibilă din orice parte a programului.
Utilizarea constantelor:
public class Program {
public static void main(String[] args) {
WaterPipe pipe = new WaterPipe();
pipe.printState(1);
}
}
class WaterPipe implements Stateable {
public void printState(int n) {
if(n == OPEN)
System.out.println("Water is opened");
else if(n == CLOSED)
System.out.println("Water is closed");
else
System.out.println("State is invalid");
}
}
interface Stateable {
int OPEN = 1;
int CLOSED = 0;
void printState(int n);
}
Implementarea multiplă a interfețelor
Dacă dorim să aplicăm mai multe interfețe într-o clasă, toate sunt enumerate prin virgulă după cuvântul implements:
interface Printable {
// metodele interfeței
}
interface Searchable {
// metodele interfeței
}
class Book implements Printable, Searchable {
// implementarea clasei
}
Moștenirea interfețelor
La fel ca și clasele, interfețele pot moșteni alte interfețe:
interface BookPrintable extends Printable {
void paint();
}
Când aplicăm această interfață, clasa Book va trebui să implementeze atât metodele interfeței BookPrintable, cât și metodele interfeței de bază Printable.
Interfețe imbricate
Ca și clasele, interfețele pot fi imbricate, adică pot fi definite în clase sau alte interfețe. De exemplu:
class Printer {
interface Printable {
void print();
}
}
Atunci când aplicăm o astfel de interfață, trebuie să specificăm numele complet împreună cu numele clasei:
public class Journal implements Printer.Printable {
String name;
Journal(String name) {
this.name = name;
}
public void print() {
System.out.println(name);
}
}
Utilizarea interfeței va fi similară cu cazurile anterioare:
Printer.Printable p = new Journal("Foreign Affairs");
p.print();
Interfețele ca parametri și rezultate ale metodelor
La fel ca și în cazul claselor, interfețele pot fi utilizate ca tip de parametru al metodei sau ca tip returnat:
public class Program {
public static void main(String[] args) {
Printable printable = createPrintable("Foreign Affairs", false);
printable.print();
read(new Book("Java for Impatients", "Cay Horstmann"));
read(new Journal("Java Daily News"));
}
static void read(Printable p) {
p.print();
}
static Printable createPrintable(String name, boolean option) {
if(option)
return new Book(name, "Undefined");
else
return new Journal(name);
}
}
interface Printable {
void print();
}
class Book implements Printable {
String name;
String author;
Book(String name, String author) {
this.name = name;
this.author = author;
}
public void print() {
System.out.printf("%s (%s) \n", name, author);
}
}
class Journal implements Printable {
private String name;
String getName() {
return name;
}
Journal(String name) {
this.name = name;
}
public void print() {
System.out.println(name);
}
}
Metoda read() primește ca parametru un obiect al interfeței Printable, deci putem transmite atât un obiect Book, cât și un obiect Journal.
Metoda createPrintable() returnează un obiect Printable, astfel încât putem returna fie un obiect Book, fie un obiect Journal.
Rezultatul afișat în consolă:
Foreign Affairs
Java for Impatients (Cay Horstmann)
Java Daily News