Moștenirea în Java
Unul dintre aspectele cheie ale programării orientate pe obiecte este moștenirea. Prin moștenire, se poate extinde funcționalitatea claselor deja existente prin adăugarea de noi funcționalități sau modificarea celor vechi. De exemplu, avem următoarea clasă Person, care descrie o persoană:
class Person {
String name;
public String getName() { return name; }
public Person(String name) {
this.name = name;
}
public void display() {
System.out.println("Name: " + name);
}
}
Să presupunem că dorim să adăugăm o clasă care descrie un angajat - clasa Employee. Deoarece această clasă implementează funcționalități similare cu cele din clasa Person, ar fi rațional să facem clasa Employee un derivat (subclasă) al clasei Person, care devine clasa de bază (sau superclasa).
class Employee extends Person {
public Employee(String name) {
super(name); // dacă clasa de bază definește un constructor
// atunci clasa derivată trebuie să-l apeleze
}
}
Pentru a declara o clasă ca moștenind de la alta, folosim cuvântul cheie extends după numele clasei derivate, urmat de numele clasei de bază. În acest caz, clasa Employee moștenește toate câmpurile și metodele din clasa Person.
Dacă clasa de bază definește un constructor, constructorul din clasa derivată trebuie să-l apeleze folosind cuvântul cheie super. De exemplu, clasa Person are un constructor care primește un parametru, astfel că în constructorul clasei Employee, trebuie să apelăm constructorul clasei Person prin super(name).
Utilizarea claselor:
public class Program {
public static void main(String[] args) {
Person tom = new Person("Tom");
tom.display();
Employee sam = new Employee("Sam");
sam.display();
}
}
class Person {
String name;
public String getName() { return name; }
public Person(String name) {
this.name = name;
}
public void display() {
System.out.println("Name: " + name);
}
}
class Employee extends Person {
public Employee(String name) {
super(name); // apelăm constructorul clasei de bază
}
}
Clasa derivată are acces la toate metodele și câmpurile din clasa de bază, cu excepția celor definite cu modificatorul private. De asemenea, clasa derivată poate adăuga propriile câmpuri și metode:
public class Program {
public static void main(String[] args) {
Employee sam = new Employee("Sam", "Microsoft");
sam.display(); // Sam
sam.work(); // Sam works in Microsoft
}
}
class Person {
String name;
public String getName() { return name; }
public Person(String name) {
this.name = name;
}
public void display() {
System.out.println("Name: " + name);
}
}
class Employee extends Person {
String company;
public Employee(String name, String company) {
super(name);
this.company = company;
}
public void work() {
System.out.printf("%s works in %s \n", getName(), company);
}
}
În acest caz, clasa Employee adaugă câmpul company, care stochează locul de muncă al angajatului, și metoda work.
Suprascrierea metodelor
Clasa derivată poate defini propriile metode sau poate suprascrie metodele moștenite din clasa de bază. De exemplu, putem suprascrie metoda display din clasa Employee:
public class Program {
public static void main(String[] args) {
Employee sam = new Employee("Sam", "Microsoft");
sam.display(); // Sam
// Works in Microsoft
}
}
class Person {
String name;
public String getName() { return name; }
public Person(String name) {
this.name = name;
}
public void display() {
System.out.println("Name: " + name);
}
}
class Employee extends Person {
String company;
public Employee(String name, String company) {
super(name);
this.company = company;
}
@Override
public void display() {
System.out.printf("Name: %s \n", getName());
System.out.printf("Works in %s \n", company);
}
}
Anotarea @Override este plasată înaintea metodei suprascrise. Această anotare, în principiu, nu este obligatorie.
La suprascrierea unei metode, aceasta trebuie să aibă un nivel de accesibilitate cel puțin la fel de mare ca cel din clasa de bază. De exemplu, dacă în clasa de bază metoda are modificatorul public, atunci și în clasa derivată metoda trebuie să aibă modificatorul public.
Totuși, în acest caz observăm că o parte din metoda display din Employee repetă acțiunile metodei display din clasa de bază. Prin urmare, putem simplifica clasa Employee:
class Employee extends Person{
String company;
public Employee(String name, String company) {
super(name);
this.company = company;
}
@Override
public void display(){
super.display();
System.out.printf("Works in %s \n", company);
}
}
Cu ajutorul cuvântului cheie super putem, de asemenea, să accesăm implementările metodelor din clasa de bază.
Interzicerea moștenirii
Deși moștenirea este un mecanism foarte interesant și eficient, în unele situații utilizarea acesteia poate fi nedorită. În acest caz, putem interzice moștenirea cu ajutorul cuvântului cheie final. De exemplu:
public final class Person {
}
Dacă clasa Person ar fi definită astfel, următorul cod ar fi eronat și nu ar funcționa, deoarece am interzis moștenirea:
class Employee extends Person{
}
Pe lângă interzicerea moștenirii, putem, de asemenea, să interzicem suprascrierea unor metode individuale. De exemplu, în exemplul de mai sus, metoda display() a fost suprascrisă; putem interzice suprascrierea ei:
public class Person {
//........................
public final void display(){
System.out.println("Nume: " + name);
}
}
În acest caz, clasa Employee nu va putea suprascrie metoda display.
Dispecerizarea dinamică a metodelor
Moștenirea și posibilitatea suprascrierii metodelor ne oferă oportunități vaste. În primul rând, putem atribui unei variabile de tip superclasă o referință către un obiect de tip subclasă:
Person sam = new Employee("Sam", "Oracle");
Deoarece Employee moștenește de la Person, obiectul Employee este, în același timp, și un obiect de tip Person. În termeni simpli, orice angajat al unei companii este și o persoană.
Totuși, deși variabila reprezintă un obiect de tip Person, mașina virtuală observă că, în realitate, ea indică un obiect de tip Employee. Prin urmare, la apelarea metodelor acestui obiect, se va apela versiunea metodei definită în clasa Employee și nu în Person. De exemplu:
public class Program{
public static void main(String[] args) {
Person tom = new Person("Tom");
tom.display();
Person sam = new Employee("Sam", "Oracle");
sam.display();
}
}
class Person {
String name;
public String getName() { return name; }
public Person(String name){
this.name = name;
}
public void display(){
System.out.printf("Person %s \n", name);
}
}
class Employee extends Person{
String company;
public Employee(String name, String company) {
super(name);
this.company = company;
}
@Override
public void display(){
System.out.printf("Employee %s works in %s \n", super.getName(), company);
}
}
Rezultatul afișat în consolă al acestui program:
Person Tom
Employee Sam works in Oracle
La apelarea unei metode suprascrise, mașina virtuală identifică și apelează în mod dinamic versiunea metodei definită în subclasă. Acest proces mai este cunoscut și sub numele de dynamic method lookup sau căutare dinamică a metodei, sau dispecerizare dinamică a metodelor.