Types à null restreint et types nullable
(bugs.openjdk.org)- Java prépare une fonctionnalité de langage en aperçu qui ajoute des marqueurs de nullité aux types :
Foo!refusenull, tandis queFoo?l’autorise intentionnellement - Un
Foonon marqué conserve une nullité non spécifiée, pour rester compatible avec le code existant ; les conversions entre types de nullité différente peuvent s’accompagner d’avertissements ou de vérifications à l’exécution - Les champs et tableaux qui refusent
nullne peuvent pas utilisernullcomme valeur par défaut : les champs d’instance doivent être définitivement assignés avant l’appel àsuper(...), et les tableaux doivent recevoir une valeur initiale pour leurs composants - Si
nullest restreint versFoo!, uneNullPointerExceptionest levée ; dans les chemins de stockage des tableaux et des champs, ArrayStoreException et FieldStoreException empêchent la contamination - C’est une base permettant à des optimisations comme l’aplatissement des value classes de Valhalla de se fier à l’exclusion de
null; l’application à la bibliothèque standard ou la réinterprétation automatique du code existant ne sont pas des objectifs actuels
Une fonctionnalité en aperçu pour exprimer la nullité dans les types Java
- Ajoute aux types Java des marqueurs de nullité pour indiquer si l’ensemble des valeurs d’un type inclut
null Foo!est un type null-restricted, qui exclutnullde son ensemble de valeursFoo?est un type nullable, qui inclut intentionnellementnulldans son ensemble de valeurs- Un
Foonon marqué a une nullité unspecified :nullpeut apparaître, mais on ne sait pas si sa présence est intentionnelle - Cette fonctionnalité est une fonctionnalité en aperçu, activée avec les options de compilation et d’exécution
--enable-preview
Objectifs et non-objectifs
- Permettre d’exprimer si un type référence Java attend
null, et fournir des avertissements et des vérifications pour les conversions entre types ayant des nullités différentes - Rester compatible avec le code Java existant, et pouvoir être introduit progressivement sans créer d’incompatibilités source ou binaire
- Les variables de types refusant
nulldoivent être initialisées avant leur première lecture, et le refus denullest imposé à l’exécution même pour des classes compilées séparément - Fournir les métadonnées et les garanties d’intégrité nécessaires pour que les optimisations à l’exécution, comme l’aplatissement des value classes de Valhalla, puissent se fier aux types à null restreint
- Les points suivants ne sont pas des objectifs actuels
- Ne pas réinterpréter automatiquement le code existant
- Ne pas transformer tous les
nullpossibles en erreurs de compilation - Ne pas ajouter de forme nullable aux types primitifs comme
int - Ne pas appliquer, à ce stade, l’extension du langage à la bibliothèque standard
Pourquoi c’est nécessaire
- Une variable
Stringen Java peut contenir une référence vers un objetStringounull, mais le langage ne permet pas d’exprimer laquelle des deux possibilités est intentionnelle - Beaucoup de programmes supposent l’absence de
null, mais un travail supplémentaire est nécessaire pour imposer cette contrainte de manière cohérente dans les spécifications Javadoc et le code d’implémentation - Quand cette attente est rompue, une valeur
nullpeut circuler dans le code d’implémentation puis provoquer une exception à un endroit très éloigné du bug d’origine - Si les développeurs expriment, comme partie du type, leur intention de refuser ou d’autoriser
null, le feedback à la compilation et les vérifications à l’exécution aident à détecter plus tôt lesnullinattendus - Dans Valhalla, les variables de type value class peuvent être optimisées sous forme de représentation aplatie des valeurs, mais si des bits supplémentaires sont nécessaires pour encoder
null, la consommation mémoire peut augmenter ou l’optimisation du stockage devenir impossible - Dans Amber, la nullité des candidats au pattern matching peut influencer le jugement d’exhaustivité d’un
switch, et la nullité des patterns de type peut déterminer sinullest pris en compte ou non
Syntaxe des marqueurs de nullité et structure des types
- La nullité est traitée comme une partie intrinsèque du type ;
Foo?etFooont une nullité différente et sont donc des types distincts - Les types tableau comme les types de composant de tableau peuvent tous deux avoir des marqueurs de nullité
Foo?[]!désigne un type où le tableau lui-même est null-restricted, et où les composants sont desFoonullable- Dans les tableaux multidimensionnels, un marqueur peut être ajouté après chaque paire de crochets ; par convention, l’interprétation va de gauche à droite, de l’extérieur vers l’intérieur
- Les types paramétrés et les arguments de type peuvent également avoir des marqueurs de nullité
Predicate!<Foo?>est unPredicatenull-restricted, et son argument de type est unFoonullable
- Pour exprimer un type null-restricted ou nullable,
!ou?doit apparaître explicitement dans le source - À l’avenir, il pourrait être envisagé d’interpréter par défaut tous les types d’une classe ou d’une unité de compilation comme null-restricted, avec
?comme seule exception, mais les détails relèvent d’un travail séparé
Règles d’initialisation des champs et des tableaux
- En Java existant, la valeur par défaut des champs de type référence et des composants de tableau est
null, mais elle ne convient pas comme valeur initiale d’un champ ou composant de tableau null-restricted - Les champs et tableaux null-restricted doivent toujours être initialisés par le programme avant d’être lus
- Si un champ d’instance null-restricted n’a pas d’initialiseur, il doit être définitivement assigné avant l’appel explicite ou implicite à
super(...)dans chaque constructeur- Le JEP Flexible Constructor Bodies permet d’écrire le code d’initialisation nécessaire au début d’un constructeur
- Dans cet early construction context, il n’est pas permis de référencer
thisni d’effectuer des opérations risquant de lire des champs non initialisés
- Si un champ d’instance null-restricted possède un initialiseur, celui-ci est exécuté au début de chaque constructeur, avant l’appel à
super(...)- Les constructeurs qui appellent
this(...)constituent un cas particulier, comme dans les règles existantes : ils n’exécutent pas les initialiseurs
- Les constructeurs qui appellent
- Les champs statiques null-restricted doivent être définitivement assignés avant la fin de tous les initialiseurs statiques et blocs d’initialisation de la classe
- Si une autre classe tente de lire un tel champ pendant l’initialisation de la classe, une vérification à l’exécution détecte la lecture prématurée et lève une exception
- Un tableau dont le type de composant est null-restricted doit fournir une valeur initiale pour chaque composant dans l’expression de création du tableau
- Toutes les valeurs peuvent être listées avec un initialiseur de tableau
- Une nouvelle syntaxe abrégée est aussi possible, mais sa syntaxe reste TBD
Nullité des expressions et conversions de nullité
- Le compilateur Java détermine la nullité de toutes les expressions pendant la vérification de type
- La nullité d’une référence à une variable vient de la déclaration de cette variable, et la nullité d’un appel de méthode vient du type de retour de la méthode référencée
- Le littéral
nullest nullable - La plupart des autres expressions de type référence sont null-restricted
- Cela inclut les littéraux, la concaténation de chaînes,
this, la création d’instance de classe, la création de tableau, les références de méthode et les expressions lambda
- Cela inclut les littéraux, la concaténation de chaînes,
- Les conversions de nullité sont autorisées dans les contextes d’affectation, d’appel et de cast
- Les conversions de nullité élargissantes incluent notamment
Foo!→Foo?Foo!→Foonon spécifiéFoo?→Foonon spécifiéFoonon spécifié →Foo?
- Les conversions de nullité rétrécissantes incluent notamment
Foo?→Foo!Foonon spécifié →Foo!
- Comme pour les conversions d’unboxing, le compilateur effectue automatiquement les conversions de nullité rétrécissantes, mais une vérification dynamique est effectuée à l’exécution et peut provoquer une
NullPointerException - Toute tentative de convertir directement le littéral
nullvers un type null-restricted est une erreur de compilation
Vérifications à l’exécution et exceptions
- À l’exécution, si une valeur
nullsubit une conversion de nullité rétrécissante vers un type null-restricted, une NullPointerException est levée - Des conversions de nullité rétrécissantes non visibles explicitement dans le code source peuvent aussi se produire pendant l’exécution
- Un tableau assigné avec un type de composant null-restricted refuse les valeurs
nullvia les vérifications habituelles de stockage de tableau, même s’il est manipulé dans le code source avec un type moins spécifique- Cet échec de conversion provoque une ArrayStoreException
- Si un champ qui n’était pas null-restricted au moment de la compilation le devient ensuite via une compilation séparée, un nouveau field store check refuse le stockage de
null- Cet échec de conversion provoque une FieldStoreException
- Dans les appels de méthodes en relation d’override, une conversion vers le type d’appel du paramètre de la méthode parente peut être suivie d’une conversion vers le type de paramètre de la méthode qui override
- Les valeurs de retour des méthodes peuvent aussi être converties vers le type de retour déclaré de la méthode, puis vers le type de retour attendu au point d’appel
Génériques, arguments de type et override
- Les utilisations de variables de type peuvent aussi recevoir des marqueurs de nullité ;
T!est un type null-restricted etT?un type nullable - Les types de variables de type null-restricted et nullable affirment une nullité spécifique à l’intérieur du code générique
- Les types utilisés comme arguments de type peuvent aussi exprimer leur nullité, et les marqueurs de nullité attachés à un type de variable de type remplacent la nullité affirmée par l’argument de type
- À l’intérieur de l’implémentation erased d’une API générique, la restriction de nullité ne peut pas être imposée
- En revanche, les casts implicites habituels qui se produisent aux frontières des API génériques imposent à l’exécution les arguments de type null-restricted
- Pour l’interopérabilité, la nullité dans les arguments de type n’est pas fortement imposée
Predicate<String!>peut être converti enPredicate<String>ouPredicate<String?>- Cette unchecked nullness conversion peut provoquer un avertissement
- Changer la nullité du type de composant d’un tableau est également autorisé comme unchecked nullness conversion ; les conditions dans lesquelles une vérification à l’exécution est effectuée restent TBD
- La nullité est ignorée pour déterminer l’identité des signatures de méthodes
- Une méthode peut en override une autre même si la nullité de leurs paramètres et de leurs types de retour ne correspond pas
- Si des API différentes introduisent indépendamment des marqueurs de nullité, ce genre d’incohérence peut être fréquent
- La nullité n’affecte pas l’applicabilité des méthodes et ne peut pas provoquer l’échec de l’inférence des arguments de type, mais elle peut influencer la nullité inférée pour le type de retour d’une méthode générique
- Les détails de l’algorithme d’inférence restent TBD
Avertissements et erreurs du compilateur
- Rendre un type null-restricted peut créer de nouvelles erreurs de compilation
- Lorsque les champs ou tableaux de ce type ne sont pas initialisés
- Lorsqu’on tente de convertir le littéral
nullvers ce type - La comparaison du littéral
nullavec une expression de type null-restricted peut aussi devenir une erreur de compilation
- Dans les autres situations, l’analyse de nullité est auxiliaire et ne crée pas d’erreurs de compilation
javacfournit des avertissements pour éviter les erreurs à l’exécution, et il est recommandé aux IDE et autres outils d’analyse d’aller dans le même sens- Les causes possibles d’avertissement incluent
- Les conversions de nullité rétrécissantes, y compris depuis des types
? - L’utilisation d’expressions de type
?pour l’accès à des membres ou d’autres opérations null-hostile - Une nullité des arguments de type incohérente avec les bornes
- Une nullité des paramètres ou des retours de méthodes incompatible avec celle des méthodes overridées
- Les unchecked conversions qui changent la nullité d’un type
- Les conversions de nullité rétrécissantes, y compris depuis des types
Fichiers de classe, réflexion et changements auxiliaires
- La plupart des utilisations de marqueurs de nullité sont effacées dans les fichiers
class, et les conversions d’exécution associées sont exprimées directement dans le bytecode - La syntaxe de l’attribut
Signatureest mise à jour pour autoriser!et?dans les types - La nullité n’est pas encodée dans les descriptors des méthodes et des champs
- Pour empêcher la contamination des champs, un nouvel attribut
NullRestrictedindique qu’un champ n’autorise pas la valeurnull- Ce champ doit aussi être marqué
ACC_STRICT, et doit être strictement initialisé - Le vérificateur confirme que tous les champs d’instance à initialisation stricte ont été assignés au moment où le constructeur appelle
super(...) - Toute tentative d’écriture dans le champ vérifie la valeur
nullet, si elle est détectée, lève uneFieldStoreException
- Ce champ doit aussi être marqué
- La création de tableaux null-restricted n’est pas prise en charge par l’instruction
anewarrayet doit être effectuée via un appel à l’API de réflexion - Il n’existe pas de littéraux
Foo!.classouFoo?.class, ni d’instancesjava.lang.Classcorrespondantes - La nouvelle RuntimeType API décrit l’ensemble des types imposés à l’exécution dans les vérifications de stockage des tableaux et des champs, et inclut les variantes null-restricted de tous les types classe et interface
- L’API
Fieldprend en charge la consultation duRuntimeTyped’un champ, qui peut différer du résultat degetType - L’API
Arrayprend en charge une variante denewInstancequi exprime le type de composant avecRuntimeType- Cette variante permet aussi de fournir des valeurs initiales pour les composants du tableau
- Elle refuse toute tentative de création d’un tableau null-restricted sans valeur initiale
- La désérialisation traditionnelle n’est pas compatible avec les champs et tableaux null-restricted ; un JEP séparé fournira un mécanisme de sérialisation qui n’expose pas de champs ni de tableaux null-restricted non initialisés
- La documentation générée par
javadocinclut les marqueurs de nullité - Les API
java.lang.reflect.Typeetjavax.lang.modelencodent la nullité dans la représentation des types
Alternatives et dépendances
- Plusieurs outils de développement de l’écosystème Java ont implémenté leur propre suivi de
nullà la compilation, mais comme ils ne modifient pas le langage Java, leur syntaxe est principalement limitée aux annotations et les comportements qu’ils peuvent influencer se limitent aux vérifications à la compilation - D’autres langages suivent la nullité dans le système de types, et beaucoup sont null-restricted par défaut, considérant comme une erreur l’affectation à un type null-restricted sans vérification explicite de
null - En Java, la fonctionnalité doit rester optionnelle et pouvoir être adoptée progressivement, sans migration massive unique
- L’application de la nullité à l’exécution peut aussi être implémentée par des vérifications explicites ou des appels à
Objects.requireNonNull- Mais une application cohérente est fastidieuse, nécessite une documentation supplémentaire et nuit à la lisibilité du programme
- Il n’existe pas de moyen de l’appliquer directement aux stockages de variables comme les champs et les tableaux
- Le prérequis est Flexible Constructor Bodies (Second Preview)
- Les constructeurs peuvent exécuter des instructions avant l’appel à
super(...)et assigner des champs d’instance, ce qui rend possible l’exigence d’initialisation des champs null-restricted
- Les constructeurs peuvent exécuter des instructions avant l’appel à
- Les travaux futurs incluent
- Null-Restricted Value Class Types (Preview) : optimisation de l’encodage des champs et tableaux typés value class null-restricted
- JEP 402: Enhanced Primitive Boxing (Preview) : suivi de la nullité lorsque le langage utilise plus largement les conversions implicites de boxing
- Spécialisation des classes et méthodes de la JVM (JEP 218 et révisions) : concrétiser et imposer la nullité de certains arguments de type
- Les améliorations futures possibles incluent l’application de marqueurs de nullité à certaines API standard, une représentation concise des null checks dans le bytecode, une application bas niveau plus forte des paramètres de méthode null-restricted, et un mécanisme de langage affirmant implicitement que tous les types de certains contextes sont null-restricted
1 commentaires
Avis sur Hacker News
Je trouve intéressant de voir en quoi cette approche diffère de celle que C# a introduite il y a quelques années. En C#, quand on active la nullabilité dans un projet, toutes les variables sont déclarées non nulles sauf si elles sont explicitement marquées comme nullable ; dans cette proposition, les variables existantes deviennent en pratique soit nullable, soit explicitement nullable, soit explicitement non-nullable
Kotlin est lui aussi un langage JVM, mais comme C#, il considère les types comme non nuls s’ils ne sont pas explicitement indiqués autrement, avec toutefois l’avantage de ne pas porter le poids de la rétrocompatibilité. Il propose aussi une échappatoire avec
lateinit var, qui permet de laisser un type non-nullable vide jusqu’à son initialisation par une autre méthode, et lève une exception dédiée si on y accède avant l’initialisationJe me demande pourquoi il y a trois options. Ne pourrait-on pas garder les variables non annotées comme nullable, et ne rendre explicitement non-nullables que les variables annotées ? Si, en l’absence de marqueur, c’est automatiquement nullable, je ne vois pas vraiment pourquoi on voudrait déclarer explicitement nullable
Je préfère l’approche de C#, mais celle-ci a l’avantage de pouvoir être utilisée dans une base de code legacy sans devoir résoudre tous les problèmes de nullabilité. À l’inverse, C# fait apparaître immédiatement les problèmes de nullabilité, alors que cette proposition permet de continuer à les masquer comme jusqu’ici
Par ailleurs, la partie disant qu’« une méthode peut en surcharger une autre même si la nullité de ses paramètres et de sa valeur de retour ne correspond pas » me paraît étrange. En surchargeant/implémentant un callback dont la méthode d’origine spécifie un retour non nul, cela risque de devenir un piège à se tirer une balle dans le pied si l’on renvoie null
Par manque de temps, nous avons pour l’instant choisi d’activer seulement les annotations, sauf à quelques endroits clés, mais nous conservons encore les
NotNullAttributeetCanBeNullAttributede JetBrains comme marqueurs afin de repérer tout de suite les endroits où une décision consciente a été prise. Le second pourrait être supprimé puisque nullable a un marqueur explicite, mais le premier entre en conflit de nom avec la fonctionnalité native de C#En ce sens, ces trois options sont plutôt souhaitables. Quand le code compte des centaines de milliers de lignes, il est difficile de migrer vite et facilement
Dans d’autres projets internes, nous élargissons progressivement le périmètre de nullabilité en ajoutant des
#nullable enableautour du code que nous touchons. Nous avons aussi posé comme condition que le nouveau code soit dans un contexte nullable. C’est également acceptable pour indiquer les zones déjà annotées, mais c’est une approche possible avec une base de code et une équipe beaucoup plus petitesString!Le terme « types de plateforme » et le symbole point d’exclamation prêtent à confusion, mais à part cela l’approche de Kotlin fonctionne plutôt bien. Comme Kotlin a eu la nullabilité dès le départ, les programmeurs ne peuvent pas spécifier directement des types de plateforme, mais ils restent nécessaires pour être compatibles avec des plateformes sous-jacentes où la nullabilité est ambiguë, comme la JVM ou JavaScript
Dans cette approche, la valeur par défaut reste raisonnable. Par défaut, cela devrait toujours être non-nullable, et ceux qui pensent autrement n’ont rien appris de Tony Hoare. En même temps, la rétrocompatibilité est préservée. Kotlin avait la tâche relativement facile, tandis que Java et C# doivent aussi préserver la compatibilité avec le code source existant
Ni l’approche de Java ni celle de C# ne sont idéales. Celle de C# modifie fortement le comportement du code selon un flag du compilateur, et celle de Java fait du pire choix la valeur par défaut
Je penche tout de même plutôt du côté de C#. Si l’on fait de « probablement nullable » le choix le plus facile, la plupart des programmeurs le choisiront par défaut. Ce sera particulièrement vrai dans un langage aussi orienté entreprise que Java. Les linters et les avertissements du compilateur aideront à long terme, mais il faudra probablement des années avant que la plupart du code Java soit correctement annoté pour la nullabilité. Les utilisateurs de C# souffriront davantage à court terme, mais ils ont bien plus de chances d’atteindre rapidement l’objectif d’une nullabilité claire
https://kotlinlang.org/docs/java-interop.html#null-safety-an...
https://www.infoq.com/presentations/Null-References-The-Bill...
T?signifie nullable,T!signifie non-nullable, et que leTordinaire dans les sources nouvellement compilées ait une valeur par défaut définie par une déclaration façon pragma, soit par fichier source, soit par fichierpackage-info, soit globalement via une option du compilateurEnsuite, une version LTS ultérieure de Java pourrait changer la valeur par défaut globale. Cela faciliterait la transition des projets, permettrait d’insérer ou de mettre à jour automatiquement le pragma, et de conserver l’ancien comportement par défaut si nécessaire
En outre, si l’annotation JSR-305 existante
@Nonnullavait été utilisée dans les fichiers de classe pour représenter l’apparition de types non-nullables, cela aurait aussi pu offrir une compatibilité bidirectionnelle avec les anciens JDK!»Même un simple flag du compilateur ou une balise de module pourrait suffire
Cela dit, je pense qu’il y aura quand même une option du compilateur supposant automatiquement les types non-nullables lorsqu’aucune indication distincte n’est fournie
Ça a l’air bien. Enfin un moyen, au niveau du langage, de supprimer des milliers d’exceptions inutiles et de vérifications de null. En revanche, la conversion automatique qui restreint la nullabilité me paraît mal conçue
Dans les exemples de la proposition,
String? id(String! arg) { return arg; },String s = null;,Object! o1 = s; // NPE,Object o2 = id(s); // NPE,Object o3 = (String!) s; // NPE, il me semble qu’au moins les deux premiers cas devraient être des erreurs de compilationLe dernier est plus ambigu parce qu’il est explicite, mais je préférerais que, dans un
if (s != null), le compilateur sache que le type effectif estString!et autoriseString! ss = s;. Dans ce cas, il n’y aurait pas de possibilité d’erreurEn fait, je préfère utiliser
Objects.requireNonNull(s), qui est plus explicite que le cast du dernier cas. Cela dit, ce serait bien d’avoir aussi quelque chose commeObjects.unsafeForceNonNull(s), qui contournerait la vérification explicite sauf dans les cas bloqués pour des raisons d’optimisation. Avec une méthode unsafe, on pourrait implémenter soi-mêmerequireNonNullsans ajouter d’analyse statique complexeMalheureusement, cela ne sera vérifié qu’au runtime
Par exemple, la bibliothèque standard indique explicitement qu’elle ne migrera pas vers les types null, au moins pendant un certain temps
Il semble absolument nécessaire d’avoir un moyen, au niveau du package ou au moins du fichier, de marquer toutes les variables comme non-null par défaut. Sinon, pour des raisons de sûreté, l’argument en faveur de l’utilisation de la syntaxe
T!sur presque toutes les variables deviendra très fort, ce qui ne fera qu’ajouter beaucoup de bruit!»@NullLe problème ne se pose qu’aux frontières du code qui interagit avec des bibliothèques. Cette nouvelle syntaxe serait probablement similaire
La partie qui dit que « pour l’instant, l’objectif n’est pas d’appliquer l’amélioration du langage à la bibliothèque standard » est décevante
Pour avoir dû utiliser PHP, c’est pénible de devoir retirer ou reconstruire, à chaque interaction avec une énorme bibliothèque standard, des propriétés déjà garanties à l’avance sur les données
Java devrait intégrer plus activement cette expressivité dans la bibliothèque standard et en faire un citoyen de première classe
Optionaldevient un poids pour une proposition de nullable/non-nullable explicite. Les types record auraient aussi pu être non-nullable par défaut, à mon avisDe plus, en séparant en deux étapes, il est plus facile de publier cette fonctionnalité en preview, de recevoir des retours, puis de figer la conception. Si on essaie de tout faire d’un coup, il ne reste presque aucune marge pour itérer réellement sur la fonctionnalité
J’aimerais bien que cette fonctionnalité arrive dans Java. La possibilité explicite, au niveau du langage, avec quelque chose comme
T?, a fortement amélioré la qualité de vie des développeurs dans Kotlin et TypeScript. Il existe des outils comme NullAway pour Java, mais c’est fastidieuxÀ mon avis, le support au niveau du langage est bien meilleur que
Optional/Maybe, parce qu’il permet de se concentrer sur la logique réelle plutôt que de mettre le code sur des rails demap/flatMaphttps://github.com/uber/NullAway
Autant utiliser simplement un autre langage JVM, non ?
Le site étant actuellement hors ligne, voici un lien d’archive : https://web.archive.org/web/20240802081039/https://bugs.open...
« Exiger que le programme traite explicitement toutes les valeurs null qui peuvent se produire n’est pas l’objectif ; les valeurs null non traitées peuvent donner lieu à des avertissements à la compilation, mais pas à des erreurs » est une mauvaise décision.
Java est en grande partie un langage à typage statique ; je ne comprends pas pourquoi on y ajoute encore un comportement dynamique. J’espère qu’il y aura un moyen simple de transformer ces avertissements en erreurs.
Les langages dont le système de types impose la gestion de null/nil dans tous les cas sont nettement meilleurs. Mais Java n’est pas ce genre de langage aujourd’hui. Cela dit, ce sera tout de même une grande amélioration.
C’est dommage qu’on apprenne ces leçons si tard. Par défaut, tout devrait être non-nullable, immuable par défaut, et avec la portée la plus étroite par défaut.
Dans les nouvelles conceptions, on choisit trop souvent la commodité immédiate plutôt que de « tomber du côté sûr ». Des valeurs par défaut sûres exigent une conception et une expérience utilisateur bien plus soignées, mais le résultat, c’est que presque tous les langages, plateformes et technologies se retrouvent truffés de pièges à se tirer une balle dans le pied. Le génie civil et l’électrotechnique ont des normes ; le logiciel, lui, réapprend les mêmes leçons tous les 30 ans environ avec de nouveaux langages et de nouvelles technologies.
Java ressemble à un village du tiers-monde qui découvre qu’il suffit de faire bouillir l’eau pour tuer les germes.
La majeure partie de ce que j’ai fait chez Facebook consistait à utiliser Hack. La nullabilité est un élément central du système de types de Hack, et elle élimine vraiment beaucoup d’erreurs inutiles.
Bien sûr, cela ne veut pas dire qu’on ne reçoit jamais de null inattendu. Comme cette fonctionnalité a aussi été ajoutée au langage plus tard, il restait beaucoup de types
mixedhérités des racines PHP, ce qui voulait en pratique dire que n’importe quoi était accepté.Je me demande d’abord ce qu’il en est des tableaux nullables. L’exemple
String![]montre le cas où l’objet peut être null, mais qu’en est-il du tableau lui-même ? En Java,String labels[] = null;est parfaitement légal. Faudrait-il donc déclarer quelque chose commeString![]! labels;?En Hack,
vec $foosignifie que ni foo ni ses éléments ne sont null, tandis que?vec $foosignifie que les éléments sont non-null, mais que foo peut être null. En pratique, il y a très peu de raisons d’utiliser un tableau null, donc la valeur par défaut devrait être non-null. Le problème, c’est que Java a tout le code legacy qui suppose la possibilité de null.Dans les exemples
Object! o1 = s,Object o2 = id(s),Object o3 = (String!) sde la proposition, il me semble que les cas 2 et 3 devraient être des erreurs de compilation.Enfin, je préfère l’opérateur de contrainte
asde Hack au cast de Java. Par exemple,foo($b)est une erreur de compilation,$b as Aproduit une erreur à l’exécution si c’est null, et$a as ?Bcaste si c’est un B, sinon renvoie null.Au final, la question est de savoir si l’on pourra appliquer cela par-dessus le SDK Java, et à quoi cela ressemblera dans le code legacy.
https://docs.hhvm.com/hack/types/nullable-types
String![]? labels = null;.On dirait que les bonnes idées de Kotlin arrivent maintenant toutes dans Java.
Cela dit, je préfère continuer à travailler avec Kotlin, où je n’ai pas à gérer des choses comme Lombok. Les records Java sont bien, quand même.