Differenze tra Java e altri Linguaggi

Edoardo Midali
Edoardo Midali

Java occupa una posizione unica nel panorama dei linguaggi di programmazione. Per comprendere meglio le sue caratteristiche, è utile confrontarlo con altri linguaggi popolari. In questa lezione, esploreremo le principali differenze tra Java e linguaggi come C++, Python, C#, JavaScript e altri.

Java vs C++

Gestione della Memoria

C++:

// Gestione manuale della memoria
int* array = new int[100];
// ... uso dell'array
delete[] array; // Necessario liberare manualmente

Java:

// Gestione automatica della memoria
int[] array = new int[100];
// ... uso dell'array
// Garbage collection automatico, no delete necessario

Puntatori vs Riferimenti

C++:

int value = 42;
int* ptr = &value;    // Puntatore esplicito
*ptr = 50;            // Dereferenziazione manuale
ptr++;                // Aritmetica dei puntatori

Java:

Integer value = 42;
Integer ref = value;  // Riferimento (no puntatori espliciti)
ref = 50;            // Assegnazione diretta
// No aritmetica dei puntatori

Ereditarietà Multipla

C++:

class A { /* ... */ };
class B { /* ... */ };
class C : public A, public B { // Ereditarietà multipla
    // Possibili conflitti diamond problem
};

Java:

interface A { /* ... */ }
interface B { /* ... */ }
class C implements A, B { // Solo ereditarietà singola per classi
    // Implementazione multipla di interfacce
}

Compilazione

C++:

# Compilazione diretta a codice nativo
g++ -o program program.cpp
./program  # Esecuzione diretta

Java:

# Compilazione a bytecode + esecuzione su JVM
javac Program.java
java Program  # Esecuzione su JVM

Java vs Python

Tipizzazione

Python (Dinamica):

# Tipizzazione dinamica
variable = "Hello"      # string
variable = 42          # int
variable = [1, 2, 3]   # list

def function(param):   # No tipo specificato
    return param * 2

Java (Statica):

// Tipizzazione statica
String variable = "Hello";  // Tipo fisso
// variable = 42;           // Errore di compilazione

public int function(int param) {  // Tipi espliciti
    return param * 2;
}

Sintassi e Verbosità

Python (Conciso):

# Sintassi più concisa
numbers = [1, 2, 3, 4, 5]
squares = [x**2 for x in numbers if x % 2 == 0]

class Person:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Hello, {self.name}"

Java (Verboso):

// Sintassi più verbosa ma esplicita
import java.util.*;
import java.util.stream.*;

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = numbers.stream()
    .filter(x -> x % 2 == 0)
    .map(x -> x * x)
    .collect(Collectors.toList());

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String greet() {
        return "Hello, " + name;
    }
}

Performance

Python:

# Interpretato, generalmente più lento
import time

start = time.time()
total = sum(range(1000000))
end = time.time()
print(f"Tempo: {end - start} secondi")

Java:

// Compilato JIT, generalmente più veloce
public class Performance {
    public static void main(String[] args) {
        long start = System.nanoTime();
        long total = 0;
        for (int i = 0; i < 1000000; i++) {
            total += i;
        }
        long end = System.nanoTime();
        System.out.println("Tempo: " + (end - start) / 1_000_000 + " ms");
    }
}

Java vs C#

Piattaforma

C# (.NET):

// Principalmente Windows (ora multipiattaforma con .NET Core)
using System;

class Program {
    static void Main() {
        Console.WriteLine("Hello, C#!");
    }
}

Java:

// Multipiattaforma nativo
public class Program {
    public static void main(String[] args) {
        System.out.println("Hello, Java!");
    }
}

Gestione Proprietà

C#:

public class Person {
    public string Name { get; set; }  // Proprietà automatiche

    private int age;
    public int Age {
        get { return age; }
        set {
            if (value >= 0) age = value;
        }
    }
}

Java:

public class Person {
    private String name;
    private int age;

    // Getter e setter espliciti
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }
    public void setAge(int age) {
        if (age >= 0) this.age = age;
    }
}

Gestione Eccezioni

C#:

// Checked e unchecked exceptions
try {
    int result = Divide(10, 0);
} catch (DivideByZeroException ex) {
    Console.WriteLine("Errore: " + ex.Message);
}

static int Divide(int a, int b) {
    return a / b;  // No dichiarazione throws necessaria
}

Java:

// Checked exceptions obbligatorie
try {
    int result = divide(10, 0);
} catch (ArithmeticException ex) {
    System.out.println("Errore: " + ex.getMessage());
}

static int divide(int a, int b) throws ArithmeticException {
    if (b == 0) throw new ArithmeticException("Divisione per zero");
    return a / b;
}

Java vs JavaScript

Ambiente di Esecuzione

JavaScript:

// Esecuzione in browser o Node.js
console.log(
  "Eseguito in: " + (typeof window !== "undefined" ? "Browser" : "Node.js")
);

// Funzioni di prima classe
const multiply = (a, b) => a * b;
const operate = (func, x, y) => func(x, y);
console.log(operate(multiply, 5, 3));

Java:

// Esecuzione su JVM
public class Environment {
    public static void main(String[] args) {
        System.out.println("Eseguito su JVM");

        // Interfacce funzionali (Java 8+)
        BinaryOperator<Integer> multiply = (a, b) -> a * b;
        System.out.println(operate(multiply, 5, 3));
    }

    static int operate(BinaryOperator<Integer> func, int x, int y) {
        return func.apply(x, y);
    }
}

Tipizzazione Dinamica vs Statica

JavaScript:

// Tipizzazione dinamica e debole
let value = "5";
let result = value * 2; // "5" * 2 = 10 (conversione automatica)
console.log(typeof result); // "number"

// Oggetti flessibili
let person = {};
person.name = "Alice"; // Aggiunta dinamica di proprietà
person.age = 30;

Java:

// Tipizzazione statica e forte
String value = "5";
// int result = value * 2;  // Errore di compilazione

// Conversione esplicita necessaria
int result = Integer.parseInt(value) * 2;
System.out.println(result);

// Struttura fissa delle classi
class Person {
    String name;
    int age;
    // Struttura definita a compile-time
}

Java vs Go

Sintassi e Semplicità

Go:

// Sintassi minimalista
package main

import "fmt"

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    for _, num := range numbers {
        fmt.Println(num)
    }
}

// Goroutines per concorrenza
go func() {
    fmt.Println("Esecuzione concorrente")
}()

Java:

// Sintassi più verbosa
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        for (Integer num : numbers) {
            System.out.println(num);
        }
    }
}

// Thread per concorrenza
new Thread(() -> {
    System.out.println("Esecuzione concorrente");
}).start();

Gestione Errori

Go:

// Gestione errori esplicita
func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("divisione per zero")
    }
    return a / b, nil
}

result, err := divide(10, 0)
if err != nil {
    fmt.Println("Errore:", err)
}

Java:

// Gestione errori tramite eccezioni
public static int divide(int a, int b) throws ArithmeticException {
    if (b == 0) {
        throw new ArithmeticException("Divisione per zero");
    }
    return a / b;
}

try {
    int result = divide(10, 0);
} catch (ArithmeticException e) {
    System.out.println("Errore: " + e.getMessage());
}

Java vs Rust

Sicurezza della Memoria

Rust:

// Ownership e borrowing (compile-time safety)
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // s1 è "moved", non più utilizzabile
    // println!("{}", s1);  // Errore di compilazione

    let s3 = String::from("world");
    takes_ownership(s3);
    // println!("{}", s3);  // Errore: s3 non più valido
}

fn takes_ownership(s: String) {
    println!("{}", s);
}  // s esce dallo scope e viene deallocato

Java:

// Garbage collection (runtime safety)
public class Main {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = s1;  // Entrambe le variabili valide
        System.out.println(s1);  // OK
        System.out.println(s2);  // OK

        String s3 = "world";
        takesReference(s3);
        System.out.println(s3);  // Ancora valido
    }

    static void takesReference(String s) {
        System.out.println(s);
    }  // s rimane valido nel chiamante
}

Performance e Controllo

Rust:

// Zero-cost abstractions, controllo preciso
use std::time::Instant;

fn main() {
    let start = Instant::now();

    // Codice ottimizzato al massimo
    let mut sum: u64 = 0;
    for i in 0..1_000_000 {
        sum += i;
    }

    let duration = start.elapsed();
    println!("Tempo: {:?}", duration);
}

Java:

// JIT optimization, meno controllo diretto
public class Performance {
    public static void main(String[] args) {
        long start = System.nanoTime();

        // JIT ottimizza durante l'esecuzione
        long sum = 0;
        for (int i = 0; i < 1_000_000; i++) {
            sum += i;
        }

        long duration = System.nanoTime() - start;
        System.out.println("Tempo: " + duration / 1_000_000 + " ms");
    }
}

Riepilogo delle Differenze Principali

Gestione Memoria

  • Java: Garbage Collection automatico
  • C++/Rust: Gestione manuale/ownership
  • Python: Reference counting + GC

Tipizzazione

  • Java/C#: Statica forte
  • Python/JavaScript: Dinamica
  • C++: Statica con puntatori

Paradigma

  • Java: OOP puro + funzionale (da Java 8)
  • Python: Multi-paradigma
  • JavaScript: Prototipale + funzionale
  • Go: Procedurale + OOP limitato

Performance

  • Java: JIT compilation, ottime performance
  • C++/Rust: Compilazione nativa, massime performance
  • Python: Interpretato, performance moderate
  • JavaScript: JIT moderno, buone performance

Ecosistema

  • Java: Enterprise, Android, big data
  • Python: Data science, AI, scripting
  • JavaScript: Web, full-stack
  • C++: Sistemi, gaming, embedded
  • Go: Microservizi, cloud, DevOps

Quando Scegliere Java

Java è ideale per:

  • Applicazioni enterprise
  • Sistemi distribuiti
  • Sviluppo Android
  • Applicazioni che richiedono portabilità
  • Team con necessità di type safety
  • Progetti a lungo termine con manutenibilità

Java potrebbe non essere la scelta migliore per:

  • Script semplici (meglio Python)
  • Sviluppo web frontend (JavaScript/TypeScript)
  • Programmazione di sistema (C/C++/Rust)
  • Prototipazione rapida (Python)

Conclusione

Java si distingue per il suo equilibrio tra performance, sicurezza, portabilità e maturità dell’ecosistema. Mentre altri linguaggi possono eccellere in aree specifiche - come la semplicità di Python o le performance native di C++ - Java offre una combinazione solida di caratteristiche che lo rendono una scelta affidabile per una vasta gamma di applicazioni.

La scelta del linguaggio dipende sempre dal contesto specifico del progetto, dai requisiti di performance, dalla piattaforma target e dalle competenze del team. Java rimane una scelta eccellente per progetti che valorizzano stabilità, portabilità e un ecosistema maturo.