Caratteristiche del Linguaggio Java

Edoardo Midali
Edoardo Midali

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:

  1. Compilazione: Codice sorgente (.java) → Bytecode (.class)
  2. Esecuzione: JVM interpreta/compila il bytecode per la piattaforma specifica
  3. 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.