Je viens de Java

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.

ConceptJavaKeal
Classe avec champsclass P { final int x; P(int x){this.x=x;} }record P(val x: Int)
Objet mutableclass 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 }
Switchswitch (n) { case 0: … }when (n) { 0 -> … }
Test d'instanceif (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
Interfaceinterface Ord { int compareTo(…); }trait Ord { func compareTo(other: Self): Int }
Lambdaxs.stream().map(x -> x * 2)xs.map({ it * 2 })
Formatage"hello " + name"hello ${name}"
DestructeurAutoCloseable / try-with-resourcesproc deinit() { … }

Ce qui vous surprendra

Pas d'héritage, et c'est délibéré

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.

Null est un type, pas une valeur à craindre

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.

La mémoire est comptée, et vous pouvez la voir

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.

Vous pouvez toujours appeler Java

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é.