JavaRush /Blogue Java /Random-PT /Recursos do Java 8 – O guia definitivo (Parte 1)
0xFF
Nível 9
Донецк

Recursos do Java 8 – O guia definitivo (Parte 1)

Publicado no grupo Random-PT
A primeira parte da tradução do artigo Java 8 Features – The ULTIMATE Guide . A segunda parte está aqui (o link pode mudar). Recursos do Java 8 – O guia definitivo (Parte 1) - 1 Nota do Editor : Este artigo foi publicado enquanto o Java 8 estava disponível ao público e todas as indicações são de que esta é realmente uma versão principal. Aqui, fornecemos guias Java Code Geeks em abundância, como Jogando com Java 8 - Lambdas e simultaneidade , Guia de API de data e hora do Java 8: LocalDateTime e Classe abstrata vs. Também oferecemos links para 15 tutoriais Java 8 de leitura obrigatória de outras fontes . É claro que analisamos algumas das desvantagens, como o lado negro do Java 8 . Então, é hora de reunir todos os principais recursos do Java 8 em um só lugar para sua conveniência. Aproveitar!

1. Introdução

Sem dúvida, o lançamento do Java 8 é o maior acontecimento desde o Java 5 (lançado há muito tempo, em 2004). Trouxe muitas novidades para Java tanto na linguagem, compilador, bibliotecas, ferramentas e JVM (Java Virtual Machine). Neste tutorial, daremos uma olhada nessas mudanças e demonstraremos diferentes casos de uso com exemplos da vida real. O guia consiste em várias partes, cada uma abordando um aspecto específico da plataforma:
  • Linguagem
  • Compilador
  • Bibliotecas
  • Ferramentas
  • Ambiente de tempo de execução (JVM)

2. Novos recursos em Java 8

De qualquer forma, o Java 8 é uma versão importante. Podemos dizer que demorou muito por causa da implementação das funcionalidades que todo desenvolvedor Java procurava. Nesta seção, cobriremos a maioria deles.

2.1. Lambdas e interfaces funcionais

Lambdas (também conhecidos como métodos privados ou anônimos) são a maior e mais esperada mudança de linguagem em toda a versão Java 8. Eles nos permitem especificar funcionalidade como um argumento de método (declarando uma função em torno dele) ou especificar código como dados : conceitos com os quais todo desenvolvedor de funcionalidades está familiarizado. programação _ Muitas linguagens na plataforma JVM (Groovy, Scala , ...) tiveram lambdas desde o primeiro dia, mas os desenvolvedores Java não tiveram escolha a não ser representar lambdas por meio de classes anônimas. O debate sobre o design do lambda exigiu muito tempo e esforço do público. Mas eventualmente foram encontrados compromissos, o que levou ao surgimento de novos designs concisos. Em sua forma mais simples, um lambda pode ser representado como uma lista de parâmetros separados por vírgula, um símbolo -> e um corpo. Por exemplo:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) )
Observe que o tipo do argumento e é determinado pelo compilador. Além disso, você pode especificar explicitamente o tipo de um parâmetro colocando o parâmetro entre parênteses. Por exemplo:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
Caso o corpo lambda seja mais complexo, ele pode ser colocado entre chaves, semelhante a uma definição de função regular em Java. Por exemplo:
Arrays.asList( "a", "b", "d" ).forEach( e -< {
    System.out.print( e );
    System.out.print( e );
} );
Um lambda pode se referir a membros de classe e variáveis ​​locais (tornando implicitamente a chamada efetiva, independentemente de finalo campo ser acessado ou não). Por exemplo, estes 2 trechos são equivalentes:
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
E:
final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
Lambdas podem retornar um valor. O tipo de retorno será determinado pelo compilador. Uma declaração returnnão é necessária se o corpo do lambda consistir em uma linha. Os dois trechos de código abaixo são equivalentes:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
E:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );
Os desenvolvedores da linguagem pensaram por muito tempo em como tornar as funções existentes compatíveis com lambda. Como resultado, surgiu o conceito de interface funcional. Uma interface funcional é uma interface com apenas um método. Como resultado, pode ser convertido implicitamente em uma expressão lambda. java.lang.Runnablee java.util.concurrent.Callabledois ótimos exemplos de interfaces funcionais. Na prática, as interfaces funcionais são muito frágeis: se alguém adicionar pelo menos um outro método à definição da interface, ele não será mais funcional e o processo de compilação não será concluído. Para evitar essa fragilidade e definir explicitamente a intenção de uma interface como funcional, uma anotação especial foi adicionada no Java 8 @FunctionalInterface(todas as interfaces existentes na biblioteca Java receberam a anotação @FunctionalInterface). Vejamos esta definição simples de uma interface funcional:
@FunctionalInterface
public interface Functional {
    void method();
}
Há uma coisa a ter em mente: métodos padrão e métodos estáticos não violam o princípio de uma interface funcional e podem ser declarados:
@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();

    default void defaultMethod() {
    }
}
Lambdas são o recurso mais popular do Java 8. Eles têm todo o potencial para atrair mais desenvolvedores para esta plataforma maravilhosa e fornecer suporte inteligente para recursos em Java puro. Para informações mais detalhadas, consulte a documentação oficial .

2.2. Interfaces padrão e métodos estáticos

Java 8 expandiu a definição de interfaces com dois novos conceitos: método padrão e método estático. Os métodos padrão tornam as interfaces um tanto semelhantes às características, mas servem a um propósito ligeiramente diferente. Eles permitem que você adicione novos métodos a interfaces existentes sem quebrar a compatibilidade com versões anteriores dessas interfaces. A diferença entre métodos padrão e métodos abstratos é que os métodos abstratos devem ser implementados, enquanto os métodos padrão não. Em vez disso, cada interface deve fornecer uma implementação padrão, e todos os descendentes a receberão por padrão (com a capacidade de substituir essa implementação padrão, se necessário). Vejamos o exemplo abaixo.
private interface Defaulable {
    // Интерфейсы теперь разрешают методы по умолчанию,
    // клиент может реализовывать  (переопределять)
    // or не реализовывать его
    default String notRequired() {
        return "Default implementation";
    }
}

private static class DefaultableImpl implements Defaulable {
}

private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}
A interface Defaulabledeclara um método padrão notRequired()usando uma palavra-chave defaultcomo parte da definição do método. Uma das classes, DefaultableImpl, implementa esta interface deixando o método padrão como está. Outra classe, OverridableImpl, substitui a implementação padrão e fornece a sua própria. Outro recurso interessante introduzido no Java 8 é que as interfaces podem declarar (e oferecer implementações de) métodos estáticos. Aqui está um exemplo:
private interface DefaulableFactory {
    // Interfaces now allow static methods
    static Defaulable create( Supplier<Defaulable> supplier ) {
        return supplier.get();
    }
}
Um pequeno trecho de código combina o método padrão e o método estático do exemplo acima:
public static void main( String[] args ) {
    Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
    System.out.println( defaulable.notRequired() );

    defaulable = DefaulableFactory.create( OverridableImpl::new );
    System.out.println( defaulable.notRequired() );
}
A saída do console deste programa é semelhante a esta:
Default implementation
Overridden implementation
A implementação do método padrão na JVM é muito eficiente e a invocação do método é suportada por instruções de bytecode. Os métodos padrão permitiram que as interfaces Java existentes evoluíssem sem interromper o processo de compilação. Bons exemplos são os vários métodos adicionados à interface java.util.Collection: stream(), parallelStream(), forEach(), removeIf(), ... Embora sejam poderosos, os métodos padrão devem ser usados ​​com cautela: antes de declarar um método padrão, você deve pensar duas vezes se isso é realmente necessário, pois isso pode levar a ambigüidades e erros de compilação em hierarquias complexas. Para informações mais detalhadas, consulte a documentação .

2.3. Métodos de referência

Os métodos de referência implementam sintaxe útil para fazer referência a métodos ou construtores existentes de classes ou objetos Java (instâncias). Juntamente com as expressões lambda, os métodos de referência tornam as construções da linguagem compactas e concisas, tornando-as baseadas em modelos. Abaixo está uma classe Carcomo exemplo de diferentes definições de métodos, vamos destacar os quatro tipos de métodos de referência suportados:
public static class Car {
    public static Car create( final Supplier<Car> supplier ) {
        return supplier.get();
    }

    public static void collide( final Car car ) {
        System.out.println( "Collided " + car.toString() );
    }

    public void follow( final Car another ) {
        System.out.println( "Following the " + another.toString() );
    }

    public void repair() {
        System.out.println( "Repaired " + this.toString() );
    }
}
O primeiro método de referência é uma referência a um construtor com sintaxe Class::newou uma alternativa para genéricos Class< T >::new. Observe que o construtor não possui argumentos.
final Car car = Car.create( Car::new );
final List<Car> cars = Arrays.asList( car );
A segunda opção é uma referência a um método estático com a sintaxe Class::static_method. Observe que o método usa exatamente um parâmetro do tipo Car.
cars.forEach( Car::collide );
O terceiro tipo é uma referência a um método de uma instância de um objeto arbitrário de um determinado tipo com a sintaxe Class::method. Observe que nenhum argumento é aceito pelo método.
cars.forEach( Car::repair );
E o último e quarto tipo é uma referência a um método de uma instância de uma classe específica com a sintaxe instance::method. Observe que o método aceita apenas um parâmetro do tipo Car.
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
A execução de todos esses exemplos como programas Java produz a seguinte saída de console (a referência de classe Carpode variar):
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Para obter informações mais detalhadas e detalhes dos métodos de referência, consulte a documentação oficial .

2.4. Anotações duplicadas

Desde que o Java 5 introduziu o suporte para anotações , esse recurso se tornou muito popular e amplamente utilizado. Porém, uma das limitações do uso de anotações foi o fato de que a mesma anotação não pode ser declarada mais de uma vez no mesmo local. Java 8 quebra essa regra e introduz anotações duplicadas. Isso permite que as mesmas anotações sejam repetidas várias vezes no local onde foram declaradas. Anotações duplicadas devem ser anotadas usando annotation @Repeatable. Na verdade, isso não é tanto uma mudança na linguagem, mas sim um truque do compilador, enquanto a técnica permanece a mesma. Vejamos um exemplo simples:
package com.javacodegeeks.java8.repeatable.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class RepeatingAnnotations {
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface Filters {
        Filter[] value();
    }

    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    @Repeatable( Filters.class )
    public @interface Filter {
        String value();
    };

    @Filter( "filter1" )
    @Filter( "filter2" )
    public interface Filterable {
    }

    public static void main(String[] args) {
        for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
            System.out.println( filter.value() );
        }
    }
}
Como podemos ver, a classe Filteré anotada com @Repeatable( Filters. class). Filtersé simplesmente o dono das anotações Filter, mas o compilador Java tenta esconder sua presença dos desenvolvedores. Assim, a interface Filterablecontém anotações Filterque são declaradas duas vezes (sem mencionar Filters). A API Reflection também fornece um novo método getAnnotationsByType()para retornar anotações duplicadas de algum tipo (lembre-se de que Filterable. class.getAnnotation( Filters. class) retornará uma instância injetada pelo compilador Filters). A saída do programa ficará assim:
filter1
filter2
Para informações mais detalhadas, consulte a documentação oficial .

2.5. Inferência de tipo aprimorada

O compilador Java 8 recebeu muitas melhorias na inferência de tipos. Em muitos casos, parâmetros de tipo explícitos podem ser definidos pelo compilador, tornando o código mais limpo. Vejamos um exemplo:
package com.javacodegeeks.java8.type.inference;

public class Value<T> {
    public static<T> T defaultValue() {
        return null;
    }

    public T getOrDefault( T value, T defaultValue ) {
        return ( value != null ) ? value : defaultValue;
    }
}
E aqui está o uso com type Value<String>:
package com.javacodegeeks.java8.type.inference;

public class TypeInference {
    public static void main(String[] args) {
        final Value<String> value = new Value<>();
        value.getOrDefault( "22", Value.defaultValue() );
    }
}
O parâmetro de tipo Value.defaultValue()é determinado automaticamente e não precisa ser fornecido explicitamente. No Java 7, o mesmo exemplo não será compilado e deve ser reescrito como <NOBR>Value.<String>defaultValue()</NOBR>.

2.6. Suporte de anotação expandido

Java 8 expande o contexto onde as anotações podem ser usadas. Hoje em dia, quase tudo pode ter uma anotação: variáveis ​​locais, tipos genéricos, superclasses e interfaces implementadas, até mesmo exceções de métodos. Alguns exemplos são apresentados abaixo:
package com.javacodegeeks.java8.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;

public class Annotations {
    @Retention( RetentionPolicy.RUNTIME )
    @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
    public @interface NonEmpty {
    }

    public static class Holder<@NonEmpty T> extends @NonEmpty Object {
        public void method() throws @NonEmpty Exception {
        }
    }

    @SuppressWarnings( "unused" )
    public static void main(String[] args) {
        final Holder<String> holder = new @NonEmpty Holder<String>();
        @NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>();
    }
}
ElementType.TYPE_USEe ElementType.TYPE_PARAMETERdois novos tipos de elementos para descrever o contexto de anotação relevante. Annotation Processing APItambém passou por pequenas alterações para reconhecer novos tipos de anotação em Java.

3. Novos recursos no compilador Java

3.1. Nomes de parâmetros

Ao longo do tempo, os desenvolvedores Java inventaram uma variedade de maneiras de armazenar nomes de parâmetros de métodos em bytecode Java para torná-los disponíveis em tempo de execução (por exemplo, a biblioteca Paranamer ). Finalmente, o Java 8 cria essa função difícil na linguagem (usando a API e o método Reflection Parameter.getName()) e no bytecode (usando o novo argumento do compilador javac:)) –parameters.
package com.javacodegeeks.java8.parameter.names;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class ParameterNames {
    public static void main(String[] args) throws Exception {
        Method method = ParameterNames.class.getMethod( "main", String[].class );
        for( final Parameter parameter: method.getParameters() ) {
            System.out.println( "Parameter: " + parameter.getName() );
        }
    }
}
Se você compilar esta classe sem usar um argumento –parameterse depois executar o programa, verá algo assim:
Parameter: arg0
Com o parâmetro –parameterspassado ao compilador, a saída do programa será diferente (o nome real do parâmetro será mostrado):
Parameter: args
Para usuários avançados do Maven, o argumento –parameters pode ser adicionado à compilação usando a seção maven-compiler-plugin:
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
    <compilerArgument>-parameters</compilerArgument>
    <source>1.8</source>
    <target>1.8</target>
    </configuration>
</plugin>
Para verificar a disponibilidade dos nomes dos parâmetros, existe um isNamePresent()método conveniente fornecido pela classe Parameter.

4. Novas ferramentas Java

Java 8 vem com um novo conjunto de ferramentas de linha de comando. Nesta seção veremos os mais interessantes deles.

4.1. Motor Nashorn: jjs

jjs é um mecanismo Nashorn independente baseado em linha de comando. Ele pega uma lista de arquivos de código-fonte JavaScript e os executa. Por exemplo, vamos criar um arquivo func.js com o seguinte conteúdo:
function f() {
     return 1;
};

print( f() + 1 );
Para executar este arquivo vamos passá-lo como argumento para jjs :
jjs func.js
A saída do console será assim:
2
Para mais detalhes consulte a documentação .

4.2. Analisador de Dependência de Classe: jdeps

jdeps é uma ótima ferramenta de linha de comando. Ele mostra dependências de nível de pacote ou classe para classes Java. Ele aceita um arquivo .class , pasta ou arquivo JAR como entrada. Por padrão , o jdeps gera dependências para a saída padrão (console). Como exemplo, vejamos o relatório de dependência da popular biblioteca Spring Framework . Para manter o exemplo curto, vamos examinar apenas as dependências do arquivo JAR org.springframework.core-3.0.5.RELEASE.jar.
jdeps org.springframework.core-3.0.5.RELEASE.jar
Este comando produz bastante, então analisaremos apenas parte da saída. As dependências são agrupadas por pacotes. Se não houver dependências, não encontrado será exibido .
org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
   org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.io
      -> java.lang
      -> java.lang.annotation
      -> java.lang.ref
      -> java.lang.reflect
      -> java.util
      -> java.util.concurrent
      -> org.apache.commons.logging                         not found
      -> org.springframework.asm                            not found
      -> org.springframework.asm.commons                    not found
   org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.lang
      -> java.lang.annotation
      -> java.lang.reflect
      -> java.util
Para informações mais detalhadas, consulte a documentação oficial .
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION