Bom dia ,venho por este meio submeter o 4º resumo do livro, no dia 13/11/2023 pela 9:31 .

45 á 54

Ao analisar a fundo as implementações atuais de Session e EntityManager do Hibernate, elas não retornam nem ArrayList, nem LinkedList, nem nenhuma coleção do java.util, e, sim, implementações de listas persistentes de pacotes do próprio Hibernate.

 Isto é possível, novamente, pelo desacoplamento provido pelo uso das interfaces.

 Além disso, o retorno das consultas com JPA e Hibernate são List, para deixar claro ao usuário que a ordem é importante.

 Manter a ordem de inserção e permitir acesso aleatório são características do contrato de List e são importantes para o resultado de consultas, pois podem definir uma ordenação (order by), uma ótima justificativa para optar por uma interface mais específica, e não usar Iterable ou Collection.

Nas principais APIs do Java, é fundamental programar voltado à interface.

Caso contrário, esse método poderia, e deveria, receber uma referência a InputStream, ficando mais flexível e podendo receber os mais diferentes tipos de streams, como argumento, que provavelmente não foram previamente imaginados.

Utilize sempre o tipo menos específico possível.

Repare que, muitas vezes, classes abstratas trabalham como interfaces, no sentido conceitual de orientação a objetos.

Classes abstratas possuem a vantagem de se poder adicionar-lhes um novo método não abstrato, sem quebrar o código já existente.

Já com o uso de interfaces(aqui, pensando na palavra-chave do Java), a adição de qualquer método acarretará a quebra das classes que a implementam.

Programe voltado à interface, não à implementação Casa do Código interfaces nunca definem nenhuma implementação, a vantagem é que o código está sempre desacoplado de qualquer outro que as utilize.

Isso muda com os polêmicos extension methods do Java 8, permitindo escrever uma implementação padrão nas interfaces, possibilitando suas evoluções, ao mesmo tempo que minimiza a quebra de compatibilidade.

Sempre que encontramos um código trabalhando com conexões de banco de dados, vemos referências à interface Connection, e nunca diretamente a MySQLConnection, OracleConnection, PostGreSQLConnection, ou qualquer outra implementação de um driver específico, apesar de esta possibilidade existir.

Desta forma, fica muito fácil trocar a implementação sem modificar todo o restante do código. Isso ocorre graças ao desacoplamento provido pelo uso de interfaces.

No caso do JDBC, essa escolha por uma implementação está centralizada na classe concreta DriverManager, que aqui age como uma factory.

O uso exagerado de reflection para invocar métodos dependendo de algumas condições pode ser muitas vezes substituído por interfaces.

Assim, a decisão de qual método invocar é deixada para a invocação virtual de método que o polimorfismo promove, diminuindo bastante a complexidade e aumentando a manutenibilidade, além de algum ganho de performance. Apesar de simples, existem seis possibilidades diferentes de execução do método, além de ele misturar diversos comportamentos que não possuem relação, isto é, responsabilidades diferentes para uma única classe: impostos, estoques, transferências e e-mails.

Tal comportamento pode ser composto por diversas partes menores e, para tanto, refatorações pequenas podem ser executadas.

 A mais simples seria a extração de quatro métodos, uma solução que simplifica o código atual, mas não aumenta sua coesão.

 É possível aumentar a coesão da classe extraindo tais métodos em quatro classes distintas, como RetemImposto, ProcessaEstoque, Transferencia e Confirmacao.

À medida que foi necessário, o código era refatorado.

 Após a quebra de um comportamento em diversas partes, é necessário juntar esses comportamentos novamente. Composição (composition) é esta união através da utilização de alguma prática de design, como no caso visto anteriormente, no qual o polimorfismo permite trabalhar de maneira uniforme com partes que executam tarefas distintas.

Um método que apresente um corpo com um número razoável de branches (como ifs e switches), ou que não permita a compreensão imediata de seu comportamento, pode ser refatorado para facilitar sua manutenção de Orientação a Objetos bem o código interno da classe mãe, o que é visto como uma quebra de encapsulamento.

 Podemos enxergar este problema em um exemplo encontrado no pacote java.util, com Properties e Hashtable.

A classe Properties herda de Hashtable e, portanto, instâncias de Properties possuem disponíveis métodos de Hashtable.

Mas, muitas vezes, esses métodos não fazem sentido, como no caso da Properties que lida apenas com Strings.

A quebra aparece quando temos uma instância de Properties e invocamos o método put(Object, Object), em vez de setProperty(String, String), podendo passar um Object qualquer como argumento.

Nesse caso, ao invocar posteriormente o método getProperty(String), recebemos um inesperado null.

 Não há como evitar que alguém invoque o método que trabalha com Object, a não ser documentando a respeito, como ocorre no javadoc dessa classe.

 Ter de garantir condições apenas através do javadoc é um contrato muito fraco, em especial dizendo que tal método não deve ser invocado com determinados tipos de objetos, algo que, em Java, poderia ser feito através da tipagem estática.

 Herança é comumente citada em cenários de reúso de código, mas não é a única solução.

 Usar composição permite reaproveitamento de código sem o efeito indesejado da quebra de encapsulamento.

Para que a classe Properties reaproveitasse os recursos já existentes em Hashtable com composição bastaria um atributo Hashtable privado, e o método setProperty delegaria a invocação para o hashtable.put.

Dessa maneira, expomos apenas os métodos que desejamos, sem possibilitar que fosse invocado o hashtable.put diretamente, nem que Properties pudesse ser passada como argumento para alguém que espera uma Hashtable.

 Melhor ainda, com o surgimento da API de Collections,  a implementação poderia ser facilmente trocada por algum Map sem causar problemas aos usuários da classe.

O uso abusivo de herança ocorre em diversos frameworks e APIs.

O Struts 1 é um exemplo deste uso e de seus diversos problemas relacionados; somos instruídos a estender Action e, ao mesmo tempo, a tomar muito cuidado com seu ciclo de vida, principalmente ao sobrescrever alguns métodos-chave dessa classe.

 Em linguagens com suporte a mixin, como Ruby, o acoplamento com as classes pode gerar complicações ainda maiores, que só serão percebidas em tempo de execução.

O problema da herança aparece, mesmo que mais sutilmente, no uso de classes abstratas para aplicar patterns, como o template method.

O novo método init(), sem parâmetros, deve ser reescrito; ele é invocado pelo próprio init(ServletConfig) após o ServletConfig ser guardado em um atributo.

Dentro de um init() reescrito podemos obter acesso ao ServletConfig através do getter da classe mãe, que agora estará corretamente atribuído.

Mesmo assim, em razão da tomada de decisão original na API de servlet, o desenvolvedor ainda deve conhecer como esses métodos funcionam na classe mãe, pois só assim saberá de todos os cuidados que são necessários.

Mas nem tudo é negativo. Todo esse design de servlets tem como objetivo principal trazer o polimorfismo e uma interface de uso uniforme para as servlets serem chamadas pelo container. Contudo, em Java, é possível usar interface para polimorfismo, sem os pontos negativos da herança como quebra de encapsulamento e alto acoplamento.

A API de servlets poderia ter usado um design baseado em interfaces com um strategy pattern por exemplo, evitando a herança.

Ao substituir a herança por interface, continuamos com o benefício do polimorfismo.

 Mas herança ainda traz outro grande benefício, o reaproveitamento de código, algo que interfaces puras não fazem.

Podemos então usar interfaces com composição, obtendo substitutos para todos os benefícios de herança sem correr o risco de cair na armadilha da quebra de encapsulamento e alto acoplamento.

 Repare que, desta forma, o acoplamento é bem menor, nenhuma das classes precisa conhecer o funcionamento interno das outras.

 Porém, se for necessário o uso de herança, alguns cuidados são importantes para minimizar a possível quebra de encapsulamento.

 Joshua Bloch, no Effective Java, fala de Design for Inheritance, com diversas práticas como evitar invocações entre métodos públicos, para que a sobrescrita de um não mude o comportamento de outro.

 Ao mesmo tempo que desejamos evitar herança, temos interesse em permitir mudança no nosso comportamento sem a necessidade de alterar ou copiar o código original.

Ao analisar a fundo as implementações atuais de Session e EntityManager do Hibernate, elas não retornam nem ArrayList, nem LinkedList, nem nenhuma coleção do java.util, e, sim, implementações de listas persistentes de pacotes do próprio Hibernate.

 Isto é possível, novamente, pelo desacoplamento provido pelo uso das interfaces.

 Além disso, o retorno das consultas com JPA e Hibernate são List, para deixar claro ao usuário que a ordem é importante.

 Manter a ordem de inserção e permitir acesso aleatório são características do contrato de List e são importantes para o resultado de consultas, pois podem definir uma ordenação (order by), uma ótima justificativa para optar por uma interface mais específica, e não usar Iterable ou Collection.

Nas principais APIs do Java, é fundamental programar voltado à interface.

Caso contrário, esse método poderia, e deveria, receber uma referência a InputStream, ficando mais flexível e podendo receber os mais diferentes tipos de streams, como argumento, que provavelmente não foram previamente imaginados.

Utilize sempre o tipo menos específico possível.

Repare que, muitas vezes, classes abstratas trabalham como interfaces, no sentido conceitual de orientação a objetos.

Classes abstratas possuem a vantagem de se poder adicionar-lhes um novo método não abstrato, sem quebrar o código já existente.

Já com o uso de interfaces(aqui, pensando na palavra-chave do Java), a adição de qualquer método acarretará a quebra das classes que a implementam.

Programe voltado à interface, não à implementação Casa do Código interfaces nunca definem nenhuma implementação, a vantagem é que o código está sempre desacoplado de qualquer outro que as utilize.

Isso muda com os polêmicos extension methods do Java 8, permitindo escrever uma implementação padrão nas interfaces, possibilitando suas evoluções, ao mesmo tempo que minimiza a quebra de compatibilidade.

Sempre que encontramos um código trabalhando com conexões de banco de dados, vemos referências à interface Connection, e nunca diretamente a MySQLConnection, OracleConnection, PostGreSQLConnection, ou qualquer outra implementação de um driver específico, apesar de esta possibilidade existir.

Desta forma, fica muito fácil trocar a implementação sem modificar todo o restante do código. Isso ocorre graças ao desacoplamento provido pelo uso de interfaces.

No caso do JDBC, essa escolha por uma implementação está centralizada na classe concreta DriverManager, que aqui age como uma factory.

O uso exagerado de reflection para invocar métodos dependendo de algumas condições pode ser muitas vezes substituído por interfaces.

Assim, a decisão de qual método invocar é deixada para a invocação virtual de método que o polimorfismo promove, diminuindo bastante a complexidade e aumentando a manutenibilidade, além de algum ganho de performance. Apesar de simples, existem seis possibilidades diferentes de execução do método, além de ele misturar diversos comportamentos que não possuem relação, isto é, responsabilidades diferentes para uma única classe: impostos, estoques, transferências e e-mails.

Tal comportamento pode ser composto por diversas partes menores e, para tanto, refatorações pequenas podem ser executadas.

 A mais simples seria a extração de quatro métodos, uma solução que simplifica o código atual, mas não aumenta sua coesão.

 É possível aumentar a coesão da classe extraindo tais métodos em quatro classes distintas, como RetemImposto, ProcessaEstoque, Transferencia e Confirmacao.

À medida que foi necessário, o código era refatorado.

 Após a quebra de um comportamento em diversas partes, é necessário juntar esses comportamentos novamente. Composição (composition) é esta união através da utilização de alguma prática de design, como no caso visto anteriormente, no qual o polimorfismo permite trabalhar de maneira uniforme com partes que executam tarefas distintas.

Um método que apresente um corpo com um número razoável de branches (como ifs e switches), ou que não permita a compreensão imediata de seu comportamento, pode ser refatorado para facilitar sua manutenção de Orientação a Objetos bem o código interno da classe mãe, o que é visto como uma quebra de encapsulamento.

 Podemos enxergar este problema em um exemplo encontrado no pacote java.util, com Properties e Hashtable.

A classe Properties herda de Hashtable e, portanto, instâncias de Properties possuem disponíveis métodos de Hashtable.

Mas, muitas vezes, esses métodos não fazem sentido, como no caso da Properties que lida apenas com Strings.

A quebra aparece quando temos uma instância de Properties e invocamos o método put(Object, Object), em vez de setProperty(String, String), podendo passar um Object qualquer como argumento.

Nesse caso, ao invocar posteriormente o método getProperty(String), recebemos um inesperado null.

 Não há como evitar que alguém invoque o método que trabalha com Object, a não ser documentando a respeito, como ocorre no javadoc dessa classe.

 Ter de garantir condições apenas através do javadoc é um contrato muito fraco, em especial dizendo que tal método não deve ser invocado com determinados tipos de objetos, algo que, em Java, poderia ser feito através da tipagem estática.

 Herança é comumente citada em cenários de reúso de código, mas não é a única solução.

 Usar composição permite reaproveitamento de código sem o efeito indesejado da quebra de encapsulamento.

Para que a classe Properties reaproveitasse os recursos já existentes em Hashtable com composição bastaria um atributo Hashtable privado, e o método setProperty delegaria a invocação para o hashtable.put.

Dessa maneira, expomos apenas os métodos que desejamos, sem possibilitar que fosse invocado o hashtable.put diretamente, nem que Properties pudesse ser passada como argumento para alguém que espera uma Hashtable.

 Melhor ainda, com o surgimento da API de Collections,  a implementação poderia ser facilmente trocada por algum Map sem causar problemas aos usuários da classe.

O uso abusivo de herança ocorre em diversos frameworks e APIs.

O Struts 1 é um exemplo deste uso e de seus diversos problemas relacionados; somos instruídos a estender Action e, ao mesmo tempo, a tomar muito cuidado com seu ciclo de vida, principalmente ao sobrescrever alguns métodos-chave dessa classe.

 Em linguagens com suporte a mixin, como Ruby, o acoplamento com as classes pode gerar complicações ainda maiores, que só serão percebidas em tempo de execução.

O problema da herança aparece, mesmo que mais sutilmente, no uso de classes abstratas para aplicar patterns, como o template method.

O novo método init(), sem parâmetros, deve ser reescrito; ele é invocado pelo próprio init(ServletConfig) após o ServletConfig ser guardado em um atributo.

Dentro de um init() reescrito podemos obter acesso ao ServletConfig através do getter da classe mãe, que agora estará corretamente atribuído.

Mesmo assim, em razão da tomada de decisão original na API de servlet, o desenvolvedor ainda deve conhecer como esses métodos funcionam na classe mãe, pois só assim saberá de todos os cuidados que são necessários.

Mas nem tudo é negativo. Todo esse design de servlets tem como objetivo principal trazer o polimorfismo e uma interface de uso uniforme para as servlets serem chamadas pelo container. Contudo, em Java, é possível usar interface para polimorfismo, sem os pontos negativos da herança como quebra de encapsulamento e alto acoplamento.

A API de servlets poderia ter usado um design baseado em interfaces com um strategy pattern por exemplo, evitando a herança.

Ao substituir a herança por interface, continuamos com o benefício do polimorfismo.

 Mas herança ainda traz outro grande benefício, o reaproveitamento de código, algo que interfaces puras não fazem.

Podemos então usar interfaces com composição, obtendo substitutos para todos os benefícios de herança sem correr o risco de cair na armadilha da quebra de encapsulamento e alto acoplamento.

 Repare que, desta forma, o acoplamento é bem menor, nenhuma das classes precisa conhecer o funcionamento interno das outras.

 Porém, se for necessário o uso de herança, alguns cuidados são importantes para minimizar a possível quebra de encapsulamento.

 Joshua Bloch, no Effective Java, fala de Design for Inheritance, com diversas práticas como evitar invocações entre métodos públicos, para que a sobrescrita de um não mude o comportamento de outro.

 Ao mesmo tempo que desejamos evitar herança, temos interesse em permitir mudança no nosso comportamento sem a necessidade de alterar ou copiar o código original.

Ao analisar a fundo as implementações atuais de Session e EntityManager do Hibernate, elas não retornam nem ArrayList, nem LinkedList, nem nenhuma coleção do java.util, e, sim, implementações de listas persistentes de pacotes do próprio Hibernate.

 Isto é possível, novamente, pelo desacoplamento provido pelo uso das interfaces.

 Além disso, o retorno das consultas com JPA e Hibernate são List, para deixar claro ao usuário que a ordem é importante.

 Manter a ordem de inserção e permitir acesso aleatório são características do contrato de List e são importantes para o resultado de consultas, pois podem definir uma ordenação (order by), uma ótima justificativa para optar por uma interface mais específica, e não usar Iterable ou Collection.

Nas principais APIs do Java, é fundamental programar voltado à interface.

Caso contrário, esse método poderia, e deveria, receber uma referência a InputStream, ficando mais flexível e podendo receber os mais diferentes tipos de streams, como argumento, que provavelmente não foram previamente imaginados.

Utilize sempre o tipo menos específico possível.

Repare que, muitas vezes, classes abstratas trabalham como interfaces, no sentido conceitual de orientação a objetos.

Classes abstratas possuem a vantagem de se poder adicionar-lhes um novo método não abstrato, sem quebrar o código já existente.

Já com o uso de interfaces(aqui, pensando na palavra-chave do Java), a adição de qualquer método acarretará a quebra das classes que a implementam.

Programe voltado à interface, não à implementação Casa do Código interfaces nunca definem nenhuma implementação, a vantagem é que o código está sempre desacoplado de qualquer outro que as utilize.

Isso muda com os polêmicos extension methods do Java 8, permitindo escrever uma implementação padrão nas interfaces, possibilitando suas evoluções, ao mesmo tempo que minimiza a quebra de compatibilidade.

Sempre que encontramos um código trabalhando com conexões de banco de dados, vemos referências à interface Connection, e nunca diretamente a MySQLConnection, OracleConnection, PostGreSQLConnection, ou qualquer outra implementação de um driver específico, apesar de esta possibilidade existir.

Desta forma, fica muito fácil trocar a implementação sem modificar todo o restante do código. Isso ocorre graças ao desacoplamento provido pelo uso de interfaces.

No caso do JDBC, essa escolha por uma implementação está centralizada na classe concreta DriverManager, que aqui age como uma factory.

O uso exagerado de reflection para invocar métodos dependendo de algumas condições pode ser muitas vezes substituído por interfaces.

Assim, a decisão de qual método invocar é deixada para a invocação virtual de método que o polimorfismo promove, diminuindo bastante a complexidade e aumentando a manutenibilidade, além de algum ganho de performance. Apesar de simples, existem seis possibilidades diferentes de execução do método, além de ele misturar diversos comportamentos que não possuem relação, isto é, responsabilidades diferentes para uma única classe: impostos, estoques, transferências e e-mails.

Tal comportamento pode ser composto por diversas partes menores e, para tanto, refatorações pequenas podem ser executadas.

 A mais simples seria a extração de quatro métodos, uma solução que simplifica o código atual, mas não aumenta sua coesão.

 É possível aumentar a coesão da classe extraindo tais métodos em quatro classes distintas, como RetemImposto, ProcessaEstoque, Transferencia e Confirmacao.

À medida que foi necessário, o código era refatorado.

 Após a quebra de um comportamento em diversas partes, é necessário juntar esses comportamentos novamente. Composição (composition) é esta união através da utilização de alguma prática de design, como no caso visto anteriormente, no qual o polimorfismo permite trabalhar de maneira uniforme com partes que executam tarefas distintas.

Um método que apresente um corpo com um número razoável de branches (como ifs e switches), ou que não permita a compreensão imediata de seu comportamento, pode ser refatorado para facilitar sua manutenção de Orientação a Objetos bem o código interno da classe mãe, o que é visto como uma quebra de encapsulamento.

 Podemos enxergar este problema em um exemplo encontrado no pacote java.util, com Properties e Hashtable.

A classe Properties herda de Hashtable e, portanto, instâncias de Properties possuem disponíveis métodos de Hashtable.

Mas, muitas vezes, esses métodos não fazem sentido, como no caso da Properties que lida apenas com Strings.

A quebra aparece quando temos uma instância de Properties e invocamos o método put(Object, Object), em vez de setProperty(String, String), podendo passar um Object qualquer como argumento.

Nesse caso, ao invocar posteriormente o método getProperty(String), recebemos um inesperado null.

 Não há como evitar que alguém invoque o método que trabalha com Object, a não ser documentando a respeito, como ocorre no javadoc dessa classe.

 Ter de garantir condições apenas através do javadoc é um contrato muito fraco, em especial dizendo que tal método não deve ser invocado com determinados tipos de objetos, algo que, em Java, poderia ser feito através da tipagem estática.

 Herança é comumente citada em cenários de reúso de código, mas não é a única solução.

 Usar composição permite reaproveitamento de código sem o efeito indesejado da quebra de encapsulamento.

Para que a classe Properties reaproveitasse os recursos já existentes em Hashtable com composição bastaria um atributo Hashtable privado, e o método setProperty delegaria a invocação para o hashtable.put.

Dessa maneira, expomos apenas os métodos que desejamos, sem possibilitar que fosse invocado o hashtable.put diretamente, nem que Properties pudesse ser passada como argumento para alguém que espera uma Hashtable.

 Melhor ainda, com o surgimento da API de Collections,  a implementação poderia ser facilmente trocada por algum Map sem causar problemas aos usuários da classe.

O uso abusivo de herança ocorre em diversos frameworks e APIs.

O Struts 1 é um exemplo deste uso e de seus diversos problemas relacionados; somos instruídos a estender Action e, ao mesmo tempo, a tomar muito cuidado com seu ciclo de vida, principalmente ao sobrescrever alguns métodos-chave dessa classe.

 Em linguagens com suporte a mixin, como Ruby, o acoplamento com as classes pode gerar complicações ainda maiores, que só serão percebidas em tempo de execução.

O problema da herança aparece, mesmo que mais sutilmente, no uso de classes abstratas para aplicar patterns, como o template method.

O novo método init(), sem parâmetros, deve ser reescrito; ele é invocado pelo próprio init(ServletConfig) após o ServletConfig ser guardado em um atributo.

Dentro de um init() reescrito podemos obter acesso ao ServletConfig através do getter da classe mãe, que agora estará corretamente atribuído.

Mesmo assim, em razão da tomada de decisão original na API de servlet, o desenvolvedor ainda deve conhecer como esses métodos funcionam na classe mãe, pois só assim saberá de todos os cuidados que são necessários.

Mas nem tudo é negativo. Todo esse design de servlets tem como objetivo principal trazer o polimorfismo e uma interface de uso uniforme para as servlets serem chamadas pelo container. Contudo, em Java, é possível usar interface para polimorfismo, sem os pontos negativos da herança como quebra de encapsulamento e alto acoplamento.

A API de servlets poderia ter usado um design baseado em interfaces com um strategy pattern por exemplo, evitando a herança.

Ao substituir a herança por interface, continuamos com o benefício do polimorfismo.

 Mas herança ainda traz outro grande benefício, o reaproveitamento de código, algo que interfaces puras não fazem.

Podemos então usar interfaces com composição, obtendo substitutos para todos os benefícios de herança sem correr o risco de cair na armadilha da quebra de encapsulamento e alto acoplamento.

 Repare que, desta forma, o acoplamento é bem menor, nenhuma das classes precisa conhecer o funcionamento interno das outras.

 Porém, se for necessário o uso de herança, alguns cuidados são importantes para minimizar a possível quebra de encapsulamento.

 Joshua Bloch, no Effective Java, fala de Design for Inheritance, com diversas práticas como evitar invocações entre métodos públicos, para que a sobrescrita de um não mude o comportamento de outro.

 Ao mesmo tempo que desejamos evitar herança, temos interesse em permitir mudança no nosso comportamento sem a necessidade de alterar ou copiar o código original.

Ao analisar a fundo as implementações atuais de Session e EntityManager do Hibernate, elas não retornam nem ArrayList, nem LinkedList, nem nenhuma coleção do java.util, e, sim, implementações de listas persistentes de pacotes do próprio Hibernate.

 Isto é possível, novamente, pelo desacoplamento provido pelo uso das interfaces.

 Além disso, o retorno das consultas com JPA e Hibernate são List, para deixar claro ao usuário que a ordem é importante.

 Manter a ordem de inserção e permitir acesso aleatório são características do contrato de List e são importantes para o resultado de consultas, pois podem definir uma ordenação (order by), uma ótima justificativa para optar por uma interface mais específica, e não usar Iterable ou Collection.

Nas principais APIs do Java, é fundamental programar voltado à interface.

Caso contrário, esse método poderia, e deveria, receber uma referência a InputStream, ficando mais flexível e podendo receber os mais diferentes tipos de streams, como argumento, que provavelmente não foram previamente imaginados.

Utilize sempre o tipo menos específico possível.

Repare que, muitas vezes, classes abstratas trabalham como interfaces, no sentido conceitual de orientação a objetos.

Classes abstratas possuem a vantagem de se poder adicionar-lhes um novo método não abstrato, sem quebrar o código já existente.

Já com o uso de interfaces(aqui, pensando na palavra-chave do Java), a adição de qualquer método acarretará a quebra das classes que a implementam.

Programe voltado à interface, não à implementação Casa do Código interfaces nunca definem nenhuma implementação, a vantagem é que o código está sempre desacoplado de qualquer outro que as utilize.

Isso muda com os polêmicos extension methods do Java 8, permitindo escrever uma implementação padrão nas interfaces, possibilitando suas evoluções, ao mesmo tempo que minimiza a quebra de compatibilidade.

Sempre que encontramos um código trabalhando com conexões de banco de dados, vemos referências à interface Connection, e nunca diretamente a MySQLConnection, OracleConnection, PostGreSQLConnection, ou qualquer outra implementação de um driver específico, apesar de esta possibilidade existir.

Desta forma, fica muito fácil trocar a implementação sem modificar todo o restante do código. Isso ocorre graças ao desacoplamento provido pelo uso de interfaces.

No caso do JDBC, essa escolha por uma implementação está centralizada na classe concreta DriverManager, que aqui age como uma factory.

O uso exagerado de reflection para invocar métodos dependendo de algumas condições pode ser muitas vezes substituído por interfaces.

Assim, a decisão de qual método invocar é deixada para a invocação virtual de método que o polimorfismo promove, diminuindo bastante a complexidade e aumentando a manutenibilidade, além de algum ganho de performance. Apesar de simples, existem seis possibilidades diferentes de execução do método, além de ele misturar diversos comportamentos que não possuem relação, isto é, responsabilidades diferentes para uma única classe: impostos, estoques, transferências e e-mails.

Tal comportamento pode ser composto por diversas partes menores e, para tanto, refatorações pequenas podem ser executadas.

 A mais simples seria a extração de quatro métodos, uma solução que simplifica o código atual, mas não aumenta sua coesão.

 É possível aumentar a coesão da classe extraindo tais métodos em quatro classes distintas, como RetemImposto, ProcessaEstoque, Transferencia e Confirmacao.

À medida que foi necessário, o código era refatorado.

 Após a quebra de um comportamento em diversas partes, é necessário juntar esses comportamentos novamente. Composição (composition) é esta união através da utilização de alguma prática de design, como no caso visto anteriormente, no qual o polimorfismo permite trabalhar de maneira uniforme com partes que executam tarefas distintas.

Um método que apresente um corpo com um número razoável de branches (como ifs e switches), ou que não permita a compreensão imediata de seu comportamento, pode ser refatorado para facilitar sua manutenção de Orientação a Objetos bem o código interno da classe mãe, o que é visto como uma quebra de encapsulamento.

 Podemos enxergar este problema em um exemplo encontrado no pacote java.util, com Properties e Hashtable.

A classe Properties herda de Hashtable e, portanto, instâncias de Properties possuem disponíveis métodos de Hashtable.

Mas, muitas vezes, esses métodos não fazem sentido, como no caso da Properties que lida apenas com Strings.

A quebra aparece quando temos uma instância de Properties e invocamos o método put(Object, Object), em vez de setProperty(String, String), podendo passar um Object qualquer como argumento.

Nesse caso, ao invocar posteriormente o método getProperty(String), recebemos um inesperado null.

 Não há como evitar que alguém invoque o método que trabalha com Object, a não ser documentando a respeito, como ocorre no javadoc dessa classe.

 Ter de garantir condições apenas através do javadoc é um contrato muito fraco, em especial dizendo que tal método não deve ser invocado com determinados tipos de objetos, algo que, em Java, poderia ser feito através da tipagem estática.

 Herança é comumente citada em cenários de reúso de código, mas não é a única solução.

 Usar composição permite reaproveitamento de código sem o efeito indesejado da quebra de encapsulamento.

Para que a classe Properties reaproveitasse os recursos já existentes em Hashtable com composição bastaria um atributo Hashtable privado, e o método setProperty delegaria a invocação para o hashtable.put.

Dessa maneira, expomos apenas os métodos que desejamos, sem possibilitar que fosse invocado o hashtable.put diretamente, nem que Properties pudesse ser passada como argumento para alguém que espera uma Hashtable.

 Melhor ainda, com o surgimento da API de Collections,  a implementação poderia ser facilmente trocada por algum Map sem causar problemas aos usuários da classe.

O uso abusivo de herança ocorre em diversos frameworks e APIs.

O Struts 1 é um exemplo deste uso e de seus diversos problemas relacionados; somos instruídos a estender Action e, ao mesmo tempo, a tomar muito cuidado com seu ciclo de vida, principalmente ao sobrescrever alguns métodos-chave dessa classe.

 Em linguagens com suporte a mixin, como Ruby, o acoplamento com as classes pode gerar complicações ainda maiores, que só serão percebidas em tempo de execução.

O problema da herança aparece, mesmo que mais sutilmente, no uso de classes abstratas para aplicar patterns, como o template method.

O novo método init(), sem parâmetros, deve ser reescrito; ele é invocado pelo próprio init(ServletConfig) após o ServletConfig ser guardado em um atributo.

Dentro de um init() reescrito podemos obter acesso ao ServletConfig através do getter da classe mãe, que agora estará corretamente atribuído.

Mesmo assim, em razão da tomada de decisão original na API de servlet, o desenvolvedor ainda deve conhecer como esses métodos funcionam na classe mãe, pois só assim saberá de todos os cuidados que são necessários.

Mas nem tudo é negativo. Todo esse design de servlets tem como objetivo principal trazer o polimorfismo e uma interface de uso uniforme para as servlets serem chamadas pelo container. Contudo, em Java, é possível usar interface para polimorfismo, sem os pontos negativos da herança como quebra de encapsulamento e alto acoplamento.

A API de servlets poderia ter usado um design baseado em interfaces com um strategy pattern por exemplo, evitando a herança.

Ao substituir a herança por interface, continuamos com o benefício do polimorfismo.

 Mas herança ainda traz outro grande benefício, o reaproveitamento de código, algo que interfaces puras não fazem.

Podemos então usar interfaces com composição, obtendo substitutos para todos os benefícios de herança sem correr o risco de cair na armadilha da quebra de encapsulamento e alto acoplamento.

 Repare que, desta forma, o acoplamento é bem menor, nenhuma das classes precisa conhecer o funcionamento interno das outras.

 Porém, se for necessário o uso de herança, alguns cuidados são importantes para minimizar a possível quebra de encapsulamento.

 Joshua Bloch, no Effective Java, fala de Design for Inheritance, com diversas práticas como evitar invocações entre métodos públicos, para que a sobrescrita de um não mude o comportamento de outro.

 Ao mesmo tempo que desejamos evitar herança, temos interesse em permitir mudança no nosso comportamento sem a necessidade de alterar ou copiar o código original.

3º resumo do livro .

                                              35 á 44       

Esse problema é muito mais grave do que aparenta; o tipo de erro que pode surgir é de difícil interpretação.

O menor dos problemas seria ter o comportamento da versão antiga do Hibernate.

 Mais grave é quando há métodos novos no JAR mais recente; a interface Session do Hibernate antigo seria carregada, e quando sua aplicação APP-nova invocar um método que só existe na versão nova, NoSuchMethod Error será lançado! Isso causa uma enorme dor de cabeça, pois o desenvolvedor fica confuso ao ver que o código compilou perfeitamente, mas durante a execução a JVM indica que aquele método não existe.

NoSuchMethodError é um forte indicador de que o código foi compilado esperando uma versão diferente de uma biblioteca que a encontrada em tempo de execução, provavelmente por causa de os jars estarem espalhados e compartilhados.

O comportamento da aplicação pode ser mais errático; imagine que a aplicação APP-nova utiliza diretamente uma classe do Hibernate que só existe na versão mais nova, como a TypeResolver. Carregamento de classes e classloader hell Casa do Código Essa confusão é similar ao DLL Hell, frequentemente chamado de Classloader hell na plataforma Java.

Em algumas configurações de classloaders diferentes, que carregam classes de diretórios em comum, pode aparecer um ClassCastException curioso e de difícil discernimento.

Se uma referência a um objeto do tipo Produto for passada como argumento para um método que recebe um objeto também deste tipo, mas esta classe tiver sido carregada por um classloader diferente, a exceção será lançada.

O desenvolvedor ficará confuso ao ver uma ClassCastException em uma invocação de método em que nem mesmo há um casting. Isso acontece porque, como vimos, em tempo de execução a identidade de uma classe não é apenas seu fully qualified name, mas também o classloader que a carregou.

Assim, uma classe com mesmo nome, mas carregada por classloaders diferentes, é outra. Isto é chamado de runtime identity e muito importante para permitir que containers tenham mais de uma versão da mesma classe na memória (como o exemplo das Sessions de diferentes versões do Hibernate.

A arquitetura tradicional dificulta que uma aplicação sobrescreva algum componente do Container classloader, mas esse modelo invertido (padrão no Tomcat, por exemplo) permite esse cenário.

 A ordem de resolução das classes passa a ser: primeiro o Bootstrap, depois as classes da aplicação e depois as compartilhadas do Container.

Criando seu ClassLoader Podemos instanciar a URLClassLoader para carregar classes a partir de um conjunto dado de URLs.

 Isto indica que seu parent classloader será o bootstrap diretamente.

Elas serão carregadas em ambos os casos pelo bootstrap classloader (representado pelo null), pois não há como evitar que esse classloader seja consultado em razão da questão de segurança já discutida.

 Ao remover o null do construtor, o URLClassLoader terá como parent o classloader que carregou o código sendo executado (o Application classloader), fazendo com que o carregamento da classe seja delegado para este, antes de ser tentado pelo nosso URLClassLoader.

Carregamento de classes e classloader hell Casa do Código O parent classloader será sempre consultado antes de o classloader de hierarquia “mais baixa” tentar carregar uma determinada classe.

 Lembre-se de que este é o motivo pelo qual jogar os JARs na variável de ambiente CLASSPATH pode acabar escondendo versões diferentes da mesma classe que estejam ao alcance de classloaders mais “baixos” na hierarquia.

Caso elas fossem incluídas dentro da biblioteca padrão, qualquer outro projeto que necessitasse delas em uma versão diferente (tanto mais atual quanto mais antiga) teria sempre a versão do rt.jar Para contornar o problema, a Sun colocou as classes que seriam do pacote org.apache para dentro de com.sun.org.apache.

Sem dúvida uma maneira deselegante, mas que evitou o problema de versionamento.

Este problema tornou-se ainda mais frequente.

 Nesses dois casos, para contornar o problema, utilizamos o recurso de endorsed JARs.

 Através da linha de comando (-Djava.endorsed.dirs), você especifica diretórios que devem ter prioridade na procura de classes antes que o diretório ext do Java SE seja consultado.

É uma forma de o administrador do sistema dizer que confia em (endossa) determinados JARs. Alguns servidores de aplicação possuem um diretório especial onde você pode jogar os JARs a serem endossados.

É comum aparecerem problemas com OutOfMemoryError, acusando que este espaço se esgotou, dado um número muito grande de classes carregadas.

 Em particular, isso ocorre facilmente depois de alguns hot deploys em um container.

 Toda classe carregada tem uma referência para o seu classloader, assim como todo classloader referencia todas as classes carregadas por ele.

 Isso para que possa devolver sempre a mesma classe no caso de outra invocação subsequente para o mesmo full qualified name, agindo como uma factory que cacheia suas instanciações. Esse relacionamento bidirecional entre Class e ClassLoader faz com que os objetos Class só sejam coletados pelo garbage collector junto com o classloader inteiro e todas as outras classes associadas.

Logo, a única maneira de um objeto Class ser coletado é se todas as referências para todas as classes do seu classloader forem liberadas também.

 Ao realizar o hot deploy de uma aplicação, o próprio container libera as referências das classes antigas, possibilitando a coleta do classloader do contexto.

Isso deveria ser suficiente para que o contexto fosse inteiramente liberado da memória, mas, na prática, outro classloader acima daquele da WebApplication segura referências para classes da aplicação.

O carregamento de um único driver JDBC é capaz de segurar na memória o contexto inteiro de uma aplicação que não é mais necessária.

 Na prática, é quase impossível uma aplicação Java conseguir evitar esses leaks de classloaders, por isso em algum momento surgem os OutOfMemoryError frequentes nos hot deploys, sendo mais um motivo para evitá-los em produção.

Todo o paradigma da orientação a objetos, seus princípios e boas práticas procuram trazer esses benefícios para o design.

 Ao pensar no sistema como um todo, outras questões de mais alto nível surgem, em especial aquelas que tratam da forma como os objetos se relacionam e sua organização dentro e entre sistemas.

Ao relacionar dois objetos distintos, deve-se levar em conta as boas práticas que serão discutidas nesse capítulo, como a diminuição do acoplamento entre objetos.

 Programe voltado à interface, não à implementação ao trabalhar com coleções, escolher a implementação certa para cada caso é uma tarefa difícil.

Cada uma delas, como ArrayList, LinkedList ou HashSet, é melhor 3.1. Programe voltado à interface, não à implementação Casa do Código para resolver determinas categorias de problemas.

 Pode ser muito arriscado escrever todo o código da aplicação dependente de uma decisão antecipada.

 Apesar disso, grande parte dos desenvolvedores opta por sempre utilizar ArrayList desde o início sem critério algum.

O problema em realizar uma mudança como esta, de implementação, é que todo código que usava o retorno do método como ArrayList quebra, mesmo que só usássemos métodos que também existem definidos em HashSet.

Seria preciso alterar todos os lugares que dependem de alguma forma desse método.

Mas sempre há também informações semânticas implícitas na utilização desse método, e que não são expostos através da assinatura.

Um exemplo de acoplamento semântico está em depender da informação de que uma List permite dados duplicados, enquanto um Set garante unicidade dos elementos.

Como problemas no acoplamento sintático são encontrados em tempo de compilação, os semânticos somente o são em execução, daí um motivo da importância de testes que garantam o comportamento esperado.

 Desta forma, alterar a implementação torna-se sempre muito mais custoso, caracterizando o alto acoplamento que tanto se procura evitar.

 Para minimizar esse problema, é possível usar um tipo de retorno de método mais genérico, que contemple diversas implementações possíveis, fazendo com que os usuários do método não dependam em nada de uma implementação específica.

Usar interfaces Java é um grande benefício nestes casos, pois ajuda a garantir que nenhum código dependa de uma implementação específica, pois interfaces não carregam nenhum detalhe de implementação.

A escolha da interface ideal vai depender do que você quer permitir que o código invocador possa utilizar e realizar na referência retornada.

Programe voltado à interface, não à implementação Casa do Código de elementos que essa coleção possui, nem os elementos de maneira aleatória através de um índice.

 Devemos procurar um balanço entre o desacoplamento e a necessidade do nosso código.

 Esta é a ideia do Princípio de Segregação de Interfaces: clientes não devem ser forçados a depender de interfaces que não usam.

Uma implementação de lista, MeuProprioArrayList, feita pelo desenvolvedor é provavelmente mais instável que a interface List, já que as forças que a impedem de mudar são fracas (não há outras classes utilizando essa implementação).

15 á 24

Com a JVM no centro da Plataforma Java, conhecer seu funcionamento interno é essencial para qualquer aplicação Java.

Dentre os diversos tópicos associados à JVM, destacamos alguns que julgamos vitais para todo desenvolvedor Java.

Durante muito tempo, uma das maiores dificuldades na hora de programar era o gerenciamento de memória.

Hoje, em todas as plataformas modernas, Java inclusive, temos gerenciamento de memória automático através de algoritmos de coleta de lixo.

 O Garbage Collector (GC) é um dos principais componentes da JVM e responsável pela liberação da memória que não esteja mais sendo utilizada.

Mas não conseguimos determinar o momento exato em que essa coleta ocorrerá; isto depende totalmente do algoritmo do garbage collector.

Em geral, o GC não fará coletas para cada objeto liberado; ele deixará o lixo acumular um pouco para fazer coletas maiores, de maneira a otimizar o tempo gasto.

 Essa abordagem, muitas vezes, é bem mais eficiente, além de evitar a fragmentação da memória, que poderia aparecer no caso de um programa que aloque e libere a memória de maneira ingênua.

Em geral, a primeira ideia que aparece ao se pensar em GC é que ele fica varrendo a memória periodicamente e libera aqueles objetos que estão sem referência.

Esse algoritmo envelheceu, da mesma forma que o ingênuo reference counting. Estudos extensivos com várias aplicações e seus comportamentos em tempo de execução ajudaram a formar premissas essenciais para algoritmos modernos de GC.

Com base nessas observações, chegou-se ao que hoje é conhecido como o algoritmo generational copying, usado como base na maioria das máquinas virtuais.

 É simples observar esse padrão geracional em muitos programas escritos em Java, quando objetos são criados dentro de um método.

 Assim que o método termina, alguns objetos que foram criados lá ficam sem referências e se tornam elegíveis à coleta de lixo, isto é, eles sobreviveram apenas durante a execução do método e tiveram vida curta.

Mesmo métodos curtos e simples, como toString, acabam gerando objetos intermediários que rapidamente não serão mais referenciados: public String toString() { return “[ contatos: “ + listaDeContatos + “]”;  Java Virtual Machine } Aqui, durante a concatenação das três partes da String, um StringBuilder será utilizado, e o mesmo vai ocorrer para a invocação implícita do toString da coleção listaDeContatos, que gera uma String a partir de outro StringBuilder.

Os objetos que sobrevivem à coleta são, então, copiados para a geração seguinte, e todo o espaço da geração nova é considerado disponível novamente.

Esse processo de cópia de objetos sobreviventes é que dá nome ao algoritmo.

Mas seu grande trunfo é que ele age nos objetos sobreviventes, e não nos descartados, como faria um algoritmo tradicional.

 No descarte, os objetos não são verdadeiramente apagados da memória; o GC apenas marca a memória como disponível.

E, embora uma cópia seja relativamente custosa, copiar apenas os poucos sobreviventes é mais rápido que liberar, um por um, os diversos objetos mortos.

Novos objetos são alocados na young e, assim que ela estiver lotada, é efetuado o chamado minor collect.

 Major collects são também chamados FullGC, e costumam demorar bem mais, já que varrem toda a memória, chegando a travar a aplicação nos algoritmos mais tradicionais (não paralelos). É possível fazer uma observação sucinta do comportamento do GC mesmo sem um profiler, bastando usar a opção -verbose:gc ao iniciar a JVM.

É importante observar esses valores para perceber se o programa não está gastando muito tempo nos GCs, ou se as coletas estão sendo ineficientes.

 Na verdade, como os algoritmos estão adaptados segundo a hipótese das gerações, o melhor são muitos pequenos objetos que logo se tornam desnecessários, do que poucos que demoram para sair da memória.

 Em alguns casos, até o tamanho do objeto pode influenciar; na JRockit, por exemplo, objetos grandes são alocados direto na old generation , logo não participam da cópia geracional.

 A melhor técnica que um desenvolvedor pode utilizar é encaixar a demanda de memória da sua aplicação na hipótese das gerações e nas boas práticas de orientação a objetos, criando objetos pequenos e encapsulados de acordo com sua necessidade.

Se o custo de criação do objeto não for grande, segurar suas referências ou fazer caches acaba sendo pior.

Obviamente, isso exclui casos em que o custo de criação é grande, como um laço de concatenação de String através do operador +; nesse caso, é melhor usar StringBuilders ou StringBuffers.

Algoritmos ingênuos de GC costumam causar grande fragmentacão, porque apenas removem os objetos não mais usados, e os sobreviventes acabam espalhados e cercados de áreas vazias.

O generational copying copia os objetos sobreviventes para outra geração de forma agrupada, e a memória da geração anterior é liberada em um grande e único bloco, sem fragmentação. Fora isso, outras estratégias de compactação de memória ainda podem ser usadas pela JVM, inclusive na old generation.

 É importante notar que isso só é possível por causa do modelo de memória do Java, que abstrai totalmente do programa a forma como os ponteiros .

 É possível mudar objetos de lugar a qualquer momento, e a VM precisa apenas atualizar seus ponteiros internos, o que seria muito difícil de realizar em um ambiente com acesso direto a ponteiros de memória.

A primeira especifica o tamanho inicial do heap, e a segunda, o tamanho máximo. Inicialmente, a JVM aloca no sistema operacional a quantidade Xms de memória de uma vez, e essa memória nunca é devolvida para o sistema.

 A alocação de memória para os objetos Java é resolvida dentro da própria JVM, e não no sistema operacional.

Conforme mais memória é necessária, a JVM aloca em grandes blocos até o máximo do Xmx (se precisar de mais que isso, um OutOfMemoryError é lançado).

 É muito comum rodar a máquina virtual com valores iguais de Xms e Xmx, fazendo com que a VM aloque memória no sistema operacional apenas no início, deixando de depender do comportamento específico do SO.

 Conhecer essas e outras opções do garbage collector da sua JVM pode impactar bastante na performance de uma aplicação.

1 á 14       

A plataforma Java atingiu a liderança devido a algumas características relacionadas ao seu processo de evolução e especificação, junto com a participação forte e ativa da comunidade. Conhecer bem o ecossistema Java revela seus pontos fortes e também as desvantagens e limitações que podemos enfrentar ao adotá-la.

 Java como plataforma, além da linguagem

 É fundamental conhecer com que objetivos a plataforma Java foi projetada, a fim de entender com profundidade os motivos que a levaram a ser fortemente adotada no lado do servidor. Java é uma plataforma de desenvolvimento criada pela Sun, que teve seu lançamento público em 1995.

 Ela vinha sendo desenvolvida desde 1991 com o nome Oak, liderada por James Gosling.

O mercado inicial do projeto Oak compunha-se de dispositivos eletrônicos, como os set-top box. Desde a sua concepção, a ideia de uma plataforma de desenvolvimento e execução sempre esteve presente.

A plataforma Java é uma completa plataforma de desenvolvimento e execução.

 Esta plataforma é composta de três pilares: a máquina virtual Java (JVM), um grande conjunto de APIs e a linguagem Java.

Uma aplicação tradicional em C, por exemplo, é escrita em uma linguagem que abstrai as operações de hardware e é compilada para linguagem de máquina. Nesse processo de compilação, é gerado um executável com instruções de máquina específicas para o sistema operacional e o hardware em questão.

Seu papel é executar as instruções de máquina genéricas no Sistema Operacional e no hardware específico sob o qual estiver rodando executado em dois sistemas operacionais diferentes.

 É preciso instalar uma JVM específica para o sistema operacional e o hardware que se vai usar.

 Por esta razão, o Java possui duas distribuições diferentes: o JDK (Java Development Kit), que é focado no desenvolvedor e traz, além da VM, compilador e outras ferramentas úteis; e o JRE (Java Runtime Environment), que traz apenas o necessário para se executar um aplicativo Java (a VM e as APIs), voltado ao usuário final.

 Temos a garantia de que isso funcionará, pois uma JVM, para ganhar este nome, tem de passar por uma bateria de testes da Sun, garantindo compatibilidade com as especificações.

 A plataforma Microsoft .NET também é uma especificação e, por este motivo, o grupo Mono pode implementar uma versão para o Linux.

 Os bytecodes Java

 A JVM é, então, o ponto-chave para a portabilidade e a performance da plataforma Java. Como vimos, ela executa instruções genéricas compiladas a partir do nosso código, traduzindo­-as para as instruções específicas do sistema operacional e do hardware utilizados.

O nome bytecode vem do fato de que cada opcode (instrução) tem o tamanho de um byte e, portanto, a JVM tem capacidade de rodar até 256 bytecodes diferentes (embora o Java 7 possua apenas 205).

A plataforma Java tem ido cada vez mais na direção de ser um ambiente de execução multilinguagem, tanto através da Scripting API quanto por linguagens que compilam direto para o bytecode.

A JVM é, portanto, um poderoso executor de bytecodes, e não interessa de onde eles vêm, independentemente de onde estiver rodando.

 É por este motivo que muitos afirmam que a linguagem Java não é o componente mais importante da plataforma, mas, sim, a JVM e o bytecode.

Especificações ajudam ou atrapalham?

A Plataforma Java é bastante lembrada por suas características de abertura, portabilidade e até liberdade.

 Há o aspecto técnico, com independência de plataforma e portabilidade de sistema operacional desde o início da plataforma. Há também a comunidade com seus grupos de usuários por todo o mundo, sempre apoiados e incentivados pela Sun/Oracle suas mãos.

Quem guia os rumos do Java é o JCP, com suas centenas de empresas e desenvolvedores participantes, que ajudam a especificar as novas tecnologias e a decidir novos caminhos.

 Mas as JSR definem apenas especificações de produtos relacionados ao Java, e não implementações.

 Até há uma implementação de referência feita pelo JCP, mas, no fundo, o papel do órgão é criar grandes documentos que definem como tudo deve funcionar. O que usamos na prática é uma implementação compatível com a especificação oficial.

 Quando baixamos o JDK da Sun/Oracle, o que vem não é o Java SE, mas, sim, uma implementação feita pela empresa seguindo a especificação. Mas não somos obrigados a usar a HotSpot, implementação da Sun/Oracle. Podemos usar outras, como a J9 da IBM, a JRockit da BEA/Oracle, ou o Harmony da Apache, e, ainda, JVMs mais específicas para certos sabores do Unix, dispositivos móveis e hardwares mais particulares.

 As diferenças entre os produtos de diferentes fabricantes estão no suporte oferecido, documentação, facilidades extras de gerenciamento, otimizações, e até extensões proprietárias. Sobre este último ponto vale uma ressalva: praticamente todo fabricante estende a especificação oficial com APIs proprietárias, seja com o objetivo de tapar lacunas na especificação, seja para ter algum destaque a mais em relação a seus concorrentes.

 Não há problema algum nisso, mas é preciso ter consciência de que, ao usar um recurso proprietário, é possível perder portabilidade e independência do fabricante.

A linguagem Java vive uma situação similar. Desenvolvida para resolver determinados problemas, as barreiras que apresenta são muitas vezes superadas pela mistura com outras linguagens dentro da plataforma Java.

Desenvolvedores precisam de novos treinamentos, o Visual Studio é alterado e diversas bibliotecas deixam de funcionar.

Uma funcionalidade pode ser tecnicamente excelente para determinados usuários, mas, se for contra os interesses de um dos membros do comitê, poderá ser deixada de fora.

Porém, na prática, é raro trocarmos a implementação de uma especificação, a não ser talvez considerando diferentes ambientes, como ir do desenvolvimento para a produção.

 Nesse sentido, se o projeto já usa Hibernate, a especificação pode mais limitar que engradecer o projeto.

 Além disso, otimizações costumam ser feitas para implementações específicas.

Uma grande vantagem das especificações é a certeza de que conhecê-la permitirá ao desenvolvedor trabalhar em projetos que tenham diferentes implementações.

Claro que haverá diferenças a serem aprendidas, mas ter a especificação diminui bastante esse aprendizado.

A plataforma Java, diferente de muitas outras, tem essa possibilidade de escolha disponível em suas diversas ramificações.

Quando for preciso uma especificação de alguma tecnologia, provavelmente a plataforma Java terá algo disponível.

Outras necessidades foram resolvidas com a manipulação de bytecodes em tempo de execução com a explosão na adoção de bibliotecas com esse fim, como ASM ou Javassist.

Há ainda linguagens como Groovy e outras foram desenvolvidas para facilitar a criação de código mais adequado à tipagem dinâmica.

 Com isso, outras linguagens, até então consideradas secundárias pelo mercado, começaram a tomar força por possuírem maneiras diferenciadas de resolver os mesmos problemas, mostrando-se mais produtivas em determinadas situações.

Para aplicações Web em Java, por exemplo, é comum a adoção de outras linguagens.

 Grande parte do tempo, utiliza-se CSS para definir estilos, HTML para páginas, JavaScript para código que será rodado no cliente, SQL para bancos de dados, XMLs de configuração e expression language (EL) nos JSPs.

 Mas, se já usamos a linguagem certa para a tarefa certa, por que não aproveitar esta prática em toda a aplicação?

Hoje, a JVM é capaz de interpretar e compilar código escrito em diversas linguagens.

Com o Rhino, código JavaScript pode ser executado dentro da JVM. Assim como código Ruby pode ser executado com JRuby, Python com Jython e PHP com Quercus.

 Existem linguagens criadas especificamente para rodar sobre a JVM, como Groovy, Beanshell, Scala e Clojure.

O alemão Robert Tolksdorf mantém uma lista da maioria das linguagens suportadas pela JVM em seu site, onde é possível encontrar até mesmo implementações da linguagem Basic. Muitos perguntam qual seria a utilidade de executar, por exemplo, código JavaScript na JVM.

A resposta está nas características da linguagem, afinal, a plataforma será a mesma.

 Apesar de todo o preconceito que existe sobre ela, JavaScript vem se tornando uma linguagem de primeira linha, que é adotada em outros pontos de nossa aplicação, e não apenas no contato final com o cliente.

  O código continua sendo executado dentro da JVM com todas as otimizações ligadas à compilação sob demanda do mesmo.

 Não é à toa que as novas versões do Hibernate Validator permitem que sua validação server side seja em JavaScript.

É comum encontrar aplicações Java rodando Ruby durante o processo de build, através de ferramentas como Rake e Cucumber, para, por exemplo, efetuar os testes end-to-end através de um browser.

O Rails rodando sob a JRuby é cada vez mais adotado.

É a primeira vez que um novo bytecode é adicionado à plataforma para não ser usado pela linguagem Java em si, mas por outras linguagens.

 Ainda seguindo esse caminho da linguagem correta no instante adequado, surgiu um forte movimento que busca o bom uso de linguagens apropriadas ao domínio a ser atacado, as Domain Specific Languages.

 Existem diversos tipos, características e maneiras de criá-las, sempre com o intuito de trazer para o código uma legibilidade que seja mais natural.

Quando Java quebrou a barreira de que máquinas virtuais seriam necessariamente lentas, ajudou a desconstruir também o mito similar a linguagens funcionais, que existem em versões interpretadas quanto compiladas.

A facilidade trazida pelas closures, a partir do Java SE 8,é também um diferencial a ser considerado, diminuindo drasticamente o número de classes anônimas, tornando o código mais legível e menos verboso.