O parâmetro de uma função Java pode exigir interfaces opcionais?

8

Eu tenho duas interfaces em Java (versão 8) que são muito semelhantes. Não posso alterar as interfaces e não posso alterar as classes que as implementam.

public interface A {
    int get();
}

public interface B {
    int get();
    int somethingelse();
}

Agora eu tenho uma função que sua implementação se encaixa nas duas interfaces (quase). Eu quero fazer algo assim:

public int foo((A | B) p) {
    int ret = 0;
    if (p instanceof B) {
        ret = p.somthingelse();
    }
    return ret + p.get();
}

Não quero usar a inspeção porque esta função está no pipeline principal do meu programa. Eu quero que ele tenha um bom desempenho. É possível fazer isso em Java?

Editar:

Uma solução simples será copiar / colar foo()e implementá-lo de maneira diferente para cada interface. Mas, na realidade, foo()as interfaces são muito mais longas que isso e estou tentando evitar a duplicação de código.

Liran Funaro
fonte
2
A e B não têm nada em comum. Portanto, crie dois métodos foo (): um que aceite um B e outro que aceite um A. Além disso, não faça suposições sobre a causa de um problema de desempenho até que você tenha um problema de desempenho e verifique essa suposição através de medições .
JB Nizet 08/12/19
@JBNizet A e B têm get()em comum. Este é obviamente um exemplo simplificado. Na realidade, eles têm mais funções em comum.
Liran Funaro
1
Não, eles não. B não estende A. Portanto, eles simplesmente têm um método que tem a mesma assinatura e tipo de retorno. Se cada B é suposto ser um A, então B deve estender A.
JB Nizet
1
@ user2478398 Você já viu a resposta ernest_k? Eu acho que é disso que você está falando.
Liran Funaro
1
@LiranFunaro Se eles (ambas as interfaces) têm algo em comum, por que não extraí-lo para um pai e estendê-lo ainda mais? Além: o publicacessador é redundante para métodos de interface. E você quis dizer em instanceofvez de implements?
Naman

Respostas:

4

Infelizmente, não há como criar esse tipo de relacionamento retroativo entre dois tipos não relacionados.

Se você puder alterar foo()e suas invocações, poderá usar interfaces funcionais que correspondem às assinaturas que você invoca dentro foo. Estou usando IntSupplieraqui, com expressões lambda correspondentes usando implementações concretas de Ae B.

Digamos que você tenha estas implementações:

class AImpl implements A {
     //implementation
}
class BImpl implements B {
     //implementation
}

Você pode mudar foopara algo como:

public int foo(IntSupplier get, IntSupplier somethingElse) {
    int ret = 0;
    if (somethingElse != null) {
        ret = somethingElse.getAsInt();
    }
    return ret + get.getAsInt();
}

E chame assim:

A a = new AImpl();
B b = new BImpl();

int result = this.foo(a::get, b::somethingelse);
ernest_k
fonte
Solução interessante! Isso pode ser estendido a uma solução que não requer alterações na assinatura do foo. Simplesmente escreva duas implementações foo()para cada interface e ligue para o seu foo()fornecedor correspondente. Deseja adicionar isso ou deseja que eu edite sua resposta?
Liran Funaro
1
@LiranFunaro Right. Fiquei limitado pela restrição de que a iflógica deve permanecer dentro foo(). Mas sim, você pode fazer isso definitivamente, se for apropriado.
Ernest_k 08/12/19
1
Desculpe, mas há muito pouco que eu possa relacionar com a pergunta e a IntSuppliersugestão nesta resposta. Além disso, o BImpl(ou simplesmente B) não pode ser usado como um FunctionalInterface, dado mais de um método abstrato. Além disso, a partir de um comentário , a afirmação " dois tipos não relacionados " não é verdadeira. Nitpick: IntSupplier getnão é usado no seu código.
Naman
1
@ Naman Obrigado pela sua opinião. Bnão está sendo usado como uma interface funcional. b:somethingelseé uma referência de método para IntSuppliereste caso (eu sugiro que você compile o código). E sim, Ae Bsão dois tipos completamente independentes. E sobre a solução geral, acho que entendo que você acha um pouco estranho. Posso supor que existem abordagens mais refinadas, mas não muitas que evitam a refatoração que o OP não pode pagar.
ernest_k
3

A única maneira que eu posso imaginar é isso:

public int foo(A p) {
    return internal_foo(p);
}

public int foo(B p) {
    return internal_foo(p);
}

private int internal_foo(Object p) {
    if (p instanceof A) {
        return ((A)p).get();
    }
    if (p instanceof B) {
        B b = (B)p;
        ret = b.somthingelse();
        return ret + b.get();
    }
    throw new ClassCastException("Wrong type");
}
Zefick
fonte
Obrigado. O problema é que a implementação da função é muito mais longa que isso. Então eu vou ter que copiar / colar um monte de código, que é o que eu estou tentando evitar :)
Liran Funaro
2

Além disso, o Java não suporta isso diretamente, mas você pode criar manualmente um "wrapper de união de tipo". Algo assim:

abstract class AorB {
    public static AorB wrap(A a) {
        return new AWrapper(a);
    }

    public static AorB wrap(B b) {
        return new BWrapper(b);
    }

    abstract int get();
    abstract int somethingElse();
}

class AWrapper extends AorB {
    private final A a;

    AWrapper(A a) {
        this.a = a;
    }

    @Override
    int get() {
        return a.get();
    }

    @Override
    int somethingElse() {
        return 0;
    }
}

class BWrapper extends AorB {
    private final B b;

    BWrapper(B b) {
        this.b = b;
    }

    @Override
    int get() {
        return b.get();
    }

    @Override
    int somethingElse() {
        return b.somethingElse();
    }
}

// and then

public int foo(A a) {
    return fooImpl(AorB.wrap(a));
}

public int foo(B b) {
    return fooImpl(AorB.wrap(b));
}

int fooImpl(AorB p) {
    return p.get() + p.somethingElse();
}

Você também pode adicionar algumas funções de inspeção como hasSomethingElse(), se não houver um valor padrão apropriado para retornar, como 0acima.

xs0
fonte
Também aceitaria esta resposta se pudesse aceitar duas respostas.
Liran Funaro