JavaRush /Blogue Java /Random-PT /10 coisas que você não sabia sobre Java
minuteman
Nível 32

10 coisas que você não sabia sobre Java

Publicado no grupo Random-PT
Então, você começou recentemente a trabalhar com Java? Lembra-se dos dias em que era chamado de "Oak", quando a orientação a objetos ainda era um tema quente, quando o pessoal de C++ pensava que Java não tinha chance e quando ninguém tinha ouvido falar de miniaplicativos? Posso presumir que você nem sabe metade das coisas a seguir. Vamos começar a semana com algumas surpresas interessantes sobre o funcionamento interno do Java. 10 coisas que você não sabia sobre Java - 11. Não existe exceção verificada. Isso mesmo! A JVM não tem ideia disso, apenas a linguagem Java faz. Hoje todos concordam que as exceções verificadas foram um erro. Como Bruce Eckel disse em sua palestra final na GeeCON em Praga, nenhuma outra linguagem desde Java usa exceções verificadas, mesmo o Java 8 não as cobre mais na nova API Streams (o que pode ser um pouco incômodo quando seus lambdas usam IO ou JDBC ). Você quer uma prova de que a JVM não sabe disso? Tente o seguinte código: Isso não apenas compilará, mas também lançará uma SQLException, você nem precisa usar o @SneakyThrows do Lombok para isso. 2. Você pode ter métodos sobrecarregados que diferem apenas em seus tipos de retornopublic class Test { // No throws clause here public static void main(String[] args) { doThrow(new SQLException()); } static void doThrow(Exception e) { Test. doThrow0(e); } @SuppressWarnings("unchecked") static void doThrow0(Exception e) throws E { throw (E) e; } } Isso não será compilado, certo? class Test { Object x() { return "abc"; } String x() { return "123"; } } Certo. A linguagem Java não permite que dois métodos sejam substituídos de forma equivalente dentro da mesma classe ao mesmo tempo, independentemente de suas diferenças em lançamentos ou tipos de retorno. Mas espere um minuto. Verifique a documentação novamente para Class.getMethod(String, Class…). Diz: Observe que é possível que haja mais de um método correspondente em uma classe, porque enquanto a linguagem Java proíbe vários métodos com a mesma assinatura, mas tipos de retorno diferentes, a Java Virtual Machine não o faz. Essa flexibilidade em uma máquina virtual pode ser usada para implementar vários recursos de linguagem. Por exemplo, retornos covariantes podem ser feitos com métodos de ponte; O método bridge e o método substituído teriam a mesma assinatura, mas tipos de retorno diferentes. Uau, isso faz sentido. Na verdade, muita coisa acontece quando você escreve o seguinte: Observe o bytecode gerado: Então, t é na verdade um objeto no bytecode. Isto é bem compreendido. O método de ponte sintética é, na verdade, gerado pelo compilador porque o tipo de retorno Parent.x() pode ser esperado em certas partes das chamadas. Adicionar genéricos sem esses métodos de ponte não será mais possível na representação binária. Portanto, alterar a JVM para permitir tal recurso produziu menos problemas (o que também permite a substituição do método covariante como efeito colateral...) Inteligente, certo? 3. Todos os itens a seguir são matrizes bidimensionais. Isso é realmente verdade. Mesmo que o seu analisador mental não consiga entender imediatamente o tipo de retorno dos métodos descritos acima, eles são todos iguais! Como o próximo trecho de código. Você acha que isso é loucura? A quantidade de oportunidades para escrever também simplesmente explode a imaginação! Anotação de tipo. Um dispositivo cujo mistério perde apenas para seu poder. Ou em outras palavras: quando eu faço meu último commit pouco antes das minhas férias de 4 semanas. Dou-lhe permissão para usá-lo da maneira que desejar. 4. Você não receberá uma condicional Então você pensou que já sabia tudo sobre condicionais quando começou a usá-las? Deixe-me decepcioná-lo - você estava errado. A maioria de vocês pensará que os dois exemplos a seguir são equivalentes: equivalente a isto? Não. Vamos fazer um teste rápido. O programa irá gerar o seguinte: Sim! O operador condicional executará conversões de tipo, se necessário. Porque caso contrário você esperaria que o programa lançasse uma NullPointerException? 5. Você também não receberá um operador de atribuição composto. A desenvoltura é suficiente? Vejamos os dois trechos de código a seguir: abstract class Parent { abstract T x(); } class Child extends Parent { @Override String x() { return "abc"; } } // Method descriptor #15 ()Ljava/lang/String; // Stack: 1, Locals: 1 java.lang.String x(); 0 ldc [16] 2 areturn Line numbers: [pc: 0, line: 7] Local variable table: [pc: 0, pc: 3] local: this index: 0 type: Child // Method descriptor #18 ()Ljava/lang/Object; // Stack: 1, Locals: 1 bridge synthetic java.lang.Object x(); 0 aload_0 [this] 1 invokevirtual Child.x() : java.lang.String [19] 4 areturn Line numbers: [pc: 0, line: 1] class Test { int[][] a() { return new int[0][]; } int[] b() [] { return new int[0][]; } int c() [][] { return new int[0][]; } } class Test { int[][] a = {{}}; int[] b[] = {{}}; int c[][] = {{}}; } @Target(ElementType.TYPE_USE) @interface Crazy {} class Test { @Crazy int[][] a1 = {{}}; int @Crazy [][] a2 = {{}}; int[] @Crazy [] a3 = {{}}; @Crazy int[] b1[] = {{}}; int @Crazy [] b2[] = {{}}; int[] b3 @Crazy [] = {{}}; @Crazy int c1[][] = {{}}; int c2 @Crazy [][] = {{}}; int c3[] @Crazy [] = {{}}; } 10 coisas que você não sabia sobre Java - 2 Object o1 = true ? new Integer(1) : new Double(2.0); Object o2; if (true) o2 = new Integer(1); else o2 = new Double(2.0); System.out.println(o1); System.out.println(o2); 1.0 1 Integer i = new Integer(1); if (i.equals(1)) i = null; Double d = new Double(2.0); Object o = true ? i : d; // NullPointerException! System.out.println(o); i += j; i = i + j; Intuitivamente, eles deveriam ser iguais, certo? Mas você sabe o que - eles são diferentes. A especificação JLS diz: Uma expressão composta do tipo E1 op = E2 é equivalente a E1 = (T) ((E1) op (E2)), onde T é o tipo de E1, exceto que E1 é avaliado apenas uma vez. Um bom exemplo é usar *= ou /= : byte b = 10; b *= 5.7; System.out.println(b); // prints 57 or: or byte b = 100; b /= 2.5; System.out.println(b); // prints 40 : char ch = '0'; ch *= 1.1; System.out.println(ch); // prints '4' or: char ch = 'A'; ch *= 1.5; System.out.println(ch); // prints 'a' Então, essa ainda é uma ferramenta útil? 6. Números inteiros aleatórios Agora, uma tarefa mais difícil. Não leia a solução. Veja se você consegue encontrar a resposta sozinho. Quando executo o seguinte programa: for (int i = 0; i < 10; i++) { System.out.println((Integer) i); } Às vezes recebo a seguinte saída: 92 221 45 48 236 183 39 193 33 84 Mas como isso é possível? Ok, a resposta está em substituir o cache inteiro do JDK por meio de reflexão e, em seguida, usar o boxe e o unboxing automáticos. Não faça isso sem permissão de um adulto! Ou seja: 10 coisas que você não sabia sobre Java - 3 7. GOTO Um dos meus favoritos. Java tem GOTO! Escreva isto: int goto = 1; e você verá isto: Isso porque goto é uma palavra reservada não utilizada, só para garantir... Mas essa não é a parte interessante. O legal é que você pode incluir goto emparelhado com break, continue e blocos marcados: Saltando para frente No código de bytes: Saltando para trás No código de bytes: 8. Java tem aliases de tipo Em outras linguagens (como Ceilão), podemos definir tipo aliases muito fáceis: a classe People aqui é construída de tal forma que pode ser trocada por um Set Test.java:44: error: expected int goto = 1; ^ label: { // do stuff if (check) break label; // do more stuff } 2 iload_1 [check] 3 ifeq 6 // Jumping forward 6 .. label: do { // do stuff if (check) continue label; // do more stuff break label; } while(true); 2 iload_1 [check] 3 ifeq 9 6 goto 2 // Jumping backward 9 .. interface People => Set ; : Em Java, não podemos simplesmente definir um alias no nível superior. Mas podemos fazer isso de acordo com as necessidades de uma classe ou método. Vamos supor que não estamos satisfeitos com nomes como Integer, Long, etc. e queremos nomes mais curtos: I e L. Fácil: No exemplo acima, Integer é convertido em I para a visibilidade da classe Test, enquanto Long é convertido em L para as necessidades do método x(). Agora podemos chamar esse método assim: É claro que essa técnica não deve ser levada a sério. Nesse caso, Integer e Long são tipos finais, o que significa que I e L são conversões eficientes (quase, a conversão só ocorre em uma direção). Se decidíssemos usar tipos não finais (por exemplo Object), poderíamos nos dar bem com genéricos regulares. Brincamos um pouco e foi o suficiente. Vamos passar para algo realmente interessante. 9. Alguns tipos de relacionamentos são indecidíveis! Ok, agora isso vai ficar muito interessante, então pegue uma xícara de café concentrado e vejamos os dois tipos a seguir: People? p1 = null; Set ? p2 = p1; People? p3 = p2; class Test { void x(I i, L l) { System.out.println( i.intValue() + ", " + l.longValue() ); } } new Test().x(1, 2L);// A helper type. You could also just use List interface Type {} class C implements Type > {} class D

implements Type >>> {} Então, o que C e D significam? De certa forma, eles são recursivos, semelhantes à recursão em java.lang.Enum. Considere: Dadas as especificações acima, a implementação real de enum é apenas um açúcar sintático: com isso em mente, vamos retornar aos nossos dois tipos. O código a seguir será compilado? Uma questão difícil... e que não está realmente sendo resolvida? C é um subtipo de Type ? Tente compilar isso no seu Eclipse ou Idea e eles dirão o que pensam. Jogue no ralo... Alguns relacionamentos de tipo em Java são indecidíveis! 10. Interseção de tipos Java possui um recurso muito interessante chamado interseção de tipos. Você pode declarar um tipo (genérico) que é na verdade a interseção de dois tipos. Por exemplo: O parâmetro de tipo customizado T associado às instâncias da classe Test deve incluir as interfaces Serializable e Cloneable. Por exemplo, String não pode ser restrito, mas Date pode: Este recurso tem vários usos em Java8, onde você pode converter tipos. Como isso ajuda? Quase nada, mas se você quiser converter sua expressão lambda no tipo que você precisa, não há outra maneira. Digamos que você tenha uma limitação tão maluca no seu método: Você quer Runnable que ao mesmo tempo seja Serializable apenas se quiser executá-lo em outro lugar e enviar o resultado pela rede. Lambda e serialização adicionam um pouco de ironia. Você pode serializar sua expressão lambda se o tipo de destino e os argumentos forem serializáveis. Mas mesmo que isso seja verdade, eles não habilitam automaticamente a interface Serializable. Você mesmo deve trazê-los para esse tipo. Mas quando você converte apenas para Serializable: então o lambda não será mais Runnable, então converta-os para os dois tipos: E para concluir: public abstract class Enum > { ... } // This enum MyEnum {} // Is really just sugar for this class MyEnum extends Enum { ... } class Test { Type c = new C(); Type> d = new D (); } Step 0) C Step 1) Type > >? Step 0) D > Step 1) Type >>> > Step 2) D >> Step 3) List >> > Step 4) D > >> Step . . . (expand forever) class Test { } // Doesn't compile Test s = null; // Compiles Test d = null; void execute(T t) {} execute((Serializable) (() -> {}));execute((Runnable & Serializable) (() -> {}));

Java é tão poderoso quanto misterioso.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION