Uso do método padrão Java

13

Por décadas tem sido o caso que interfaces eram única única (apenas) para especificar assinaturas de método. Disseram-nos que este era o "caminho certo para fazer as coisas ™".

Então o Java 8 saiu e disse:

Bem, agora você pode definir métodos padrão. Tenho que correr, tchau.

Estou curioso para saber como isso está sendo digerido por desenvolvedores Java experientes e por aqueles que começaram recentemente (nos últimos anos) a desenvolvê-lo. Também estou pensando em como isso se encaixa na ortodoxia e prática do Java.

Estou criando um código experimental e, enquanto fazia refatoração, acabei com uma interface que simplesmente estende uma interface padrão (Iterable) e adiciona dois métodos padrão. E vou ser sincero, me sinto muito bem com isso.

Eu sei que isso é um pouco aberto, mas agora que já houve algum tempo para o Java 8 ser usado em projetos reais, existe uma ortodoxia em torno do uso de métodos padrão ainda? O que eu vejo principalmente quando são discutidos é sobre como adicionar novos métodos a uma interface sem prejudicar os consumidores existentes. Mas que tal usar isso desde o início, como no exemplo que eu dei acima. Alguém já teve problemas com o fornecimento de implementações em suas interfaces?

JimmyJames
fonte
Eu também estaria interessado nessa perspectiva. Estou voltando para Java depois de 6 anos no mundo .Net. Parece-me que essa pode ser a resposta do Java para métodos de extensão C #, com um pouco de influência dos métodos do módulo Ruby. Eu não brinquei com isso, então não tenho certeza.
precisa saber é o seguinte
1
Eu me sinto como a razão pela qual eles adicionaram métodos padrão é em grande parte para que eles pudessem estender as relações da coleção sem ter que fazer as interfaces completamente diferentes
Justin
1
@ Justin: veja o java.util.function.Functionuso de métodos padrão em uma interface totalmente nova.
Jörg W Mittag
@ Justin Meu palpite é que esse foi o principal driver. Eu realmente deveria começar a prestar atenção ao processo novamente, pois eles começaram a fazer mudanças.
precisa saber é o seguinte

Respostas:

12

Um ótimo caso de uso é o que eu chamo de interfaces "alavanca": interfaces que possuem apenas um pequeno número de métodos abstratos (idealmente 1), mas oferecem muita "alavancagem", pois fornecem muitas funcionalidades: Você precisa implementar 1 método em sua classe, mas obter muitos outros métodos "de graça". Pense de uma interface de recolha, por exemplo, com um sumário único foreachmétodo e defaultmétodos como map, fold, reduce, filter, partition, groupBy, sort, sortBy, etc.

Aqui estão alguns exemplos. Vamos começar com java.util.function.Function<T, R>. Ele tem um único método abstrato R apply<T>. E possui dois métodos padrão que permitem compor a função com outra função de duas maneiras diferentes, antes ou depois. Ambos os métodos de composição são implementados usando apenasapply :

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    return (V v) -> apply(before.apply(v));
}

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    return (T t) -> after.apply(apply(t));
}

Você também pode criar uma interface para objetos comparáveis, algo como isto:

interface MyComparable<T extends MyComparable<T>> {
  int compareTo(T other);

  default boolean lessThanOrEqual(T other) {
    return compareTo(other) <= 0;
  }

  default boolean lessThan(T other) {
    return compareTo(other) < 0;
  }

  default boolean greaterThanOrEqual(T other) {
    return compareTo(other) >= 0;
  }

  default boolean greaterThan(T other) {
    return compareTo(other) > 0;
  }

  default boolean isBetween(T min, T max) {
    return greaterThanOrEqual(min) && lessThanOrEqual(max);
  }

  default T clamp(T min, T max) {
    if (lessThan(   min)) return min;
    if (greaterThan(max)) return max;
                          return (T)this;
  }
}

class CaseInsensitiveString implements MyComparable<CaseInsensitiveString> {
  CaseInsensitiveString(String s) { this.s = s; }
  private String s;

  @Override public int compareTo(CaseInsensitiveString other) {
    return s.toLowerCase().compareTo(other.s.toLowerCase());
  }
}

Ou uma estrutura de coleções extremamente simplificada, em que todas as operações de coleções retornam Collection, independentemente do tipo original:

interface MyCollection<T> {
  void forEach(java.util.function.Consumer<? super T> f);

  default <R> java.util.Collection<R> map(java.util.function.Function<? super T, ? extends R> f) {
    java.util.Collection<R> l = new java.util.ArrayList();
    forEach(el -> l.add(f.apply(el)));
    return l;
  }
}

class MyArray<T> implements MyCollection<T> {
  private T[] array;

  MyArray(T[] array) { this.array = array; }

  @Override public void forEach(java.util.function.Consumer<? super T> f) {
    for (T el : array) f.accept(el);
  }

  @Override public String toString() {
    StringBuilder sb = new StringBuilder("(");
    map(el -> el.toString()).forEach(s -> { sb.append(s); sb.append(", "); } );
    sb.replace(sb.length() - 2, sb.length(), ")");
    return sb.toString();
  }

  public static void main(String... args) {
    MyArray<Integer> array = new MyArray<>(new Integer[] {1, 2, 3, 4});
    System.out.println(array);
    // (1, 2, 3, 4)
  }
}

Isso se torna muito interessante em combinação com lambdas, porque essa interface "alavanca" pode ser implementada por uma lambda (é uma interface SAM).

É o mesmo caso de uso para o qual foram adicionados os métodos de extensão em C in, mas os métodos padrão têm uma vantagem distinta: são métodos de instância "adequados", o que significa que eles têm acesso a detalhes de implementação privados da interface (os privatemétodos de interface estão chegando no Java 9), enquanto os métodos de extensão são apenas açúcar sintático para métodos estáticos.

Caso o Java receba a injeção de interface, também permitiria o patch de macaco modular, com escopo seguro e de tipo seguro. Isso seria muito interessante para implementadores de linguagem na JVM: no momento, por exemplo, o JRuby herda ou agrupa classes Java para fornecer a eles semântica adicional em Ruby, mas, idealmente, eles querem usar as mesmas classes. Com Injeção de Interface e Métodos Padrão, eles podem injetar, por exemplo, uma RubyObjectinterface java.lang.Object, para que Java Objecte Ruby Objectsejam exatamente a mesma coisa .

Jörg W Mittag
fonte
1
Eu não sigo completamente isso. O método padrão na interface deve ser definido em termos de outros métodos na interface ou métodos definidos em Objeto. Você pode dar um exemplo de como você cria uma interface de método único significativa com um método padrão? Se você precisa da sintaxe do Java 9 para demonstrar, tudo bem.
precisa saber é o seguinte
Por exemplo: a Comparableinterface com um abstrato compareTométodo e padrão lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, isBetween, e clampmétodos, todas implementadas em termos de compareTo. Ou, apenas observe java.util.function.Function: ele possui um applymétodo abstrato e dois métodos de composição padrão, ambos implementados em termos de apply. Tentei dar um exemplo de Collectioninterface, mas conseguir que tudo seja do tipo seguro é complicado e muito longo para esta resposta - tentarei dar uma chance a uma versão que não seja do tipo e que não preserve o tipo. Fique ligado.
Jörg W Mittag
3
Os exemplos ajudam. Obrigado. Entendi mal o que você quis dizer com interface de método único.
precisa saber é o seguinte
Que métodos padrão dizer é que uma única interface método abstrato já não tem de ser uma única interface método ;-)
Jörg W Mittag
Eu estava pensando sobre isso e me ocorreu que AbstractCollection e AbstractList são basicamente o que você está falando aqui (método 2 em vez de 1, mas eu não acho que isso seja crucial.) Se eles fossem reformulados como interfaces com métodos defualt, seria ser super simples de transformar um iterável em uma coleção adicionando tamanho e criando uma lista a partir de qualquer coisa também é muito fácil se você puder indexar e saber o tamanho.
JimmyJames