Tipos com restrição de null e tipos anuláveis
(bugs.openjdk.org)- O Java está preparando um recurso de linguagem em preview que adiciona marcadores de nulidade aos tipos:
Foo!rejeitanulleFoo?o permite intencionalmente Foosem marcação permanece com nulidade não especificada, mantendo compatibilidade com código existente, e conversões entre tipos com nulidades diferentes podem vir acompanhadas de avisos ou verificações em tempo de execução- Campos e arrays que rejeitam
nullnão podem usar o valor padrãonull; campos de instância devem ser atribuídos de forma definitiva antes da chamada asuper(...), e arrays exigem valores iniciais para seus componentes - Quando
nullé estreitado paraFoo!, ocorreNullPointerException, e nos caminhos de gravação em arrays e campos a contaminação é evitada com ArrayStoreException e FieldStoreException - Otimizações como o achatamento de value classes do Valhalla dependem da exclusão confiável de
null, e aplicar isso à biblioteca padrão ou reinterpretar automaticamente código existente não faz parte do objetivo atual
Recurso em preview para explicitar a nulidade nos tipos do Java
- O Java passa a usar marcadores de nulidade nos tipos para expressar se o conjunto de valores do tipo inclui
null Foo!é um tipo null-restricted, que excluinulldo conjunto de valoresFoo?é um tipo nullable, que incluinullintencionalmente no conjunto de valoresFoosem marcação tem nulidade unspecified, ou seja,nullpode ocorrer, mas não se sabe se sua presença é intencional- O recurso é um preview feature ativado com as flags
--enable-previewna compilação e em tempo de execução
Objetivos e não objetivos
- Expressar se tipos de referência em Java esperam
nulle fornecer avisos e verificações para conversões entre tipos com nulidades diferentes - Ser compatível com código Java existente e permitir adoção gradual sem criar incompatibilidades de código-fonte ou binárias
- Variáveis de tipos que rejeitam
nulldevem ser inicializadas antes da primeira leitura, e a rejeição denulltambém deve ser imposta em tempo de execução mesmo em classes compiladas separadamente - Otimizações de runtime, como o achatamento de value class do Valhalla, devem contar com metadados e garantias de integridade que tornem confiáveis os tipos com restrição de
null - Itens que não fazem parte do objetivo neste momento:
- Não reinterpretar automaticamente código existente
- Não transformar todos os possíveis
nullem erro de compilação - Não adicionar formas anuláveis a tipos primitivos como
int - Não aplicar a extensão de linguagem à biblioteca padrão nesta fase
Por que isso é necessário
- Uma variável
Stringem Java pode armazenar uma referência para um objetoStringounull, mas a linguagem não oferece uma forma de expressar qual dos dois é o comportamento pretendido - Muitos programas assumem que
nullnão estará presente, mas impor isso de forma consistente entre especificações Javadoc e código de implementação exige trabalho extra - Quando essa expectativa falha, valores
nullpodem circular pelo código e só causar exceção em um ponto distante do bug original - Se o desenvolvedor puder expressar, como parte do tipo, a intenção de rejeitar ou permitir
null, o feedback em compilação e as verificações em tempo de execução ajudam a encontrarnullinesperado mais cedo - No Valhalla, variáveis de tipos value class podem ser otimizadas para uma representação achatada do valor, mas se for necessário um bit extra para codificar
null, isso pode aumentar o uso de memória ou impedir otimizações de armazenamento - No Amber, a nulidade de candidatos a pattern matching pode afetar a análise de exaustividade de
switch, e a nulidade de type patterns pode afetar senullfaz match ou não
Sintaxe dos marcadores de nulidade e estrutura dos tipos
- A nulidade é tratada como parte intrínseca do tipo, então
Foo?eFoosão tipos diferentes por terem nulidades diferentes - Tanto o tipo do array quanto o tipo dos componentes do array podem receber marcadores de nulidade
Foo?[]!é um tipo em que o array em si é null-restricted e os componentes sãoFoonullable- Em arrays multidimensionais, é possível colocar marcadores após cada par de colchetes; por convenção, a leitura vai da esquerda para a direita, do nível mais externo ao mais interno
- Tipos parametrizados e argumentos de tipo também podem receber marcadores de nulidade
Predicate!<Foo?>é umPredicatenull-restricted cujo argumento de tipo éFoonullable
- Para expressar tipos com restrição de
nullou anuláveis,!ou?devem aparecer explicitamente no código-fonte - No futuro, pode-se considerar interpretar por padrão todos os tipos de uma classe ou unidade de compilação como null-restricted e usar apenas
?como exceção, mas isso será tratado separadamente
Regras de inicialização de campos e arrays
- No Java atual, o valor padrão de campos de referência e componentes de array é
null, mas isso é inadequado como valor inicial de campos ou componentes null-restricted - Campos e arrays null-restricted devem sempre ser inicializados pelo programa antes de serem lidos
- Se um campo de instância null-restricted não tiver inicializador, ele deve receber atribuição definitiva antes da chamada explícita ou implícita a
super(...)em cada construtor- O Flexible Constructor Bodies JEP permite escrever o código de inicialização necessário no início do construtor
- Nesse early construction context, não são permitidas operações que referenciem
thisou corram o risco de ler campos ainda não inicializados
- Se um campo de instância null-restricted tiver inicializador, ele será executado no início de cada construtor, antes da chamada a
super(...)- Construtores que chamam
this(...)continuam sendo um caso especial e não executam os inicializadores, como nas regras atuais
- Construtores que chamam
- Campos estáticos null-restricted devem estar definitivamente atribuídos até o fim de todos os inicializadores e blocos de inicialização estáticos da classe
- Se outra classe tentar ler esse campo durante a inicialização da classe, uma verificação em tempo de execução detectará a leitura precoce e lançará exceção
- Arrays com tipo de componente null-restricted devem fornecer um valor inicial para cada componente na expressão de criação do array
- É possível listar todos os valores com um inicializador de array
- Uma nova sintaxe abreviada também pode existir, mas a gramática ainda está TBD
Nulidade de expressões e conversões de nulidade
- O compilador Java determina a nulidade de todas as expressões durante a verificação de tipos
- A nulidade de uma referência de variável vem da declaração da variável, e a nulidade de uma chamada de método vem do tipo de retorno do método referenciado
- O literal
nullé nullable - A maioria das outras expressões de tipo de referência é null-restricted
- Incluem literais, concatenação de strings,
this, criação de instância de classe, criação de array, referência de método e expressão lambda
- Incluem literais, concatenação de strings,
- Conversões de nulidade são permitidas em contextos de atribuição, chamada e cast
- Conversões de nulidade ampliadoras incluem:
Foo!→Foo?Foo!→Foonão especificadoFoo?→Foonão especificadoFoonão especificado →Foo?
- Conversões de nulidade redutoras incluem:
Foo?→Foo!Foonão especificado →Foo!
- Conversões de nulidade redutoras são feitas automaticamente pelo compilador, de forma semelhante ao unboxing, mas em tempo de execução fazem uma verificação dinâmica que pode gerar
NullPointerException - Tentar converter diretamente o literal
nullpara um tipo null-restricted é erro de compilação
Verificações em tempo de execução e exceções
- Em tempo de execução, se um valor
nullfor convertido por estreitamento de nulidade para um tipo null-restricted, ocorre NullPointerException - Conversões de nulidade redutoras que não aparecem explicitamente no código-fonte também podem ocorrer durante a execução
- Arrays declarados com tipo de componente null-restricted continuam rejeitando valores
nullnas verificações normais de armazenamento em array, mesmo quando tratados no código-fonte como um tipo menos específico- Essa falha de conversão provoca ArrayStoreException
- Se um campo que não era null-restricted no momento da compilação passar a ser null-restricted após recompilação separada, uma nova field store check rejeitará o armazenamento de
null- Essa falha de conversão provoca FieldStoreException
- Em chamadas de métodos em relações de overriding, pode haver primeiro uma conversão para o tipo de chamada do parâmetro do método superior e depois uma conversão para o tipo do parâmetro do método sobrescrito
- Valores de retorno de método também podem ser convertidos primeiro para o tipo de retorno declarado do método e depois para o tipo de retorno esperado no ponto de chamada
Genéricos, argumentos de tipo e overriding
- Usos de variáveis de tipo também podem receber marcadores de nulidade:
T!é um tipo null-restricted eT?é um tipo nullable - Tipos de variável null-restricted e nullable afirmam uma nulidade específica dentro do código genérico
- Tipos usados como argumentos de tipo também podem expressar nulidade, e o marcador de nulidade em um tipo de variável sobrescreve a nulidade afirmada no argumento de tipo
- Dentro da implementação apagada (erased) de uma API genérica, não é possível impor a restrição de
null- Ainda assim, casts implícitos normais nas fronteiras da API genérica impõem em tempo de execução argumentos de tipo null-restricted
- Por interoperabilidade, a nulidade dentro de argumentos de tipo não é imposta de forma forte
Predicate<String!>pode ser convertido paraPredicate<String>ouPredicate<String?>- Essas unchecked nullness conversions podem gerar avisos
- Alterações de nulidade em tipos de componente de array também são permitidas como unchecked nullness conversion, e ainda está TBD em que condições haverá verificação em tempo de execução
- A nulidade é ignorada ao determinar a identidade de assinaturas de método
- Um método pode sobrescrever outro mesmo que a nulidade de parâmetros e tipo de retorno não coincida
- Se APIs diferentes introduzirem marcadores de nulidade independentemente, esse tipo de desencontro pode ser comum
- A nulidade não afeta a aplicabilidade de métodos nem pode causar falha de inferência de argumentos de tipo, mas pode influenciar a nulidade inferida para o tipo de retorno de métodos genéricos
- Os detalhes do algoritmo de inferência ainda estão TBD
Avisos e erros do compilador
- Tornar um tipo null-restricted pode criar novos erros de compilação
- Quando campos ou arrays desse tipo não forem inicializados
- Ao tentar converter o literal
nullpara esse tipo - Comparar o literal
nullcom uma expressão de tipo null-restricted também pode virar erro de compilação
- Fora isso, a análise de nulidade é auxiliar e não cria erros de compilação
- O
javacfornecerá avisos para evitar erros em tempo de execução, e a mesma direção é recomendada para IDEs e outras ferramentas de análise - Possíveis causas de aviso incluem:
- Conversões de nulidade redutoras, inclusive quando vierem de um tipo
? - Uso de expressão de tipo
?em acesso a membro ou outra operação hostil anull - Casos em que a nulidade de um argumento de tipo não é consistente com seu bound
- Casos em que a nulidade de parâmetros ou retorno de método não coincide com a do método sobrescrito
- Unchecked conversion que altera a nulidade de um tipo
- Conversões de nulidade redutoras, inclusive quando vierem de um tipo
Arquivos de classe, reflexão e mudanças auxiliares
- A maior parte dos usos de marcadores de nulidade é apagada no arquivo
class, e as conversões de runtime associadas são expressas diretamente no bytecode - A gramática do atributo
Signatureserá atualizada para permitir!e?dentro de tipos - A nulidade não é codificada nos descriptors de métodos e campos
- Para evitar contaminação de campos, um novo atributo
NullRestrictedindicará que o campo não aceita valoresnull- O campo também deve ser marcado com
ACC_STRICTe passar por inicialização estrita - O verificador confirma, no momento em que o construtor chama
super(...), se todos os campos de instância com inicialização estrita já receberam atribuição - Toda tentativa de escrita no campo verifica valores
nulle, se encontrar um, lançaFieldStoreException
- O campo também deve ser marcado com
- A criação de arrays null-restricted não é suportada pela instrução
anewarraye deve ser feita por chamadas à API de reflexão - Não existem literais
Foo!.classouFoo?.class, nem instâncias correspondentes dejava.lang.Class - A nova RuntimeType API descreve o conjunto de tipos imposto em tempo de execução nas verificações de armazenamento em arrays e campos, incluindo variantes null-restricted de todos os tipos de classe e interface
- A API
Fieldpassa a oferecer consulta aoRuntimeTypede um campo, e esse valor pode ser diferente do resultado degetType - A API
Arrayterá uma variante denewInstanceque expressa o tipo do componente comRuntimeType- Essa variante também permite fornecer valores iniciais aos componentes do array
- Tentativas de criar arrays null-restricted sem valores iniciais serão rejeitadas
- A desserialização tradicional não é compatível com campos e arrays null-restricted, e um JEP separado deverá fornecer um mecanismo de serialização que não exponha campos e arrays null-restricted não inicializados
- A documentação gerada pelo
javadocincluirá os marcadores de nulidade - As APIs
java.lang.reflect.Typeejavax.lang.modelcodificarão nulidade na representação de tipos
Alternativas e dependências
- Várias ferramentas do ecossistema Java já implementam rastreamento de
nullem tempo de compilação, mas, por não alterarem a linguagem Java, sua sintaxe fica em geral limitada a annotations e o comportamento possível também se limita a checagens de compilação - Outras linguagens acompanham a nulidade no sistema de tipos, e muitas adotam por padrão tipos null-restricted, tratando como erro a atribuição a esses tipos sem verificação explícita de
null - Em Java, o recurso precisa ser opcional e usável de forma gradual, sem exigir uma única migração massiva
- A imposição de nulidade em runtime também pode ser implementada com verificações explícitas ou chamadas a
Objects.requireNonNull- Mas isso é trabalhoso de aplicar de forma consistente, exige documentação adicional e reduz a legibilidade do programa
- Também não há como aplicar isso diretamente a armazenamentos de variáveis como campos e arrays
- O pré-requisito é Flexible Constructor Bodies (Second Preview)
- Ele permite que construtores executem instruções e atribuam campos de instância antes da chamada a
super(...), viabilizando a exigência de inicialização de campos null-restricted
- Ele permite que construtores executem instruções e atribuam campos de instância antes da chamada a
- Trabalhos futuros incluem:
- Null-Restricted Value Class Types (Preview): otimização da codificação de campos e arrays tipados com value class null-restricted
- JEP 402: Enhanced Primitive Boxing (Preview): rastreamento de nulidade quando a linguagem usar conversões implícitas de boxing de forma mais ampla
- Especialização de classes e métodos na JVM (JEP 218 e revisões): concretização e imposição da nulidade de alguns argumentos de tipo
- Possíveis melhorias futuras adicionais incluem aplicar marcadores de nulidade a partes da API padrão, uma representação mais compacta de null check no bytecode, imposição de baixo nível mais forte para parâmetros de método null-restricted e mecanismos de linguagem que afirmem implicitamente todos os tipos de certos contextos como null-restricted
1 comentários
Comentários do Hacker News
É interessante como essa abordagem difere da que o C# introduziu alguns anos atrás. No C#, quando você ativa a nulabilidade em um projeto, todas as variáveis são declaradas como non-null, a menos que sejam explicitamente marcadas como nullable; nesta proposta, uma variável existente passa a ser, na prática, uma entre três opções: nullable, explicitamente nullable ou explicitamente non-nullable
Kotlin também é uma linguagem da JVM, mas, como C#, trata como non-null quando não há marcação explícita; só que Kotlin não tem o peso da compatibilidade retroativa. Com
lateinit var, ele também oferece uma saída para deixar um tipo non-nullable vazio até que seja inicializado por outro método, lançando uma exceção específica se houver acesso antes da inicializaçãoFico curioso sobre por que colocar três opções. Não bastaria manter variáveis sem anotação como nullable e tornar apenas variáveis anotadas explicitamente non-null? Se, mesmo sem nenhuma marca, elas já são automaticamente nullable, não consigo pensar em um motivo para querer declarar nullable explicitamente
Gosto mais do modelo do C#, mas este tem a vantagem de poder ser usado em bases de código legadas sem resolver todos os problemas de nulabilidade. Por outro lado, o C# expõe imediatamente os problemas de nulabilidade, enquanto esta proposta permite mantê-los escondidos como até agora
Além disso, a parte que diz que “um método pode sobrescrever outro método mesmo que a nulidade dos parâmetros e do valor de retorno não corresponda” parece estranha. Ao sobrescrever/implementar um callback, isso parece uma arma apontada para o próprio pé: o método original especifica um retorno non-null, mas a implementação retorna null
Por questão de tempo, por enquanto optamos por apenas ativar as anotações, exceto em alguns pontos críticos, mas ainda mantemos
NotNullAttributeeCanBeNullAttributeda JetBrains como marcadores para ver imediatamente os lugares onde uma decisão consciente foi tomada. O segundo poderia ser removido, já que nullable tem uma marca explícita, mas o primeiro entra em conflito de nome com o recurso próprio do C#Nesse sentido, as três opções são bastante desejáveis. Afinal, quando o código tem centenas de milhares de linhas, não é fácil migrar de forma rápida e simples
Em outros projetos internos, estamos espalhando
#nullable enableao redor do código em que mexemos para ampliar gradualmente o escopo de nulabilidade. Também temos a condição de que código novo esteja em um contexto nullable. Isso também funciona razoavelmente para explicitar partes já anotadas, mas é um método viável em uma base de código e equipe muito menoresString!O termo “tipo de plataforma” e o símbolo de exclamação são confusos, mas, fora isso, o modelo do Kotlin funciona muito bem. Como Kotlin teve nulabilidade desde o início, o programador não pode especificar diretamente tipos de plataforma, mas eles ainda são necessários para compatibilidade com plataformas de base em que a nulabilidade é ambígua, como JVM ou JavaScript
Nessa abordagem, o padrão continua sendo razoável. Por padrão, deveria ser sempre non-nullable, e quem pensa diferente não aprendeu nada com Tony Hoare. Ao mesmo tempo, a compatibilidade retroativa também é preservada. Para Kotlin isso foi relativamente fácil; Java e C# também precisam preservar compatibilidade com código-fonte existente
Nem o modelo do Java nem o do C# são ideais. O modelo do C# muda bastante o comportamento do código dependendo de flags do compilador, e o modelo do Java transforma a pior escolha no padrão
Ainda assim, tendo mais para o lado do C#. Se “talvez possa ser null” for a opção mais fácil, a maioria dos programadores vai escolhê-la por padrão. Isso parece especialmente provável em uma linguagem amigável a empresas como Java. Linters e avisos do compilador ajudarão no longo prazo, mas acho que levará muitos anos até que a maior parte do código Java esteja devidamente anotada quanto à nulabilidade. Usuários de C# sofrerão mais no curto prazo, mas provavelmente chegarão muito mais rápido ao objetivo de uma nulabilidade clara
https://kotlinlang.org/docs/java-interop.html#null-safety-an...
https://www.infoq.com/presentations/Null-References-The-Bill...
T?significasse nullable,T!significasse non-nullable, e que houvesse uma declaração parecida com pragma para definir o padrão deTcomum em código-fonte recém-compilado, seja por arquivo-fonte, por arquivopackage-infoou globalmente via opção do compiladorDepois, em uma versão LTS futura do Java, bastaria mudar o padrão global. Isso facilitaria a transição dos projetos, permitiria inserir/atualizar pragmas automaticamente e, se necessário, manter o padrão antigo
Além disso, se as anotações existentes
@Nonnulldo JSR-305 tivessem sido aproveitadas nas classes para representar ocorrências de tipos non-nullable, também teria sido possível oferecer compatibilidade bidirecional com JDKs antigos!explícito”Acho que até algo como uma flag do compilador ou uma tag de módulo já seria suficiente
Ainda assim, acredito que surgirá uma flag do compilador que, quando nada for especificado, assumirá automaticamente non-nullable
Parece bom. Finalmente há uma forma, no nível da linguagem, de eliminar milhares de exceções e verificações de null desnecessárias. Mas a conversão automática que estreita a nulidade parece equivocada
Nos exemplos da proposta,
String? id(String! arg) { return arg; },String s = null;,Object! o1 = s; // NPE,Object o2 = id(s); // NPE,Object o3 = (String!) s; // NPE, acho que pelo menos os dois primeiros casos deveriam ser erros de compilaçãoO último é explícito, então é meio ambíguo, mas eu preferiria que, dentro de
if (s != null), o compilador entendesse o tipo efetivo comoString!e permitisseString! ss = s;. Assim não haveria possibilidade de erroNa verdade, prefiro usar
Objects.requireNonNull(s)em vez do cast do último caso, por ser mais explícito. Ainda assim, seria bom ter algo comoObjects.unsafeForceNonNull(s), que contornasse a verificação explícita exceto nos casos em que isso fosse impedido por otimização. Com um método unsafe, daria para implementar diretamenterequireNonNullsem adicionar uma análise estática complexaInfelizmente, isso só será verificado em tempo de execução
Por exemplo, a biblioteca padrão declara que, pelo menos por enquanto, não vai migrar para tipos null
Parece realmente necessário haver uma forma, no nível do pacote ou ao menos do arquivo, de marcar todas as variáveis como non-null por padrão. Caso contrário, por segurança, o argumento para usar a sintaxe
T!em quase todas as variáveis ficará forte, e isso só vai gerar muito ruído!explícito”@NullO problema só aparece nas fronteiras do código que interage com bibliotecas. Acho que essa nova sintaxe seria parecida
É uma pena a parte que diz “por enquanto, aplicar a melhoria da linguagem à biblioteca padrão não é um objetivo”
Pela experiência de ter sido obrigado a usar PHP, é trabalhoso ter que remover ou reconstruir as propriedades já garantidas sobre os dados toda vez que se interage com uma biblioteca padrão enorme
Java também deveria colocar essa expressividade de forma mais ativa na biblioteca padrão e torná-la cidadã de primeira classe
Optionalse torna um peso para a proposta explícita de nullable/non-nullable. Tipos record também poderiam ter sido non-nullable por padrão, eu achoAlém disso, dividir em duas etapas facilita lançar esse recurso como recurso de prévia, receber feedback e só então consolidar o design. Se tentarem fazer tudo de uma vez, quase não haverá espaço para iterar o recurso de verdade
Seria bom ver esse recurso chegar ao Java. A possibilidade de optar explicitamente no nível da linguagem, como
T?, aumentou muito a qualidade de vida dos desenvolvedores em Kotlin e TypeScript. Em Java há ferramentas como NullAway, mas elas são trabalhosasAcho suporte no nível da linguagem muito melhor que
Optional/Maybe, porque permite focar na lógica real em vez de colocar o código nos trilhos demap/flatMaphttps://github.com/uber/NullAway
Acho que seria melhor simplesmente usar outra linguagem da JVM
O site está fora do ar no momento, então deixo um link arquivado: https://web.archive.org/web/20240802081039/https://bugs.open...
“Não é objetivo exigir que o programa trate explicitamente todos os valores
nullque possam ocorrer; valoresnullnão tratados podem gerar um aviso em tempo de compilação, mas não um erro” é uma decisão ruimJava é, em sua maior parte, uma linguagem de tipagem estática, então não entendo por que colocar mais um comportamento dinâmico. Espero que exista uma forma fácil de promover esses avisos a erros
nullem todos os lugares. Com este novo recurso, o tratamento obrigatório denullsó pode ser aplicado a operações relacionadas aos novos tipos, e por isso nada quebraLinguagens em que o sistema de tipos força o tratamento de
null/nilem todos os casos são muito melhores. Mas o Java atual não é essa linguagem. Ainda assim, isso será uma grande melhoriaÉ uma pena que essas lições estejam sendo aprendidas tão tarde. O padrão deveria ser não anulável, imutável por padrão e com o escopo mais estreito por padrão
Em novos designs, escolhe-se com frequência a conveniência imediata em vez de “cair no caminho seguro”. Valores padrão seguros exigem um design e uma experiência de usuário muito mais cuidadosos, mas o resultado é que quase toda linguagem, plataforma e tecnologia acaba cheia de armadilhas. Na engenharia civil e elétrica existem normas; em software, a cada uns 30 anos reaprendemos as mesmas lições em novas linguagens e tecnologias
Java é como um vilarejo de terceiro mundo aprendendo que, para matar germes, basta ferver a água
A maior parte do que fiz no Facebook foi usando Hack. A nulidade é um elemento central do sistema de tipos do Hack e resolve uma quantidade enorme de erros desnecessários
Claro, isso não significa que você nunca receba um
nullinesperado. Como esse recurso também foi adicionado à linguagem depois, ainda havia muitos tipos legadosmixed, refletindo as raízes em PHP, o que na prática significava que qualquer coisa valiaPrimeiro, fico curioso sobre como ficam arrays anuláveis. O exemplo
String![]mostra o caso em que o objeto pode sernull, mas e o próprio array? Em Java,String labels[] = null;é totalmente legal. Então teria de declarar algo comoString![]! labels;?Em Hack,
vec $foosignifica que nemfoonem seus elementos sãonull, enquanto?vec $foosignifica que os elementos são não anuláveis, masfoopode sernull. Na prática, quase nunca há motivo para usar arraysnull, então o padrão deveria ser não permitirnull. O problema é que, em Java, todo código legado presume a possibilidade denullNos exemplos da proposta
Object! o1 = s,Object o2 = id(s),Object o3 = (String!) s, acho que os casos 2 e 3 deveriam ser erros de compilaçãoPor fim, gosto mais do operador de coerção
asdo Hack do que dos casts do Java. Por exemplo,foo($b)é erro de compilação;$b as Agera erro em tempo de execução se fornull;$a as ?Bfaz o cast se forBe, caso contrário, retornanullNo fim, a pergunta é se isso pode ser sobreposto ao Java SDK e como ficaria no código legado
https://docs.hhvm.com/hack/types/nullable-types
String![]? labels = null;Parece que as partes boas do Kotlin estão entrando todas no Java agora
Ainda assim, quero continuar trabalhando em Kotlin, onde não preciso lidar com coisas como Lombok. Registros do Java são legais, no entanto