Como muitos sabem, a nova versão JAVA 1.8 foi lançada em Outubro de 2021, com melhorias, novas funcionalidades e correções de bugs recorrentes da antiga versão.
A seguir, veja as novidades e exemplos práticos e objetivos. Boa leitura!
Principais novidades introduzidas nos recursos do JAVA 1.8
A seguir, exemplificaremos as principais novidades nos recursos do JAVA 1.8. São elas:
Default Methods em interfaces;
Lambda e Functional interfaces;
Method References;
Stream;
Nova API de Datas;
Default Methods em interfaces
Abaixo, criaremos uma List do tipo String contendo títulos de alguns jogos marcantes que serão utilizados nos exemplos do conteúdo.
List<String> jogos = new ArrayList<>();jogos.add(“Top Gear”);jogos.add(“The Legend of Zelda: Ocarina of Time”);jogos.add(“Shadow of the Colossus”);
Antigamente, o comum para fazer uma ordenação de objetos em uma Listlist era utilizar a classe Ccollections e o método estáticgo sort.
System.out.println(jogos);// [Top Gear, The Legend of Zelda: Ocarina of Time, Shadow of the Colossus]Collections.sort(jogos);System.out.println(jogos);// [Shadow of the Colossus, The Legend of Zelda: Ocarina of Time, Top Gear]
É importante lembrar que o código acima compilou sem a necessidade de nenhuma alteração. Isso ocorre porque a classe String implementa uma interface chamada Comparable, que obriga a declaração do método compareTo.
Dentro dessa técnica, a configuração é feita na ordem lexicográfica.
E se quisermos ordenar as palavras em uma ordem diferente?
Se quisermos fazer a ordenação por tamanho das palavras, por exemplo, precisamos criar um comparador para informar ao método sort.
É necessário criar uma classe. O nome dela não tem muita importância, mas é obrigatório que ela tenha implementado a interface Comparator e uma declaração para o método compare.
class ComparadorDeStringPorTamanho implements Comparator<String> { public int compare(String s1, String s2) { if(s1.length() < s2.length()) return –1; if(s1.length() > s2.length()) return 1; return 0; }}
Agora, precisamos passar para o método sort esse comparador, pois ele possui uma reescrita que recebe uma List e um Comparator.
Comparator<String> comparador = new ComparadorDeStringPorTamanho();System.out.println(jogos);// [Top Gear, The Legend of Zelda: Ocarina of Time, Shadow of the Colossus]Collections.sort(jogos, comparador);System.out.println(jogos);// [Top Gear, Shadow of the Colossus, The Legend of Zelda: Ocarina of Time]
Com o novo recurso de Default Methods, agora podemos criar métodos com corpo dentro de uma interface. A partir disso, a interface List passa a ter uma implementação do método sort.
// Método sort dentro da interface Listdefault void sort(Comparator<? super E> c) { Object[] a = this.toArray(); Arrays.sort(a, (Comparator) c); ListIterator<E> i = this.listIterator(); for (Object e : a) { i.next(); i.set((E) e);}
Perceba que, para implementar o sort pela List, obrigatoriamente devemos informar um Comparator.
Comparator<String> comparador = new ComparadorDeStringPorTamanho();jogos.sort(comparador);System.out.println(jogos);// [Top Gear, Shadow of the Colossus, The Legend of Zelda: Ocarina of Time]
Lambda e Functional interfaces
Nesse tópico, vamos continuar no mesmo contexto do último exemplo.
public class App { public static void main(String[] args) throws Exception { Comparator<String> comparador = new ComparadorDeStringPorTamanho(); List<String> jogos = new ArrayList<>(); jogos.add(“Top Gear”); jogos.add(“The Legend of Zelda: Ocarina of Time”); jogos.add(“Shadow of the Colossus”); jogos.sort(comparador); System.out.println(jogos); // [Top Gear, Shadow of the Colossus, The Legend of Zelda: Ocarina of Time] }}class ComparadorDeStringPorTamanho implements Comparator<String> { public int compare(String s1, String s2) { if(s1.length() < s2.length()) return –1; if(s1.length() > s2.length()) return 1; return 0; }}
É possível deixar o código lambda um pouco melhor?
public class App { public static void main(String[] args) throws Exception { List<String> jogos = new ArrayList<>(); jogos.add(“Top Gear”); jogos.add(“The Legend of Zelda: Ocarina of Time”); jogos.add(“Shadow of the Colossus”); jogos.sort(new Comparator<String>(){ @Override public int compare(String s1, String s2) { if(s1.length() < s2.length()) return –1; if(s1.length() > s2.length()) return 1; return 0; } }); System.out.println(jogos); // [Top Gear, Shadow of the Colossus, The Legend of Zelda: Ocarina of Time] }}
Pelo fato da abordagem da classe ser simples, podemos fazer com que o compilador do java crie uma Anonymous Classes.
Mas ainda podemos melhorar mais um pouco. Perceba que o método compare possui algumas condicionais testando os tamanhos dos títulos. Essencialmente, estamos testando números com números e no Integer temos um método compare que faz exatamente isso.
jogos.sort(new Comparator<String>(){ @Override public int compare(String s1, String s2) { return Integer.compare(s1.length(), s2.length()); }}
Atenção, o tipo Double também implementa esse método. Mesmo nosso último código ficando um pouco mais compacto, ainda está um pouco verboso.
Nesse contexto, foi adicionado um recurso chamado lambda, mas o seu funcionamento só é possível através de uma Functional interfaces. Essas interfaces possuem 1 único método abstrato, mas, além desse método, elas podem ter outros métodos, desde que sejam default ou static.
Essa estrutura é fundamental, pois assim o compilador sabe exatamente que o corpo da expressão lambda que escrevemos é a implementação de seu único método abstrato. Então, como o Comparator é uma Functional interfaces, podemos utilizar um lambda.
jogos.sort((String s1, String s2) -> { return Integer.compare(s1.length(), s2.length());});
O compilador do java nos permite deixar o código um pouco mais curto.
jogos.sort((jogo1, jogo2) -> Integer.compare(jogo1.length(), jogo2.length()));
Method References
Nesse tópico, continuaremos no mesmo contexto do último exemplo.
public class App { public static void main(String[] args) throws Exception { List<String> jogos = new ArrayList<>(); jogos.add(“Top Gear”); jogos.add(“The Legend of Zelda: Ocarina of Time”); jogos.add(“Shadow of the Colossus”); jogos.sort((jogo1, jogo2) -> Integer.compare(jogo1.length(), jogo2.length())); System.out.println(jogos); // [Top Gear, Shadow of the Colossus, The Legend of Zelda: Ocarina of Time] }}
A interface Comparator possui um novo método (comparing), que deve receber uma interface Function, que por sua vez é uma Functional interfaces.
jogos.sort(Comparator.comparing(jogo -> jogo.length()));
É como se tivéssemos escrito assim.
//Estamos falando abaixo: Dado uma String, vamos devolver um Integer que será o return do lambda jogo -> jogo.length()Function<String, Integer> function = jogo -> jogo.length();jogos.sort(Comparator.comparing(function);
Nesse caso, como temos uma invocação simples do método length do String, é possível utilizarmos Method References.
jogos.sort(Comparator.comparing(String::length));
Um destaque, a utilização do Method References precisa de atenção., Eessa prática só funciona para invocações simples. No próximo tópico, veremos um exemplo em que ele não pode ser utilizado.
Stream
Nesse tópico, vamos continuar no mesmo contexto do último exemplo. Mas, agora, precisamos de uma classe Jogo.
public class Jogo { private String titulo; private int lancamento; public Jogo(String titulo, int lancamento) { this.titulo = titulo; this.lancamento = lancamento; } public String getTitulo() { return titulo; } public void setTitulo(String titulo) { this.titulo = titulo; } public int getLancamento() { return lancamento; } public void setLancamento(int lancamento) { this.lancamento = lancamento; } }
public class App { public static void main(String[] args) throws Exception { List<Jogo> listaDeJogos = new ArrayList<>(); listaDeJogos.add(new Jogo(“Top Gear”, 1992)); listaDeJogos.add(new Jogo(“The Legend of Zelda: Ocarina of Time”, 1998)); listaDeJogos.add(new Jogo(“Shadow of the Colossus”, 2005)); }}
Stream é uma nova interface que possibilita a utilização de diversos métodos bem interessantes. Vamos filtrar nossa lista para que apenas os jogos com lançamentos inferiores aos anos 2000 sejam exibidos no terminal.
Uma maneira simples de fazer isso, antigamente, seria criandor um foreach com uma condicional testando cada valor.
for (Jogo jogo : listaDeJogos) { if (jogo.getLancamento() < 2000) { System.out.println(jogo.getTitulo()); }}
Mas existe uma maneira melhor: Invocar o filter do Stream.
listaDeJogos.stream() .filter(jogo -> jogo.getLancamento() < 2000);
Nesse caso, não conseguimos utilizar o Method References, pois não estamos usando uma simples invocação do filter.
Esse trecho de código não soluciona nosso problema, ele está testando item por item com a nossa condicional, mas uma modificação em um stream não modifica a coleção/objeto que o gerou.
Para funcionar, podemos utilizar o forEach, que o próprio Stream implementa.
listaDeJogos.stream() .filter(jogo -> jogo.getLancamento() < 2000) .forEach(jogo -> System.out.println(jogo.getTitulo())); // Top Gear // The Legend of Zelda: Ocarina of Time
Como não temos o método toString na classe Jogo, não podemos utilizar o Method References, pois precisamos informar qual parâmetro o System.out deve imprimir no terminal.
Nova API de Datas
Agora temos várias classes com diversos métodos que facilitam (e muito) o trabalho relacionado com datas no java.
A seguir, veja alguns exemplos de LocalDate:
LocalDate date = LocalDate.now();System.out.println(date);// 2021-08-17date = LocalDate.of(2021, Month.DECEMBER, 15);System.out.println(date);// 2021-12-15
Já para usar o DateTimeFormatter na formatação de um LocalDate, você pode fazer da seguinte forma:
DateTimeFormatter formatador = DateTimeFormatter.ofPattern(“dd/MM/yyyy”);String novaData = date.format(formatador);System.out.println(novaData);// 15/12/2021
Exemplo de como extrair o dia nominal da semana utilizando DayOfWeek
DayOfWeek semana = DayOfWeek.from(date);System.out.println(semana);// WEDNESDAY
Exemplos de LocalDateTime:
LocalDateTime time = LocalDateTime.now();System.out.println(time);// 2021-08-17T11:29:56.861110time = LocalDateTime.of(2021, Month.DECEMBER, 15, 21, 30, 0);System.out.println(time);// 2021-12-15T21:30
Exemplo de como formatar um LocalDateTime utilizando DateTimeFormatter:
DateTimeFormatter formatadorTime = DateTimeFormatter.ofPattern(“dd/MM/yyyy hh:mm:ss”);String novaTime = time.format(formatadorTime);System.out.println(novaTime);// 15/12/2021 09:30:00
Exemplo de como comparar LocalDate utilizando Period:
LocalDate date1 = LocalDate.of(2021, Month.DECEMBER, 15);LocalDate date2 = LocalDate.of(2051, Month.JANUARY, 22);Period periodo = Period.between(date1, date2);System.out.println(periodo);// P29Y1M7DSystem.out.printf( “Falta %d anos, %d mês e %d dias.”, periodo.getYears(), periodo.getMonths(), periodo.getDays());// Falta 29 anos, 1 mês e 7 dias.
Conclusão sobre o JAVA 1.8
O que foi exemplificado nos tópicos é apenas uma visão muito superficial de todo o poder dessas novas mudanças. Existem muitos outros métodos que trazem soluções boas para diversos problemas que, eventualmente, surgem no desenvolvimento de nossas aplicações.
O conselho que fica é: sempre tenha em mente que, se estamos quebrando a cabeça para escrever algum método, é provável que já exista um método pronto para fazer isso por nós.
Vale sempre verificar as documentações e as soluções existentes.
Saiba mais em nosso Blog!
Materiais interessante:
Mergulhe em tecnologia — ALURA
Documentação JAVA