Caratteristiche del Linguaggio Java

Java è un linguaggio di programmazione con caratteristiche distintive che lo hanno reso uno dei più popolari e utilizzati al mondo. In questa lezione, esploreremo le caratteristiche fondamentali che definiscono Java e lo distinguono da altri linguaggi di programmazione.
Portabilità - “Write Once, Run Anywhere”
Indipendenza dalla Piattaforma
La caratteristica più famosa di Java è la sua portabilità. Il codice Java, una volta compilato, può essere eseguito su qualsiasi sistema che supporti la Java Virtual Machine (JVM).
// Questo codice funziona identicamente su Windows, Linux, macOS
public class Portabilita {
public static void main(String[] args) {
System.out.println("Questo programma funziona ovunque!");
System.out.println("Sistema operativo: " + System.getProperty("os.name"));
System.out.println("Architettura: " + System.getProperty("os.arch"));
}
}
Compilazione in Bytecode
Il processo di portabilità funziona attraverso:
- Compilazione: Codice sorgente (.java) → Bytecode (.class)
- Esecuzione: JVM interpreta/compila il bytecode per la piattaforma specifica
- Risultato: Stesso comportamento su tutte le piattaforme
# Compila una volta
javac MioPrograma.java
# Esegui ovunque ci sia una JVM
java MioPrograma # Windows
java MioPrograma # Linux
java MioPrograma # macOS
Orientamento agli Oggetti
Paradigma OOP Puro
Java è un linguaggio completamente orientato agli oggetti, dove tutto (eccetto i tipi primitivi) è un oggetto.
// Tutto in Java è basato su classi e oggetti
public class Automobile {
// Attributi (stato)
private String marca;
private String modello;
private int anno;
// Costruttore
public Automobile(String marca, String modello, int anno) {
this.marca = marca;
this.modello = modello;
this.anno = anno;
}
// Metodi (comportamento)
public void avvia() {
System.out.println("L'automobile è stata avviata!");
}
public String getDettagli() {
return marca + " " + modello + " (" + anno + ")";
}
}
Quattro Pilastri dell’OOP
1. Incapsulamento:
public class BankAccount {
private double saldo; // Attributo privato
// Accesso controllato attraverso metodi pubblici
public void deposita(double importo) {
if (importo > 0) {
saldo += importo;
}
}
public double getSaldo() {
return saldo;
}
}
2. Ereditarietà:
// Classe base
class Animale {
protected String nome;
public void mangia() {
System.out.println(nome + " sta mangiando");
}
}
// Classe derivata
class Cane extends Animale {
public Cane(String nome) {
this.nome = nome;
}
public void abbaia() {
System.out.println(nome + " sta abbaiando");
}
}
3. Polimorfismo:
// Interfaccia comune
interface Forma {
double calcolaArea();
}
class Cerchio implements Forma {
private double raggio;
public double calcolaArea() {
return Math.PI * raggio * raggio;
}
}
class Rettangolo implements Forma {
private double lunghezza, larghezza;
public double calcolaArea() {
return lunghezza * larghezza;
}
}
// Uso polimorfico
Forma[] forme = {new Cerchio(), new Rettangolo()};
for (Forma forma : forme) {
System.out.println("Area: " + forma.calcolaArea());
}
4. Astrazione:
abstract class Veicolo {
protected String marca;
// Metodo astratto - deve essere implementato dalle sottoclassi
public abstract void avvia();
// Metodo concreto
public void ferma() {
System.out.println("Veicolo fermato");
}
}
Gestione Automatica della Memoria
Garbage Collection
Java gestisce automaticamente la memoria attraverso il Garbage Collector, eliminando il rischio di memory leak.
public class GestioneMemoria {
public static void main(String[] args) {
// Creazione oggetti
String s1 = new String("Hello");
String s2 = new String("World");
// Gli oggetti non referenziati vengono automaticamente rimossi
s1 = null; // L'oggetto "Hello" diventa eligibile per GC
// Suggerimento per eseguire il GC (non garantito)
System.gc();
// Informazioni sulla memoria
Runtime runtime = Runtime.getRuntime();
System.out.println("Memoria totale: " + runtime.totalMemory());
System.out.println("Memoria libera: " + runtime.freeMemory());
}
}
Vantaggi della Gestione Automatica
- Nessun memory leak: Prevenzione automatica
- Nessun puntatore: Eliminazione errori di accesso
- Produttività: Focus sulla logica, non sulla gestione memoria
Sicurezza
Modello di Sicurezza Multilayer
Java implementa sicurezza a più livelli:
1. Sicurezza a Livello di Linguaggio:
// Non ci sono puntatori espliciti
// Array bounds checking automatico
int[] array = new int[5];
// array[10] = 5; // Genera ArrayIndexOutOfBoundsException
// Type safety rigoroso
String testo = "Hello";
// int numero = testo; // Errore di compilazione
2. Verifica del Bytecode:
// La JVM verifica il bytecode prima dell'esecuzione
// - Controllo type safety
// - Verifica stack overflow/underflow
// - Controllo accesso ai campi e metodi
3. Sandboxing:
// Le applet eseguono in un ambiente sicuro
// Limitazioni automatiche su:
// - Accesso al file system
// - Connessioni di rete
// - Esecuzione di programmi esterni
Security Manager
public class EsempioSicurezza {
public static void main(String[] args) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
try {
sm.checkRead("file.txt");
System.out.println("Lettura consentita");
} catch (SecurityException e) {
System.out.println("Lettura non consentita");
}
}
}
}
Robustezza
Gestione Rigida degli Errori
Java promuove la scrittura di codice robusto attraverso:
1. Exception Handling:
public class GestioneErrori {
public static void dividi(int a, int b) {
try {
int risultato = a / b;
System.out.println("Risultato: " + risultato);
} catch (ArithmeticException e) {
System.out.println("Errore: Divisione per zero!");
} finally {
System.out.println("Operazione completata");
}
}
}
2. Type Safety:
// Controllo rigoroso dei tipi a compile-time
List<String> lista = new ArrayList<>();
lista.add("Hello");
// lista.add(42); // Errore di compilazione con generics
String primo = lista.get(0); // Sicuro, no cast necessario
3. Controlli Runtime:
public class ControlliRuntime {
public static void main(String[] args) {
String[] array = {"uno", "due", "tre"};
try {
System.out.println(array[5]); // Controllo bounds automatico
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Indice fuori limite!");
}
Object obj = null;
try {
obj.toString(); // Controllo null automatico
} catch (NullPointerException e) {
System.out.println("Riferimento null!");
}
}
}
Multithreading Integrato
Supporto Nativo per la Concorrenza
Java offre supporto built-in per la programmazione concorrente:
// Estendendo Thread
class MioThread extends Thread {
private String nome;
public MioThread(String nome) {
this.nome = nome;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(nome + ": " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// Implementando Runnable
class MioRunnable implements Runnable {
private String nome;
public MioRunnable(String nome) {
this.nome = nome;
}
@Override
public void run() {
System.out.println("Eseguendo: " + nome);
}
}
// Uso
public class Concorrenza {
public static void main(String[] args) {
// Creazione e avvio thread
MioThread thread1 = new MioThread("Thread-1");
Thread thread2 = new Thread(new MioRunnable("Runnable-1"));
thread1.start();
thread2.start();
}
}
Sincronizzazione
public class ContatoreSincronizzato {
private int contatore = 0;
// Metodo sincronizzato
public synchronized void incrementa() {
contatore++;
}
// Blocco sincronizzato
public void decrementa() {
synchronized(this) {
contatore--;
}
}
public int getValore() {
return contatore;
}
}
Interpretazione e Compilazione JIT
Approccio Ibrido
Java combina interpretazione e compilazione per ottimizzare le performance:
// 1. Compilazione statica: .java → .class (bytecode)
// 2. Caricamento: Class loader carica il bytecode
// 3. Interpretazione: JVM interpreta il bytecode
// 4. Compilazione JIT: Codice frequente viene compilato in codice nativo
Ottimizzazioni JIT
public class EsempioJIT {
public static void main(String[] args) {
// Codice eseguito frequentemente
for (int i = 0; i < 100000; i++) {
calcoloIntensivo(); // Sarà ottimizzato dal JIT
}
}
private static void calcoloIntensivo() {
double risultato = 0;
for (int i = 0; i < 1000; i++) {
risultato += Math.sqrt(i);
}
}
}
Ricco Set di Librerie
API Standard Completa
Java fornisce un vasto ecosistema di librerie standard:
// Collezioni
import java.util.*;
// I/O
import java.io.*;
// Networking
import java.net.*;
// Database
import java.sql.*;
// Concorrenza
import java.util.concurrent.*;
// Time API moderna
import java.time.*;
public class EsempioLibrerie {
public static void main(String[] args) throws Exception {
// Collezioni
Map<String, Integer> mappa = new HashMap<>();
mappa.put("Java", 25);
// Data e ora moderna
LocalDateTime ora = LocalDateTime.now();
System.out.println("Ora attuale: " + ora);
// Networking
URL url = new URL("https://www.oracle.com");
URLConnection connection = url.openConnection();
// Streams moderni
List<Integer> numeri = Arrays.asList(1, 2, 3, 4, 5);
int somma = numeri.stream()
.mapToInt(Integer::intValue)
.sum();
}
}
Neutralità Architetturale
Indipendenza dall’Hardware
Java astrae le differenze hardware attraverso la JVM:
public class NeutralitaArchitetturale {
public static void main(String[] args) {
// Stesso codice funziona su:
// - x86, x64, ARM, SPARC
// - Little-endian e big-endian
// - 32-bit e 64-bit
System.out.println("Architettura: " + System.getProperty("os.arch"));
System.out.println("Java version: " + System.getProperty("java.version"));
System.out.println("JVM vendor: " + System.getProperty("java.vendor"));
}
}
Performance e Scalabilità
Ottimizzazioni Moderne
Le JVM moderne offrono performance eccellenti:
// Esempio di benchmark
public class PerformanceTest {
public static void main(String[] args) {
long start = System.nanoTime();
// Operazioni intensive
for (int i = 0; i < 1_000_000; i++) {
String s = "Iteration " + i;
s.hashCode();
}
long end = System.nanoTime();
System.out.println("Tempo: " + (end - start) / 1_000_000 + " ms");
}
}
Garbage Collectors Avanzati
- G1GC: Low-latency collector
- ZGC: Ultra-low latency
- Shenandoah: Low-pause collector
- Parallel GC: High-throughput
Conclusione
Le caratteristiche distintive di Java - portabilità, sicurezza, robustezza, orientamento agli oggetti e gestione automatica della memoria - lo hanno reso uno dei linguaggi più utilizzati e affidabili al mondo.
Queste caratteristiche, combinate con un ricco ecosistema di librerie e strumenti, rendono Java una scelta eccellente per progetti di ogni dimensione, dalle applicazioni desktop ai sistemi enterprise distribuiti. La continua evoluzione del linguaggio e della piattaforma garantisce che Java rimanga rilevante e competitivo nell’ecosistema di sviluppo moderno.