Concetti di programmazione orientata agli oggetti in Java

1. Panoramica

In questo articolo, esamineremo i concetti di programmazione orientata agli oggetti (OOP) in Java. Discuteremo di classi, oggetti, astrazione, incapsulamento, ereditarietà e polimorfismo .

2. Classi

Le classi sono il punto di partenza di tutti gli oggetti e possiamo considerarli come il modello per la creazione di oggetti. Una classe normalmente contiene campi membro, metodi membro e un metodo costruttore speciale.

Useremo il costruttore per creare oggetti della classe:

public class Car { // member fields private String type; private String model; private String color; private int speed; // constructor public Car(String type, String model, String color) { this.type = type; this.model = model; this.color = color; } // member methods public int increaseSpeed(int increment) { this.speed = this.speed + increment; return this.speed; } // ... }

Notare che una classe può avere più di un costruttore. Possiamo leggere di più sulle classi nel nostro articolo sulle classi.

3. Oggetti

Gli oggetti vengono creati dalle classi e sono chiamati istanze della classe. Creiamo oggetti dalle classi usando i loro costruttori:

Car veyron = new Car("Bugatti", "Veyron", "crimson"); Car corvette = new Car("Chevrolet", "Corvette", "black"); 

Qui abbiamo creato due istanze della classe Car. Maggiori informazioni su di loro nel nostro articolo sugli oggetti.

4. Astrazione

L'astrazione nasconde le complessità dell'implementazione ed espone interfacce più semplici.

Se pensiamo a un tipico computer, si può vedere solo l'interfaccia esterna, essenziale per interagire con esso, mentre i chip e i circuiti interni sono nascosti all'utente.

In OOP, astrazione significa nascondere i complessi dettagli di implementazione di un programma, esponendo solo l'API richiesta per utilizzare l'implementazione. In Java, otteniamo l'astrazione utilizzando interfacce e classi astratte.

Possiamo leggere di più sull'astrazione nella nostra classe astratta e negli articoli sull'interfaccia.

5. Incapsulamento

L'incapsulamento nasconde lo stato o la rappresentazione interna di un oggetto al consumatore di un'API e fornisce metodi accessibili pubblicamente associati all'oggetto per l'accesso in lettura-scrittura. Ciò consente di nascondere informazioni specifiche e controllare l'accesso all'implementazione interna.

Ad esempio, i campi dei membri in una classe sono nascosti dalle altre classi e sono accessibili tramite i metodi dei membri. Un modo per farlo è rendere tutti i campi dati privati e accessibili solo utilizzando i metodi membri pubblici :

public class Car { // ... private int speed; public int getSpeed() { return color; } public void setSpeed(int speed) { this.speed = speed; } // ... }

In questo caso, la velocità del campo è incapsulata utilizzando il modificatore di accesso privato ed è possibile accedervi solo utilizzando i metodi pubblici getSpeed ​​() e setSpeed ​​() . Possiamo leggere di più sui modificatori di accesso nel nostro articolo sui modificatori di accesso.

6. Eredità

L'ereditarietà è il meccanismo che consente a una classe di acquisire tutte le proprietà da un'altra classe ereditando la classe. Chiamiamo la classe che eredita una classe figlia e la classe ereditata come la superclasse o la classe genitore.

In Java, lo facciamo estendendo la classe genitore. Pertanto, la classe figlia ottiene tutte le proprietà dal genitore:

public class Car extends Vehicle { //... }

Quando estendiamo una classe, formiamo una relazione IS-A. L' auto è un veicolo . Quindi, ha tutte le caratteristiche di un veicolo .

Potremmo porci la domanda: perché abbiamo bisogno dell'eredità ? Per rispondere a questa domanda, consideriamo un produttore di veicoli che produce diversi tipi di veicoli, come automobili, autobus, tram e camion.

Per semplificare il lavoro, possiamo raggruppare le caratteristiche e le proprietà comuni di tutti i tipi di veicoli in un modulo (una classe in caso di Java). E possiamo lasciare che i singoli tipi ereditino e riutilizzino quelle proprietà:

public class Vehicle { private int wheels; private String model; public void start() { // the process of starting the vehicle } public void stop() { // process to stop the vehicle } public void honk() { // produces a default honk } }

Il tipo di veicolo Car erediterà ora dalla classe Veicolo genitore :

public class Car extends Vehicle { private int numberOfGears; public void openDoors() { // process to open the doors } }

Java supporta l'ereditarietà singola e l'ereditarietà multilivello. Ciò significa che una classe non può estendersi direttamente da più di una classe, ma può utilizzare una gerarchia:

public class ArmoredCar extends Car { private boolean bulletProofWindows; public void remoteStartCar() { // this vehicle can be started by using a remote control } }

Qui, l' ArmouredCar estende l' Auto e l' Auto estende il Veicolo . Così, ArmouredCar eredita sia oggetti di auto e di veicoli .

Mentre ereditiamo dalla classe genitore, uno sviluppatore potrebbe anche sovrascrivere un'implementazione del metodo dal genitore. Questo è noto come override del metodo.

Nel nostro esempio precedente della classe Vehicle , c'è il metodo honk () . La classe Car che estende la classe Vehicle può sovrascrivere questo metodo e implementarlo nel modo in cui vuole produrre il clacson:

public class Car extends Vehicle { //... @Override public void honk() { // produces car-specific honk } }

Si noti che questo è anche definito polimorfismo di runtime, come spiegato nella sezione successiva. Possiamo leggere di più sull'ereditarietà nei nostri articoli sull'ereditarietà e l'ereditarietà e la composizione di Java.

7. Polimorfismo

Il polimorfismo è la capacità di un linguaggio OOP di elaborare i dati in modo diverso a seconda dei tipi di input. In Java, questo può essere lo stesso nome di metodo con firme di metodo diverse e che esegue funzioni diverse:

public class TextFile extends GenericFile { //... public String read() { return this.getContent() .toString(); } public String read(int limit) { return this.getContent() .toString() .substring(0, limit); } public String read(int start, int stop) { return this.getContent() .toString() .substring(start, stop); } }

In questo esempio, possiamo vedere che il metodo read () ha tre diverse forme con diverse funzionalità. Questo tipo di polimorfismo è un polimorfismo statico o in fase di compilazione ed è anche chiamato sovraccarico del metodo.

C'è anche il runtime o il polimorfismo dinamico, dove la classe figlia sovrascrive il metodo del genitore :

public class GenericFile { private String name; //... public String getFileInfo() { return "Generic File Impl"; } }

Una classe figlia può estendere la classe GenericFile e sovrascrivere il metodo getFileInfo () :

public class ImageFile extends GenericFile { private int height; private int width; //... getters and setters public String getFileInfo() { return "Image File Impl"; } }

Maggiori informazioni sul polimorfismo nel nostro articolo sul polimorfismo in Java.

8. Conclusione

In questo articolo, abbiamo appreso i concetti fondamentali di base di OOP con Java.

Gli esempi di codice in questo articolo sono disponibili su GitHub.