Keal vous semblera proche : types statiques, classes, génériques, et un compilateur qui refuse ce qu'il ne peut pas prouver. Les différences qui comptent : la nullité vit dans le type, il n'y a aucun héritage, et la mémoire d'une valeur est comptée plutôt que ramassée.
| Concept | Java | Keal |
|---|---|---|
| Classe avec champs | class P { final int x; P(int x){this.x=x;} } | record P(val x: Int) |
| Objet mutable | class C { int n; } | class C(var n: Int) |
| Nullable | @Nullable String s; | val s: String? |
| Test de nullité | if (s != null) s.length() | if (s != null) { s.length } |
| Switch | switch (n) { case 0: … } | when (n) { 0 -> … } |
| Test d'instance | if (o instanceof Circle c) | when (o) { is Circle(r) -> … } |
| Méthode générique | <T> T first(List<T> xs) | func first<T>(xs: List<T>): T |
| Interface | interface Ord { int compareTo(…); } | trait Ord { func compareTo(other: Self): Int } |
| Lambda | xs.stream().map(x -> x * 2) | xs.map({ it * 2 }) |
| Formatage | "hello " + name | "hello ${name}" |
| Destructeur | AutoCloseable / try-with-resources | proc deinit() { … } |
Il n'y a pas d'extends. Les traits à méthodes par défaut et la composition couvrent le même terrain sans le losange, et les records couvrent les classes de données. Si votre conception s'appuie sur une classe de base, elle devient un trait plus un champ.
String ne peut jamais être null ; String? le peut. Le vérificateur rétrécit une liaison immuable après un test, si bien que le cast que Java vous fait écrire est déduit.
Pas de ramasse-miettes, pas de pauses. keal layout imprime la taille et le remplissage de chaque objet, deinit s'exécute de façon déterministe quand la dernière référence meurt, et un cycle est la seule chose que le comptage ne peut libérer — écrivez weak sur l'arête arrière.
import java.time.LocalDate génère des enveloppes typées via JNI. La passerelle est écrite en Keal ; voyez le document d'interopérabilité.