Java 8 trouxe mudanças nas interfaces, incluindo métodos estáticos e métodos padrão em interfaces. Antes do Java 8, só podíamos ter declarações de método nas interfaces. Mas a partir do Java 8, podemos ter métodos padrão e estáticos nas interfaces.
Interface Java 8
Projetar interfaces sempre foi um trabalho difícil porque se quisermos adicionar métodos adicionais nas interfaces, exigirá alterações em todas as classes que as implementam. À medida que a interface envelhece, o número de classes que a implementam pode crescer a ponto de não ser possível estender as interfaces. É por isso que, ao projetar uma aplicação, a maioria dos frameworks fornece uma classe de implementação base e então a estendemos e substituímos os métodos aplicáveis para nossa aplicação. Vamos analisar os métodos de interface padrão e os métodos de interface estática e a razão de sua introdução nas mudanças da interface Java 8.
Método Padrão de Interface Java
Para criar um método padrão em uma interface Java, precisamos usar a palavra-chave “default” com a assinatura do método. Por exemplo,
package com.journaldev.java8.defaultmethod;
public interface Interface1 {
void method1(String str);
default void log(String str){
System.out.println("I1 logging::"+str);
}
}
Observe que log(String str) é o método padrão na Interface1
. Agora, quando uma classe implementar Interface1, não é obrigatório fornecer implementação para os métodos padrão da interface. Essa funcionalidade nos ajudará a estender interfaces com métodos adicionais, tudo o que precisamos é fornecer uma implementação padrão. Digamos que temos outra interface com os seguintes métodos:
package com.journaldev.java8.defaultmethod;
public interface Interface2 {
void method2();
default void log(String str){
System.out.println("I2 logging::"+str);
}
}
Sabemos que o Java não nos permite estender várias classes porque resultaria no “Problema do Diamante”, onde o compilador não consegue decidir qual método da superclasse usar. Com os métodos padrão, o problema do diamante também surgiria para interfaces. Porque se uma classe estiver implementando tanto Interface1
quanto Interface2
e não implementar o método padrão comum, o compilador não conseguirá decidir qual escolher. Estender várias interfaces é uma parte integral do Java, você encontrará isso nas classes principais do Java, bem como na maioria das aplicações e estruturas empresariais. Então, para garantir que esse problema não ocorra em interfaces, é obrigatório fornecer uma implementação para os métodos padrão comuns das interfaces. Portanto, se uma classe estiver implementando ambas as interfaces acima, será necessário fornecer uma implementação para o método log()
, caso contrário, o compilador lançará um erro de tempo de compilação. Uma classe simples que está implementando tanto Interface1
quanto Interface2
será:
package com.journaldev.java8.defaultmethod;
public class MyClass implements Interface1, Interface2 {
@Override
public void method2() {
}
@Override
public void method1(String str) {
}
@Override
public void log(String str){
System.out.println("MyClass logging::"+str);
Interface1.print("abc");
}
}
Pontos importantes sobre métodos padrão de interface Java:
- As métodos padrão da interface Java nos ajudarão a estender interfaces sem o receio de quebrar as classes de implementação.
- Os métodos padrão da interface Java têm diminuído as diferenças entre interfaces e classes abstratas.
- Os métodos padrão da interface Java 8 nos ajudarão a evitar classes utilitárias, pois todos os métodos da classe Collections podem ser fornecidos diretamente nas próprias interfaces.
- Os métodos padrão da interface Java nos ajudarão a remover classes de implementação base, podendo fornecer implementações padrão e as classes de implementação podem escolher qual delas substituir.
- Uma das principais razões para introduzir métodos padrão nas interfaces é aprimorar a API de Coleções no Java 8 para suportar expressões lambda.
- Se alguma classe na hierarquia tiver um método com a mesma assinatura, os métodos padrão se tornam irrelevantes. Um método padrão não pode substituir um método de
java.lang.Object
. A razão é muito simples, é porque Object é a classe base para todas as classes Java. Portanto, mesmo que tenhamos métodos de classe Object definidos como métodos padrão nas interfaces, será inútil, pois o método de classe Object sempre será utilizado. É por isso que, para evitar confusão, não podemos ter métodos padrão que substituam os métodos de classe Object. - Os métodos padrão da interface Java também são chamados de Métodos Defensores ou Métodos de Extensão Virtual.
Método Estático da Interface Java
interface Java método estático é semelhante ao método padrão, exceto que não podemos sobrescrevê-los nas classes de implementação. Essa característica ajuda a evitar resultados indesejados em caso de implementações inadequadas nas classes de implementação. Vamos analisar isso com um exemplo simples.
package com.journaldev.java8.staticmethod;
public interface MyData {
default void print(String str) {
if (!isNull(str))
System.out.println("MyData Print::" + str);
}
static boolean isNull(String str) {
System.out.println("Interface Null Check");
return str == null ? true : "".equals(str) ? true : false;
}
}
Agora, vejamos uma classe de implementação que possui o método isNull() com uma implementação inadequada.
package com.journaldev.java8.staticmethod;
public class MyDataImpl implements MyData {
public boolean isNull(String str) {
System.out.println("Impl Null Check");
return str == null ? true : false;
}
public static void main(String args[]){
MyDataImpl obj = new MyDataImpl();
obj.print("");
obj.isNull("abc");
}
}
Observe que isNull(String str)
é um método simples da classe, não está sobrescrevendo o método da interface. Por exemplo, se adicionarmos @Override anotação ao método isNull(), resultará em erro de compilação. Agora, ao executarmos a aplicação, obtemos a seguinte saída.
Interface Null Check
Impl Null Check
Se tornarmos o método da interface de estático para padrão, obteremos a seguinte saída.
Impl Null Check
MyData Print::
Impl Null Check
O método estático da interface Java é visível apenas para os métodos da interface. Se removermos o método isNull() da classe MyDataImpl
, não poderemos usá-lo para o objeto MyDataImpl
. No entanto, como outros métodos estáticos, podemos usar os métodos estáticos da interface usando o nome da classe. Por exemplo, uma declaração válida seria:
boolean result = MyData.isNull("abc");
Pontos importantes sobre o método estático da interface Java:
- O método estático da interface Java faz parte da interface, não podemos usá-lo para objetos da classe de implementação.
- Os métodos estáticos da interface Java são úteis para fornecer métodos de utilidade, como verificação nula, ordenação de coleções, etc.
- A interface estática do Java ajuda-nos a fornecer segurança ao não permitir que as classes de implementação as substituam.
- Não podemos definir métodos estáticos de interface para métodos da classe Object; obteremos um erro do compilador como “Este método estático não pode ocultar o método de instância de Object”. Isso ocorre porque não é permitido em Java, uma vez que Object é a classe base para todas as classes, e não podemos ter um método estático de nível de classe e outro método de instância com a mesma assinatura.
- Podemos usar métodos estáticos de interface Java para remover classes de utilitários, como Collections, e mover todos os seus métodos estáticos para a interface correspondente, o que facilitaria encontrar e usar.
Interfaces Funcionais em Java
Antes de concluir a postagem, gostaria de fornecer uma breve introdução às Interfaces Funcionais. Uma interface com exatamente um método abstrato é conhecida como Interface Funcional. Uma nova anotação @FunctionalInterface foi introduzida para marcar uma interface como Interface Funcional. A anotação @FunctionalInterface é uma facilidade para evitar a adição acidental de métodos abstratos nas interfaces funcionais. É opcional, mas é uma boa prática usá-la. As interfaces funcionais são uma característica há muito aguardada e muito procurada no Java 8, pois nos permite usar expressões lambda para instanciá-las. Um novo pacote java.util.function
com uma variedade de interfaces funcionais foi adicionado para fornecer tipos-alvo para expressões lambda e referências a métodos. Vamos explorar interfaces funcionais e expressões lambda em postagens futuras.