4 pontos por GN⁺ 4 시간 전 | 2 comentários | Compartilhar no WhatsApp
  • JEP 401: Value Classes and Objects chegou ao estágio de entrar como preview real no JDK
  • O objetivo central é fazer com que objetos Java “sejam programados como classes e funcionem como int”, reduzindo os custos de cabeçalho de objeto, alocação no heap, GC e indireção por ponteiros
  • A value class do JDK 28 ainda é um tipo de referência anulável; tipos non-null, genéricos especializados e codificação de 128 bits não estão incluídos, e é necessário --enable-preview
  • A JVM pode escalarizar value objects ou fazer achatamento no heap em campos e arrays, mas eles podem ser materializados como objetos no heap em tipos superiores como genéricos com erasure ou Object
  • Desenvolvedores Java precisam refletir a diferença entre identity e value no design do código, e isso afeta ==, synchronized, wrappers de primitivos, desempenho de arrays e a futura especialização de genéricos

O escopo do Valhalla que entra no JDK 28

  • Em 15 de junho, a engenheira da Oracle Lois Foltan confirmou a integração de JEP 401: Value Classes and Objects no repositório principal do OpenJDK e o alvo para o JDK 28
  • O pull request relacionado adiciona mais de 197 mil linhas em 1.816 arquivos
  • Como a mudança é muito grande, durante a integração houve um pedido para que outros committers suspendessem temporariamente commits grandes
  • O JEP 401 é um recurso preview desativado por padrão
    • Para usar a sintaxe, é necessário --enable-preview
    • Brian Goetz delimitou isso como “a primeira parte do Valhalla”
  • O JDK 28 está previsto para ser lançado em março de 2027, e a integração na mainline está planejada para algo em torno de julho de 2026

O custo do modelo de objetos Java que o Valhalla quer atacar

  • O lema do Valhalla é “codes like a class, works like an int
    • A meta é permitir usar classes normais com métodos, validação em construtores e nomes de campos significativos, enquanto a JVM as trata com eficiência semelhante à de primitivos
  • Em Java, com exceção dos 8 primitivos, quase tudo é tipo de referência
    • Em Point p = new Point(1, 2), p não é o próprio point, mas um ponteiro para um objeto no heap
    • Cada vez que lê um campo, a JVM precisa seguir esse ponteiro
  • Quando o número de objetos cresce, o custo aumenta rapidamente
    • Cada objeto tem um cabeçalho de objeto para tipo, estado de sincronização etc.
    • Objetos são alocados no heap e depois se tornam alvo do GC
    • Um array com um milhão de Point é, na prática, composto por um milhão de ponteiros e um milhão de objetos espalhados pelo heap
  • Em “State of Valhalla”, Brian Goetz chama esse layout de memória de fluffy
    • O que o Valhalla busca é um layout dense, em que os dados ficam lado a lado

A diferença para o hardware e os limites da escape analysis

  • A razão de um layout de memória denso ser importante está na diferença de velocidade entre CPU e memória
    • Em 1995, o custo de acesso à memória era parecido com o de operações da CPU
    • Hoje, a CPU é dezenas de vezes mais rápida que a memória principal, e o cache preenche essa lacuna
  • A CPU normalmente lê memória em unidades de cache line de 64 bytes
    • Se os dados estiverem densos e em sequência, ela traz muitos valores úteis de uma vez
    • Ao seguir ponteiros para objetos espalhados, pode haver cache miss, que pode ser muito mais lento que um hit
  • A escape analysis da JVM pode eliminar algumas alocações de objetos
    • Se ela concluir que o objeto não “escapa” para fora de um trecho local de código, pode não alocá-lo no heap e expandir seus campos em variáveis ou registradores
  • Mas a escape analysis é pouco previsível e frágil
    • Se o objeto entrar em um campo de outra classe, for armazenado em um array, passado para um método complexo ou cruzar um limite que o JIT não consegue analisar, a otimização pode parar
    • Pequenos refactorings, atualizações do JDK ou mudanças na estrutura do código podem fazer o objeto voltar para o heap
  • Se, em busca de desempenho, você abandonar objetos e codificar tudo manualmente como bytes crus, como r, g e b, pode ganhar velocidade, mas perde segurança, legibilidade, validação e métodos

O início em 2014 e a transição de Q World para L World

  • O Project Valhalla começou oficialmente em 2014
  • James Gosling o descreveu na época como “six PhDs tied into a single knot”
  • Os criadores do Java já queriam value types desde a era do Java 1.0, mas em 1995 o problema era difícil demais e foi deixado de lado
  • O objetivo inicial era restaurar o alinhamento entre o modelo de programação e as características de desempenho do hardware moderno
    • A ideia era permitir que usuários declarassem tipos flat e dense como primitivos, mas que aparentassem e se comportassem como classes normais
  • O prototype inicial seguia a direção do Q World
    • Ele tratava os novos value types como entidades fundamentalmente diferentes de objetos, com type descriptor, bytecode e top type separados
    • Isso aumentava a complexidade porque todo o sistema de tipos da JVM precisava ter duas variações
  • O L World, que surgiu por volta de 2019, tornou-se o ponto de virada
    • Os value types passam a compartilhar o mesmo “L carrier” das referências normais
    • A equipe esperava que essa integração fosse difícil, mas ela funcionou sem grandes concessões e resolveu vários problemas dos prototypes anteriores
  • No L World surgiu uma separação importante
    • O modelo da JVM e o modelo da linguagem não precisam coincidir 100%
    • É possível manter o modelo L World na JVM e oferecer aos programadores um modelo de linguagem mais conveniente
  • Depois disso, o trabalho passou a se dividir em duas etapas: value classes e genéricos especializados

Mudanças de nome e de modelo

  • A terminologia de Valhalla mudou várias vezes, e isso não refletia apenas uma troca de nome, mas também mudanças no modelo
  • O termo inicial era value types
    • Na época, ainda não estava claro exatamente o que esses tipos seriam
  • Por volta de 2019~2020, o modelo de inline classes se consolidou
    • As classes existentes eram tratadas como identity classes, e as novas classes eram diferenciadas como inline classes sem identidade
    • A inline class era final por padrão, os campos eram final, e havia a restrição de não poder ser sincronizada
  • Em 2021, o “State of Valhalla” abordou primitive classes e o modelo de duas projeções
    • A ideia era que um único tipo tivesse uma variante value, compacta e sem possibilidade de null, e uma variante reference que aceitasse null
    • Sintaxes como Point.val / Point.ref e, depois, Point! / Point? também foram testadas
  • Esse modelo era poderoso, mas trazia uma carga cognitiva alta
    • Os programadores precisavam entender no dia a dia as duas formas do mesmo tipo e os momentos de conversão entre elas
    • No fim, o dualismo foi reduzido para simplificar o modelo para o usuário
  • Atualmente, o JEP 401 usa value class e value object
    • A declaração de uma value class é feita com o modificador value
    • As instâncias são value objects sem identidade
    • A value class continua sendo um reference type
  • A não nulidade foi separada em um JEP opcional à parte, Null-Restricted Value Class Types
    • Não está incluído no JDK 28
  • Textos antigos que explicam o modelo anterior de “primitive classes” podem diferir do padrão atual do OpenJDK
  • O JEP 401 também vem acompanhado do preview JEP 402: Enhanced Primitive Boxing
    • A direção é tornar mais suave a conversão entre primitive e wrapper
    • Não se deve presumir que tudo entrará junto com o JEP 401 em sua forma final

O modelo de value class no JDK 28

  • A value class é declarada com o modificador value
value class USDCurrency implements Comparable<USDCurrency> {  
    private int cents; // implicitly final  
    public USDCurrency(int dollars, int cents) {  
        this.cents = dollars * 100 + cents;  
    }  
  
    public USDCurrency plus(USDCurrency that) {  
        return new USDCurrency(0, this.cents + that.cents);  
    }  
  
    // dollars(), cents(), compareTo(), toString()...  
}  
  • Também é possível ter value record
  • As principais regras são as seguintes
    • Todos os instance fields são implicitamente final
    • Um method não pode ser synchronized
    • A classe é final por padrão
    • Hierarquias formadas por value class e abstract value class são permitidas
    • Não é possível herdar de uma classe com identidade
    • É possível implementar interfaces
  • A característica central é a ausência de identidade
    • Em um objeto comum, mesmo com o mesmo conteúdo, se você fizer new Point(1, 2) duas vezes, serão dois objetos diferentes
    • Um value object não tem identidade, assim como não existem “dois 4 diferentes” no valor int 4

Mudanças em ==, synchronized e null

  • Em value objects, == deixa de ser comparação de identidade e passa a ser uma verificação de substitutability
    • Compara recursivamente se é a mesma classe e se possui os mesmos valores de campo
    • Primitive fields são comparados no nível de bits, e object fields são comparados novamente com ==
    • new USDCurrency(3,95) == new USDCurrency(3,95) passa a ser true
  • Ainda assim, como == observa o estado interno, para a pergunta “representa os mesmos dados?”, normalmente equals continua sendo mais apropriado
  • Value objects não têm identidade para sincronizar
    • Se houver tentativa de sincronização, ocorre IdentityException
    • Quando for necessário verificar identidade explicitamente, podem ser usados Objects.requireIdentity e Objects.hasIdentity
  • As value classes do JDK 28 continuam sendo nuláveis
    • USDCurrency d = null; é válido
    • Um tipo que proíba null é tema de um JEP futuro separado e não faz parte do JDK 28
  • A não nulidade não é apenas uma questão de sintaxe, mas um mecanismo de desempenho que abre espaço para uma flattening mais ampla de value classes

Escalarização e achatamento no heap

  • O JEP 401 dá à JVM liberdade para otimizar value objects
  • Escalarização (scalarization) é uma técnica de JIT que decompõe uma referência a value object em um conjunto de campos
    • Em vez de passar um ponteiro para Color, é possível passar os bytes r, g, b e um flag indicando null
    • Os custos de alocação e GC podem desaparecer
    • É parecida com escape analysis, mas mais previsível e aplicável além do limite de chamadas de method não inline
  • A escalarização tem limitações
    • Em geral, ela não funciona quando o tipo da variável é um supertype da value class, como Object, ou um parâmetro genérico com erasure
    • Nesses casos, o objeto precisa ser materializado no heap
  • Heap flattening é a abordagem de codificar os valores de campo de um value object como um vetor de bits compacto e gravá-los diretamente em um campo ou célula de array
    • Não é necessário um ponteiro para outro local do heap
    • Isso traz densidade de dados e locality
  • Para evitar tearing em acesso concorrente, dados achatados devem poder ser lidos e escritos de forma atômica
    • Em plataformas comuns, um tamanho “pequeno o suficiente” pode ficar na faixa de 64 bits, incluindo o flag de null
    • Value classes pequenas podem ser bem achatadas, mas até mesmo dois campos int ou apenas um double já podem não caber no tamanho de escrita atômica e acabar como objetos comuns no heap
  • No futuro, codificações de 128 bits e tipos com restrição de null podem permitir o achatamento de value classes maiores

Efeitos em boxing, wrappers e arrays

  • Com o preview ativado, as próprias primitive wrapper classes, como Integer, Long e Double, passam a ser value class
    • Como o box perde identidade, a JVM pode escalarizá-lo e achatá-lo
    • Integer[] caminha para uma eficiência próxima de int[], com grande redução do overhead de boxing
  • O JEP 402: Enhanced Primitive Boxing amplia ainda mais as conversões entre primitive e box
    • Ele abre caminho para expressões como List<int>, mas ainda é um trabalho separado em amadurecimento
  • É nos arrays que o efeito aparece com mais clareza
    • Antes, Color[] podia significar um milhão de ponteiros e um milhão de objetos espalhados pelo heap
    • Com value class, Color[] pode se tornar um bloco contíguo armazenando diretamente os valores de cor
    • A CPU consegue ler vários valores em sequência por unidade de cache line

Diferença antes e depois no exemplo de Point[]

  • O exemplo de class comum antes de Valhalla é o seguinte
final class Point {  
    final int x;  
    final int y;  
    Point(int x, int y) { this.x = x; this.y = y; }  
}  
  
Point[] points = new Point[1_000_000];  
  • Este array armazena um milhão de ponteiros
    • Cada ponteiro aponta para um objeto Point separado em algum lugar do heap
    • Cada objeto tem, além de dois int, um cabeçalho de objeto
    • Ao percorrer, é preciso ler o ponteiro, saltar para aquele endereço e ler os campos
  • Após o Valhalla, um exemplo de value class fica assim
value class Point {  
    final int x;  
    final int y;  
    Point(int x, int y) { this.x = x; this.y = y; }  
}  
  
Point[] points = new Point[1_000_000];  
  • A diferença no código é a palavra value, mas o layout de memória muda
    • A JVM pode armazenar os valores de cada point de forma compacta dentro do array
    • Não há cabeçalho nem ponteiro por elemento
    • Com base nos dois int, x e y, eles podem ser dispostos de forma contígua em 8 bytes e com um possível null flag
  • A manutenção também é preservada
    • Point continua sendo uma class com nome, construtor, validação e method
    • Dá para evitar dividir em int[] xs, int[] ys e alinhar índices manualmente

Por que os genéricos especializados ainda ficaram de fora

  • Os generics de Java são implementados com type erasure
    • List<String> e List<Integer> são o mesmo List em runtime
    • O parâmetro de tipo T é apagado para Object
  • O erasure foi uma escolha intencional para introduzir generics sem quebrar a base de código existente do Java
    • Mesmo ao transformar uma class não genérica em genérica, os source files e classes compiladas existentes não quebram
  • Valhalla e erasure entram em conflito em termos de desempenho
    • Se você colocar um value object em List<Point>, como T é apagado para Object, o objeto precisa ser materializado no heap
    • O ganho de achatamento obtido em Point[] pode desaparecer em ArrayList<Point>
  • O plano de recuperação tem duas etapas
    • Universal Generics: no nível da linguagem, permitir que variáveis de tipo também tratem value types
      • Ainda usa erasure
      • Como campos T começam como null por padrão, isso pode gerar um warning do compilador de “null pollution”
      • Ao resolver esses warnings, a API fica mais próxima de estar pronta para specialization
    • Specialized Generics: no nível da JVM, gerar layouts de class especializados para cada argumento de tipo concreto
      • Na terminologia do projeto, species e type restriction estão relacionados a isso
      • Só nessa etapa ArrayList<Point> poderá realmente usar memória flat
  • O JDK 28 não tem full specialized generics
    • Coleções, streams e APIs se tornarem flat e livres de alocação sobre value types é trabalho para releases futuras

O que está e o que não está no JDK 28

  • O que entra no JDK 28 é o seguinte
    • declarações value class e value record
    • migração para value class de classes value-based já existentes no JDK, como wrappers de primitivos
    • escalarização e achatamento de classes que atendam às condições
    • boxing mais barato
  • O que não está no JDK 28 é o seguinte
    • tipos com restrição de null
    • full specialized generics
    • codificação de 128 bits
    • um JEP 402 totalmente maduro
  • Como é um preview feature, a sintaxe e o comportamento podem mudar a cada release conforme o feedback
  • O JDK 28 não é LTS
    • O próximo LTS provavelmente será o JDK 29, em setembro de 2027
    • Muitas empresas poderão encontrar um Valhalla estabilizado em um LTS, mas o preview do JDK 28 inicia o ciclo real de feedback em código

Mudanças que isso trará para o ecossistema e o código

  • Nas áreas de Java de alto desempenho, o Valhalla passa a ser um caminho para lidar com dados densos sem abrir mão de abstrações
    • Isso inclui áreas como processamento de dados, computação vetorial, ML, desenvolvimento de jogos, finanças e codecs
  • Frameworks e bibliotecas podem começar a migrar classes value-based
  • Código que dependia de identidade pode sofrer diferenças de comportamento
    • == em value objects deixa de ser comparação de endereço e passa a ser comparação de substitutability
    • synchronized em value objects leva a IdentityException
  • Mesmo que Integer vire uma value class, na maioria dos casos o binário continuará fazendo link
    • Um novo erro de compilação aparece ao tentar sincronizar nesses tipos
    • == baseado na identidade de Integer ou synchronized(someInteger) podem ser afetados
  • Builds early-access estão disponíveis em jdk.java.net/valhalla

Resumo das perguntas mais frequentes

  • value class é diferente de record
    • record é a escolha de fazer com que o conteúdo seja composto por components
    • value é a escolha de abrir mão da identidade
    • Todas as combinações são possíveis: class comum, record, value class e value record
  • value objects podem ser comparados com ==
    • O significado não é comparação de endereço, mas de substitutability
    • Para igualdade dos dados representados, normalmente equals é mais apropriado
  • Value classes do JDK 28 podem ser null
    • Tipos não anuláveis são tema de um JEP futuro
    • Isso também é importante para o achatamento de value classes maiores
  • Ainda não é a vez de um ArrayList<Point> flat e rápido
    • Por causa do type erasure, objetos dentro de coleções genéricas precisam ser materializados no heap
    • No JDK 28, os casos representativos em que o achatamento funciona diretamente são fields e arrays de value type, como Point[]
  • Escape analysis não consegue substituir tudo
    • Se o objeto escapar para um field, array ou para fora do limite da análise, a otimização pode falhar
    • A escalarização de value objects é mais previsível e pode ir mais longe além dos limites de chamada de method
  • O Valhalla completo será expandido ao longo de várias releases
    • O JDK 28 é o primeiro preview de value class
    • specialized generics, tipos com restrição de null e codificação de 128 bits são trabalhos para releases futuras

2 comentários

 
click 1 시간 전

As threads virtuais lançadas pelo Project Loom ao longo de muito tempo são convenientes e resolvem muita coisa no nível do runtime da JVM, então o desenvolvedor acaba tendo menos com o que se preocupar.
Tomara que o Project Valhalla também chegue ao lançamento final com essa sensação de “almoço grátis”.

 
GN⁺ 4 시간 전
Comentários do Hacker News
  • Disseram que a diferença de memória é algo fundamental, mas fico em dúvida se o texto foi realmente revisado
    Até agora há pouco não estava explicando que objetos com representação maior que 64 bits não são achatados no heap? O Point do exemplo tem dois inteiros de 32 bits mais um flag de null, então dá no mínimo 65 bits
    Pela expressão “pode haver um flag de null” e pelas frases curtas de ênfase que vêm depois, parece que a IA foi gerar frases de destaque e acabou se desviando, e aquele bloco no meio com "[IMAGE: the same Point[] array in two variants..." também é lamentável

    • Frases como “Sem header por elemento. Sem ponteiros. Sem ficar pulando pelo heap.” têm cheiro de estilo de IA, e por isso parecem escrita preguiçosa
      Tudo bem usar IA para ajudar a escrever, mas se você não coloca a própria voz, não há motivo para ler
      https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing#...
    • O tema parecia realmente interessante e eu queria ler, e até dava para relevar as imagens geradas por IA
      Mas depois de alguns parágrafos ficou claro que era um texto feito passando por um LLM ou por algo ainda pior
      Seja blog técnico ou qualquer outra coisa, por favor, não deixem a IA escrever no lugar de vocês. Ninguém quer ler esse tipo de texto
    • Existem 18446744073709551616 valores possíveis; então não dá para reservar 1 deles para null? :)
      Hoje descobri que Rust tem NonZeroU64, e que ao combiná-lo com Optional dá para obter o comportamento necessário usando só 64 bits por item
      https://doc.rust-lang.org/std/num/type.NonZeroU64.html
    • Estava tão óbvio que usaram IA demais que parei depois de ler 2 parágrafos
    • Isso de “objetos com representação maior que 64 bits não são achatados no heap” é coisa do commit inicial
      Como o próprio JEP deixa claro, isso é só a primeira entrega de uma funcionalidade enorme e, como outras funções recentes do Java, está sendo entregue em partes
      Naturalmente, o objetivo é também achatar valores maiores, e esse mecanismo já existe dentro da JVM. O que falta é expor no nível da linguagem a intenção de “permitir tearing”
  • Reconheço o esforço do trabalho que realmente entrou em Valhalla, mas acho difícil concordar com a interpretação de que “o modelo era poderoso, mas mentalmente pesado”
    Dizer que uma variável não pode ser null não é uma distinção mentalmente pesada, especialmente se tudo estiver bem anotado
    A postura de “simplificar o modelo para o usuário mesmo sacrificando o teto de desempenho” pode, na verdade, ter fornecido essa simplificação ao usuário
    O sistema de tipos de uma linguagem de programação existe para oferecer garantias convenientes ao desenvolvedor sobre uma CPU que, no fim, só lida com números. Não há necessidade de reduzir garantias opcionais de segurança porque seriam “complexas demais”
    Ainda mais quando já se chegou ao entendimento de que “o modelo da linguagem e o modelo da JVM não precisam coincidir 100%”

    • Não tenho muita confiança de que Java consiga acertar sua direção
      A governança do Java parece fraca, especialmente em contraste com o lado do .NET, que tomou decisões em geral corretas desde o começo
      Hoje em dia fico até em dúvida se Java ainda tem valor ou atenção dentro da Oracle. A empresa agora parece mais um negócio de datacenter/compute com atividades legadas e uma dívida enorme grudada nisso
      Às vezes dá a impressão de que os únicos setores ainda lucrativos da Oracle são o jurídico e o de cortar grama
    • Essa reclamação não seria mais com o blogueiro do que com a linguagem Java?
      E o marcador de null também está a caminho: https://openjdk.org/jeps/8303099
      Só que isso precisa ser lançado gradualmente, e mesmo este PR que introduz classes/objetos de valor já tem 200 mil linhas
    • Tipos de valor não anuláveis só ficaram para um JEP posterior
      Não me parece que estejam dizendo que é impossível, e sim que não dá para comer um elefante de uma vez só
      Dito isso, já faz bastante tempo que estão roendo essa perna específica
    • nullable é apenas outro estado de carga em programação orientada a trilhos
      Se isso é um conceito resolvido desde 2012, não há motivo para colocar diretamente na linguagem vários sabores de estado. Os trilhos só vão para A ou para B; o desvio depende do estado da carga do trem
      Quando um conceito aparece e desaparece e vira guerra de linguagem, isso é sinal de que existe demanda e de que a linguagem não está lidando direito com ela, ou que até lida, mas criando sobrecarga mental
      Coisas como Value, Errorstates, Null, IoExceptions, WeirdOsStatesNeededToHandleUpstairs
      https://fsharpforfunandprofit.com/rop/
      Para falar à la Monty Python, vamos em frente logo
    • O que está sendo discutido aqui não é segurança de null, e sim projeções de referência/valor parecidas com Integer/int
      Em vez de dar a cada tipo uma projeção com identidade e outra sem identidade, a equipe de Valhalla fez com que tipos de valor simplesmente não tenham identidade, e por isso Integer e int viram sinônimos
      O layout de memória é decidido automaticamente conforme o contexto e as decisões de otimização. Por isso, o significado de == para wrappers primitivos como Integer também mudou, e agora não depende de se estar usando uma “projeção de referência” ou uma “projeção de valor”
      Não foi aqui que reduziram garantias opcionais de segurança por serem “mentalmente pesadas”
  • Uma coisa que aparece repetidamente nos comentários do HN sobre Java/JVM é que, surpreendentemente, muita gente tem uma imagem antiga da JVM ou do Java, mas quase não conhece como ele é hoje
    A JVM de 2026 é um predador muito saudável. Tem defeitos? Claro que sim, mas a base é extremamente boa

    • No HN é difícil conseguir uma boa avaliação da JVM, e aqui ela é tratada como tecnologia fora de moda
      No trabalho, uso o Java 26 mais recente e recursos de preview, principalmente StructuredConcurrency, e é excelente. Mesmo vindo de empresas anteriores onde usei Haskell e Python, não me arrependo nem um pouco
    • Muita gente ainda precisa manter os monólitos Java que começaram na época em que Java bombava nos anos 2000, e ainda rodá-los em Java 8
      Pessoalmente conheço os novos recursos lançados nos últimos anos, mas no trabalho real Java está literalmente preso ao passado
  • Muitos comentários aqui são um tanto injustos em relação ao ótimo trabalho em andamento e aos JEPs ainda melhores que estão por vir
    Se comparar Java a uma criança, nos primeiros anos ele foi criado por um pai amoroso (Sun), depois foi jogado na garagem junto com outras crianças e negligenciado por um tutor maligno (Oracle)
    Como ficou negligenciado e sem carinho até o JDK 8, basicamente teve de correr atrás do prejuízo
    É verdade dizer que “só agora surgiram coisas como structs ou tipos-valor”, mas isso aconteceu porque o crescimento foi dificultado por processos corporativos gigantescos, burocráticos e hostis. Agora ele está livre e sendo bem cuidado pela família OpenJDK
    E continuará aproveitando a alegria de escreva uma vez e distribua em qualquer lugar

    • Goste você da Oracle ou não, essa não é uma descrição correta da história do Java
      Está mais para um pai amoroso que o criou e depois, por problemas financeiros, o deixou com uma família temporária, onde ele foi negligenciado
      Depois foi adotado por um novo pai amoroso, a Oracle, e Java floresceu, tornando-se um adulto saudável e estável
      Foi a Oracle que concluiu a abertura do ecossistema ao tornar o OpenJDK a implementação de referência, e também abriu ferramentas antes proprietárias como JFR e Mission Control
      Ela também manteve muitos membros fundadores da equipe da linguagem, algo bem raro nesse tipo de aquisição, e Java melhorou muito tanto na linguagem quanto no runtime
    • Java foi negligenciado nos últimos anos da Sun
      A Oracle fez Java avançar em um ritmo sem precedentes, mantendo ao mesmo tempo a maior parte da compatibilidade retroativa
      Dizem que o .NET “fez certo desde o começo”, mas se isso significa a divisão e reescrita entre .NET Framework/.NET Core/.NET, então isso não faz sentido nem dentro desta discussão. O .NET pôde aprender com Java, e mesmo assim errou em algumas coisas
      O mesmo vale para o MySQL. Neste site diziam que estava “morto”, mas para quem realmente entende, ele reviveu sob a Oracle
    • Dizer que “a Oracle negligenciou Java” e que “ele foi negligenciado até o JDK 8” é contraditório
      A última versão de Java sob a Sun saiu em 2006, a Oracle comprou a Sun em 2010, o JDK 7 saiu em 2011 e o JDK 8 em 2014
      A equipe em grande parte permaneceu a mesma, e a maior diferença foi que a Oracle encerrou a negligência e colocou mais dinheiro. Por isso o ritmo de Java acelerou após a aquisição
      Falam em “correr atrás”, mas nem está claro atrás de quem. As únicas linguagens tão ou mais populares que Java são JS/TS e Python
      Quem diz que Java ficou para trás normalmente o compara com linguagens que estão em situação muito pior. Às vezes, pessoas que gostam de um recurso específico deixam passar que a linguagem que tem esse recurso vai mal não por causa disso, mas apesar disso
      Gestores gostam de lançamentos rápidos, enquanto a liderança técnica — desde a era Sun — costuma defender fazer as coisas com cautela, devagar e direito
      Entendo a sensação de que Java não é tão popular quanto era em 2003, mas aquele período foi excepcionalmente unificado não só para Java, como para todo o ecossistema de software, e nunca mais houve algo tão unificado nem antes nem depois
    • Dizem “escreva uma vez e distribua em qualquer lugar”, mas isso não vale para navegador, iOS nem sistemas embarcados
      Agora a tecnologia que realmente é escreva uma vez e distribua em qualquer lugar é o WebAssembly. A JVM teve sua chance e perdeu
    • Levando a metáfora adiante, Java não foi só jogado na garagem, como também foi usado para mover um processo bilionário por pensão contra o Google e acabou virando, no fim, um meio de arrecadação em dinheiro
      Ainda assim, eu não chamaria isso de “crescimento prejudicado”. Foram feitas escolhas; algumas eram razoáveis, outras não, e esse tipo de escolha é muito difícil de corrigir depois
      Basta olhar para C++: pessoalmente vejo a semicompatibilidade com C como um albatroz de 150 pés impossível de remover, e muitas versões desde o C++11 foram um trabalho de tornar esse albatroz um pouco mais suportável
      Considero tratar todas as classes de valor na JVM como um único tipo-L, como os tipos primitivos, uma solução bem elegante para um problema difícil
      No fim, tudo isso vem da decisão tomada no Java 2 de implementar generics com apagamento de tipos por causa da compatibilidade retroativa, e o C3 viu esse resultado e rejeitou esse caminho
  • Só a evolução dos tipos-valor em Java já daria um ótimo thriller técnico
    Li a mailing list e vi todos os vídeos relacionados, e o processo de integrar o design em algo que sempre parecesse tipicamente Java foi realmente impressionante
    Ao mesmo tempo, houve um aprofundamento muito mais detalhado no que tipos-valor significam e em que pontos quais otimizações podem ser feitas

    • A única mudança de sintaxe é adicionar value
  • Em classes de valor, == acaba funcionando praticamente como memcmp()
    Isso é meio decepcionante, porque quebra a encapsulação e expõe detalhes de implementação
    O código cliente pode passar a fazer ramificações dependendo de como um dado valor é representado internamente. Em certo sentido, isso é até pior do que comparação de identidade, porque a comparação de identidade pelo menos não expõe o estado interno

    • Tipos de valor são um conceito bem distante daquela visão orientada a objetos de “organismos mágicos em caixa-preta”
      Não é fazer orientação a objetos clássica de um jeito novo, e sim uma linguagem nascida da ideologia orientada a objetos dando mais um passo rumo a um mundo pós-orientação a objetos
    • Se um bloco de dados tem estado interno, então o próprio bloco de dados está errado
      Imagino que o lado do Java também tenha considerado bem a possibilidade de excluir padding da comparação ou forçar bytes de padding a 0
      Isso também precisaria funcionar para strings. Strings claramente continuarão sendo alocadas no heap, e fazer memcmp de ponteiros dentro de uma nova “struct” é exatamente comparação de identidade
    • O ponto central das classes de valor é que elas não devem encapsular estado, ou seja, são contêineres de dados totalmente transparentes
    • Se você nunca usou Java na prática, talvez não sinta o verdadeiro peso dessa mudança. Esta é uma mudança que quebra compatibilidade rara para o Java
      Java separa verificação de identidade de objeto e verificação de igualdade. == basicamente olha se dois ponteiros são o mesmo, enquanto igualdade é um conceito subjetivo baseado em interfaces como equals/hashCode
      Por isso, new Integer(1000) == new Integer(1000) antes era false, mas agora passa a ser true; new Integer(1000).equals(new Integer(1000)) é true; e new Integer(10) == new Long(10) antes era false, mas agora vira erro de compilação
      No Java antigo, inteiros abaixo de certo valor às vezes eram substituídos por um tipo canonicalizado, e acho que o limite era por volta de 128. Daí vinha a diferença entre 10 e 1000
      Agora parece que essas comparações fazem unboxing implícito. O fato de a comparação Integer/Long antes ser false e agora ser erro de compilação deixa claro que há unboxing envolvido
      Talvez ainda seja possível obter o comportamento antigo usando variáveis
      De todo modo, quando classes de valor perdem identidade, == deixa de ser igualdade de ponteiro e vira igualdade bit a bit. Espero que resolvam esses vários casos de canto, mas tecnicamente é uma mudança que quebra compatibilidade
  • Entendo a ideia das classes de valor, mas a implementação é falha
    O que o código a seguir imprime? Point a = new Point(10, 10); Point b = a; a.x = 100; System.out.println(b.x);
    Até agora a resposta era clara, mas com a adição de classes de valor a resposta passa a depender de Point ser uma classe de valor ou uma classe de referência. Então esse design prejudica a legibilidade
    Isso viola o princípio da uniformidade. Em The Psychology of Computer Programming, de Weinberg, uniformidade é descrita como o princípio psicológico segundo o qual o usuário espera que coisas parecidas se comportem de forma parecida, e coisas diferentes se comportem de forma diferente
    Quando uma linguagem de programação permite comportamentos semanticamente diferentes para duas construções que parecem quase idênticas no ponto de uso, ela aumenta a carga cognitiva do leitor. Para saber se atribuição, igualdade, identidade e mutação funcionam como em objetos de referência comuns ou como em valores, é preciso verificar a declaração do tipo ou depender de ferramentas
    Isso poderia ser corrigido se a palavra-chave value fosse exigida não só na declaração, mas também no ponto de uso. Por exemplo, algo como value Point a = new Point(10, 10);

    • Pelos objetivos da JEP 401, isso é impossível. O propósito das classes de valor é permitir que o desenvolvedor escolha um modelo de programação para dados imutáveis
      https://openjdk.org/jeps/401
    • Lendo o texto, a 3ª linha é erro de sintaxe. Todos os campos de um tipo de valor são final
      Claro, olhando só para essas quatro linhas não há como saber que isso aconteceria, mas esse problema já existe hoje. A mesma coisa aconteceria se Point fosse um record
    • A instrução a.x = 100; não seria válida. Tipos record são imutáveis
      Portanto, a situação temida não deveria ser possível
    • Ainda assim fica meio nebuloso
      Se fosse algo como value Point a = new Point(10, 10); value Point b copy= a; a.x uniq= 100; System.out.println(b.x);, ficaria bem mais claro que ocorre clonagem/cópia e que mudar um campo não afeta o campo de outro objeto
  • Sei que no mundo Java é quase falta de educação reconhecer a existência do .NET, mas fiquei curioso sobre como isso difere de uma struct do .NET
    Olhando por alto para tipos de valor, especialização de genéricos e boxing, parece que fizeram escolhas parecidas

    • Em C# realmente existem várias armadilhas, e o objetivo do Java parece ser torná-las explícitas
      Se C# basicamente copiou C sob uma ótica de baixo nível, o lado do Java abordou isso em nível mais alto e analisou em detalhe que restrições trazem quais vantagens
      Enquanto em outras linguagens a distinção struct/classe é binária, Java permite um controle mais fino que reflete a semântica do domínio subjacente
      E ficou claro que structs, especialmente em contextos paralelos, têm várias armas apontadas para o próprio pé
    • Há uma seção no texto que trata disso
      Pessoalmente, vejo structs de C/C# como mutáveis e passadas por cópia, enquanto classes de valor são imutáveis e passadas por valor
      Acho que alocação na stack não é possível em Java
    • Funcionalmente não é diferente; o Java só está finalmente alcançando práticas antigas
      A falsa dicotomia do tipo “structs de C# têm identidade e mutação, então precisam definir com precisão a semântica de cópia em atribuição ou passagem, dando ao programador um modelo mais pesado e ao runtime menos liberdade” não combina muito com o que está sendo explicado
      Pode não haver identidade no sentido da semântica de referência de classe do Java, mas no sentido de uma estrutura de memória única em um endereço específico, obviamente ainda há identidade. Isso chega perto de discutir semântica no detalhe da terminologia do Java
  • A nota de rodapé 6, “como isso difere de struct em C#”, é imprecisa
    Vendo o tanto de imagens geradas por IA no texto, dá até a impressão de que houve bastante alucinação na escrita, ou pelo menos no processo de pesquisa

  • O texto é meio confuso e dramático, mas felizmente os documentos originais são bem fáceis de ler
    Página principal: https://openjdk.org/projects/jdk/28/spec/
    Status dos JEPs: https://bugs.openjdk.org/secure/Dashboard.jspa?selectPageId=...
    Seria bom se alguém acompanhasse os desenvolvimentos relacionados em C#, Swift, Java e Rust. Todos vêm competindo para alcançar o hardware e, na minha visão, também vêm se influenciando mutuamente
    Pessoalmente, minha preocupação é com o impacto que essas mudanças terão no compartilhamento de memória via FFI