1 pontos por GN⁺ 2024-08-04 | 1 comentários | Compartilhar no WhatsApp
  • O Java está preparando um recurso de linguagem em preview que adiciona marcadores de nulidade aos tipos: Foo! rejeita null e Foo? o permite intencionalmente
  • Foo sem 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 null não podem usar o valor padrão null; campos de instância devem ser atribuídos de forma definitiva antes da chamada a super(...), e arrays exigem valores iniciais para seus componentes
  • Quando null é estreitado para Foo!, ocorre NullPointerException, 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 exclui null do conjunto de valores
  • Foo? é um tipo nullable, que inclui null intencionalmente no conjunto de valores
  • Foo sem marcação tem nulidade unspecified, ou seja, null pode ocorrer, mas não se sabe se sua presença é intencional
  • O recurso é um preview feature ativado com as flags --enable-preview na compilação e em tempo de execução

Objetivos e não objetivos

  • Expressar se tipos de referência em Java esperam null e 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 null devem ser inicializadas antes da primeira leitura, e a rejeição de null també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 null em 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 String em Java pode armazenar uma referência para um objeto String ou null, mas a linguagem não oferece uma forma de expressar qual dos dois é o comportamento pretendido
  • Muitos programas assumem que null nã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 null podem 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 encontrar null inesperado 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 se null faz 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? e Foo sã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ão Foo nullable
    • 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?> é um Predicate null-restricted cujo argumento de tipo é Foo nullable
  • Para expressar tipos com restrição de null ou 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 this ou 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
  • 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
  • Conversões de nulidade são permitidas em contextos de atribuição, chamada e cast
  • Conversões de nulidade ampliadoras incluem:
    • Foo!Foo?
    • Foo!Foo não especificado
    • Foo?Foo não especificado
    • Foo não especificado → Foo?
  • Conversões de nulidade redutoras incluem:
    • Foo?Foo!
    • Foo nã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 null para 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 null for 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 null nas 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 e T? é 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 para Predicate<String> ou Predicate<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 null para esse tipo
    • Comparar o literal null com 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 javac fornecerá 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 a null
    • 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

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 Signature será 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 NullRestricted indicará que o campo não aceita valores null
    • O campo também deve ser marcado com ACC_STRICT e 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 null e, se encontrar um, lança FieldStoreException
  • A criação de arrays null-restricted não é suportada pela instrução anewarray e deve ser feita por chamadas à API de reflexão
  • Não existem literais Foo!.class ou Foo?.class, nem instâncias correspondentes de java.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 Field passa a oferecer consulta ao RuntimeType de um campo, e esse valor pode ser diferente do resultado de getType
  • A API Array terá uma variante de newInstance que expressa o tipo do componente com RuntimeType
    • 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 javadoc incluirá os marcadores de nulidade
  • As APIs java.lang.reflect.Type e javax.lang.model codificarão nulidade na representação de tipos

Alternativas e dependências

  • Várias ferramentas do ecossistema Java já implementam rastreamento de null em 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
  • Trabalhos futuros incluem:
  • 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

 
GN⁺ 2024-08-04
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ção
    Fico 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

    • Na empresa onde trabalho agora, estamos migrando uma base de código C# legada para tipos de referência nullable, e seria muito bom ter uma marcação non-nullable explícita, porque daria para saber imediatamente quais partes já foram revisadas e anotadas
      Por questão de tempo, por enquanto optamos por apenas ativar as anotações, exceto em alguns pontos críticos, mas ainda mantemos NotNullAttribute e CanBeNullAttribute da 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 enable ao 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 menores
    • Em Kotlin também existem tipos cuja nulabilidade não é especificada, embora não seja possível defini-los diretamente. Kotlin os chama de tipos de plataforma (platform types) e, em erros e diagnósticos, os mostra com um ponto de exclamação, como String!
      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...
    • Concordo que é estranho. O que eu esperava era que, no nível do código-fonte, T? significasse nullable, T! significasse non-nullable, e que houvesse uma declaração parecida com pragma para definir o padrão de T comum em código-fonte recém-compilado, seja por arquivo-fonte, por arquivo package-info ou globalmente via opção do compilador
      Depois, 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 @Nonnull do 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
    • No fim há uma seção que diz: “fornecer mecanismos na linguagem para afirmar que todos os tipos em determinados contextos são implicitamente restritos a null, sem que o programador precise usar o símbolo ! explícito”
      Acho que até algo como uma flag do compilador ou uma tag de módulo já seria suficiente
    • Acho que o motivo é a compatibilidade retroativa de código, que Java valoriza muito mais do que outras linguagens
      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ção
    O último é explícito, então é meio ambíguo, mas eu preferiria que, dentro de if (s != null), o compilador entendesse o tipo efetivo como String! e permitisse String! ss = s;. Assim não haveria possibilidade de erro

    • Falando como alguém que já usou Java na indústria, é melhor que verificações dinâmicas aconteçam apenas quando solicitadas explicitamente, e que todo o resto seja tratado estaticamente. Bean Validation funciona bem porque, mesmo que um objeto possa ficar temporariamente inválido, ele é válido no momento em que é verificado explicitamente, ou no momento em que o framework o valida antes de entrar no meu código
      Na verdade, prefiro usar Objects.requireNonNull(s) em vez do cast do último caso, por ser mais explícito. Ainda assim, seria bom ter algo como Objects.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 diretamente requireNonNull sem adicionar uma análise estática complexa
    • Está escrito que “não é objetivo exigir que os programas tratem explicitamente todos os valores null que possam surgir; valores null não tratados podem gerar avisos em tempo de compilação, mas não erros”
      Infelizmente, isso só será verificado em tempo de execução
    • Se o primeiro ou o segundo exemplo virassem erros de compilação, isso significaria ter que anotar todos os pontos de uso de todas as bibliotecas. Até que a biblioteca migrasse para tipos null — ou se nunca migrasse — quase todas as linhas ficariam cheias de casts. Não faz sentido
      Por exemplo, a biblioteca padrão declara que, pelo menos por enquanto, não vai migrar para tipos null
    • Se a essência do problema é introduzir null em uma API, não sei bem se isso é um erro da linguagem. Entendo que seja verboso e que algo seja necessário para organizar, mas no fim o problema não está nos desenvolvedores que escrevem código assim?
    • Se o segundo caso não for erro de compilação, código legado poderá chamar funções que recebem argumentos non-null, mas não sei se isso é necessariamente bom. É difícil julgar antes de usar na prática
  • 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

    • Em “possíveis melhorias futuras” há “fornecer um mecanismo pelo qual a linguagem afirme que todos os tipos em determinado contexto são implicitamente null-restricted, sem que o programador precise escrever o símbolo ! explícito”
    • Acho que isso não seria tão ruim. No nosso projeto, já temos o padrão de que todas as variáveis no código Java que possuímos não são null. Se uma variável nullable for necessária, ela precisa ser anotada com @Null
      O problema só aparece nas fronteiras do código que interage com bibliotecas. Acho que essa nova sintaxe seria parecida
    • Um linter agressivo poderia tratar tipos não marcados como non-null e exigir marcação de nulidade apenas onde fosse necessário
  • É 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

    • Um dos maiores problemas do Java é que, por ser melhorado de forma incremental, código novo um pouco melhorado volta a virar código legado na próxima funcionalidade incremental. Código legado que usa Optional se 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 acho
    • Parece que você está lendo “por enquanto” como “nunca”, mas na prática é mais que essa transição em si é um trabalho gigantesco e provavelmente querem tratá-la separadamente
      Alé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
    • A expressão-chave é “por enquanto”. Acho que, quando o recurso se estabilizar, isso se tornará um objetivo
    • Obrigado? PHP moderno é bastante agradável
  • 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 trabalhosas
    Acho 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 de map/flatMap
    https://github.com/uber/NullAway

    • A ideia é quebrar mais de 30 anos de compatibilidade de código-fonte? Por que fazer isso?
      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...

    • Nada surpreendente que tenham mudado para o amado por todos “jira”
  • “Não é objetivo exigir que o programa trate explicitamente todos os valores null que possam ocorrer; valores null não tratados podem gerar um aviso em tempo de compilação, mas não um erro” é uma decisão ruim
    Java é, 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

    • Não dá para fazer isso. Isso quebraria todos os programas Java existentes. Se fosse obrigatório, todos os programas teriam de ser reescritos para tratar respostas null em todos os lugares. Com este novo recurso, o tratamento obrigatório de null só pode ser aplicado a operações relacionadas aos novos tipos, e por isso nada quebra
      Linguagens em que o sistema de tipos força o tratamento de null/nil em 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

    • Infelizmente, parece que Java ainda não aprendeu a regra de ser não anulável por padrão. Esta proposta introduz dois novos tipos, mas, se você perguntar se o tipo padrão sem anotação é anulável, a resposta continua sendo “sei lá”
    • O que é uma pena é o Java estar aprendendo isso tarde. “Nós” e “Java” não são a mesma coisa
      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 null inesperado. Como esse recurso também foi adicionado à linguagem depois, ainda havia muitos tipos legados mixed, refletindo as raízes em PHP, o que na prática significava que qualquer coisa valia
    Primeiro, fico curioso sobre como ficam arrays anuláveis. O exemplo String![] mostra o caso em que o objeto pode ser null, mas e o próprio array? Em Java, String labels[] = null; é totalmente legal. Então teria de declarar algo como String![]! labels;?
    Em Hack, vec $foo significa que nem foo nem seus elementos são null, enquanto ?vec $foo significa que os elementos são não anuláveis, mas foo pode ser null. Na prática, quase nunca há motivo para usar arrays null, então o padrão deveria ser não permitir null. O problema é que, em Java, todo código legado presume a possibilidade de null
    Nos 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ção
    Por fim, gosto mais do operador de coerção as do Hack do que dos casts do Java. Por exemplo, foo($b) é erro de compilação; $b as A gera erro em tempo de execução se for null; $a as ?B faz o cast se for B e, caso contrário, retorna null
    No 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

    • Provavelmente seria apenas 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

    • Mesmo assim, programadores Java continuarão usando os velhos idioms e ficarão presos a codebases antigas. Vai demorar para essas coisas serem amplamente usadas, então programadores Kotlin não precisam se preocupar
    • Java tem uma longa história de se inspirar em outras linguagens. É bom ver que ele continua evoluindo em um ritmo bem razoável, mantendo em geral a compatibilidade com versões anteriores