¿Puede un parámetro de función Java requerir interfaces opcionales?

8

Tengo dos interfaces en Java (versión 8) que son muy similares. No puedo cambiar las interfaces y no puedo cambiar las clases que las implementan.

public interface A {
    int get();
}

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

Ahora tengo una función que su implementación se ajusta a ambas interfaces (casi). Quiero que haga algo así:

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

No quiero usar la inspección porque esta función está en la tubería principal de mi programa. Quiero que tenga un buen rendimiento. ¿Es posible hacer esto en Java?

Editar:

Una solución simple será copiar / pegar foo()e implementarlo de manera diferente para cada interfaz. Pero, en realidad, foo()las interfaces son mucho más largas que eso y estoy tratando de evitar la duplicación de código.

Liran Funaro
fuente
2
A y B no tienen nada en común. Por lo tanto, cree dos métodos foo (): uno que tome una B y otro que tome una A. Además, no haga suposiciones sobre la causa de un problema de rendimiento hasta que tenga un problema de rendimiento, y haya verificado este supuesto mediante mediciones .
JB Nizet
@JBNizet A y B tienen get()en común. Este es, por supuesto, un ejemplo simplificado. En realidad, tienen más funciones en común.
Liran Funaro
1
No, ellos no. B no extiende a A. Por lo tanto, tienen un método que tiene la misma firma y tipo de retorno. Si se supone que cada B es una A, entonces B debería extender A.
JB Nizet
1
@ user2478398 ¿Has mirado la respuesta ernest_k? Creo que es de lo que estás hablando.
Liran Funaro
1
@LiranFunaro Si ellos (ambas interfaces) tienen algo en común, ¿por qué no extraerlo a un padre y extenderlo más? Aparte: el descriptor de publicacceso es redundante para los métodos de interfaz. ¿Y quiso decir en instanceoflugar de implements?
Naman

Respuestas:

4

Desafortunadamente, no hay forma de crear ese tipo de relación retroactiva entre dos tipos no relacionados.

Si puede cambiar foo()y sus invocaciones, podría utilizar interfaces funcionales que coincidan con las firmas que invoca en su interior foo. Estoy usando IntSupplieraquí, con las correspondientes expresiones lambda usando implementaciones concretas de Ay B.

Digamos que tiene estas implementaciones:

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

Puedes cambiar fooa algo como:

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

Y llámalo de esta manera:

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

int result = this.foo(a::get, b::somethingelse);
ernest_k
fuente
¡Solución interesante! Esto se puede extender a una solución que no requiere cambios en la firma de foo. Simplemente escriba dos implementaciones foo()para cada interfaz y luego llame a su foo()proveedor correspondiente. ¿Desea agregar esto o desea que edite su respuesta?
Liran Funaro
1
@LiranFunaro Derecha. Estaba limitado por la restricción de que la iflógica debe permanecer dentro foo(). Pero sí, definitivamente puedes hacerlo si es apropiado.
ernest_k
1
Lo siento, pero es muy poco lo que puedo relacionar con la pregunta y la IntSuppliersugerencia en esta respuesta. Además, BImpl(o simplemente B) no se puede utilizar como una interfaz funcional, dado más de un método abstracto. Además, de un comentario , la afirmación " dos tipos no relacionados " no es cierta. Nitpick: IntSupplier getno se usa en su código.
Naman
1
@Naman Gracias por tu opinión. Bno se usa como una interfaz funcional. b:somethingelsees una referencia de método para IntSupplieren este caso (te sugiero que compiles el código). Y sí, Ay Bson dos tipos completamente ajenos. Y sobre la solución en general, creo que entiendo que te resulta un poco incómodo. Puedo suponer que hay enfoques más precisos, pero no muchos que eviten la refactorización que el OP no puede permitirse.
ernest_k
3

La única forma que puedo imaginar es esta:

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
fuente
Gracias. El problema con esto es que la implementación de la función es mucho más larga que eso. Así que tendré que copiar / pegar mucho código, que es lo que estoy tratando de evitar :)
Liran Funaro
2

Afaik, Java no admite esto directamente, pero puede crear manualmente un "contenedor de unión de tipo". Algo como esto:

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();
}

También puede agregar algunas funciones de inspección como hasSomethingElse(), si no hay un valor predeterminado apropiado para devolver, como el 0anterior.

xs0
fuente
También aceptaría esta respuesta si pudiera aceptar dos respuestas.
Liran Funaro