Enforcing Java method return type to fit certain Generic signature

被刻印的时光 ゝ 提交于 2019-12-10 10:12:37

问题


Let's say I have this interface:

interface Foo<T extends Foo<T>> { // ... }

And the following three classes implementing it:

class TrueFoo  implements Foo<TrueFoo > { // ... }

class FalseFoo implements Foo<FalseFoo> { // ... }

class WrongFoo implements Foo<FalseFoo> { // ... }
//                            ^^^^^^^^ this here is wrong, only 
//                                     Foo<WrongFoo> should be allowed

What would the method signature of the following method need to be to enforce that I can return objects of type TrueFoo or FalseFoo, but not WrongFoo...

public ???? getFoo(boolean which) {
    return which ? new TrueFoo() : new FalseFoo();
}

The following don't work:

  • This allows me to also return a new WrongFoo():

    public Foo<?> getFoo(boolean which) {
        return which ? new TrueFoo() : new FalseFoo();
    }
    
  • This doesn't allow me to return anything (explanation following):

    public <FooType extends Foo<FooType>> FooType getFoo(boolean which) {
        return which ? new TrueFoo() : new FalseFoo();
    }
    

    The problem is that the type of FooType is determined by the context in which I call the function getFoo() and not by the object I am actually trying to return.

Enforcing this signature when calling a method is easy, by the way:

public <FooType extends Foo<FooType>> void test(FooType foo) {
    // Do something with foo
}

This will give you a bound mismatch error if you try to call it with an instance of WrongFoo.

Makes me think there should be a way to do this for method return types as well.

Or is there a way to enforce this signature already in the interface definition for Foo?

EDIT:

To help visualize what this interface is supposed to do, you can for example think of this version of it:

interface CanCopy<Type extends CanCopy<Type>> {
    public Type copy();
}

Clearly a class like Foo implements CanCopy<Bar> doesn't make any sense, only Foo implements CanCopy<Foo> or Bar implements CanCopy<Bar>.

EDIT 2:

Just as proof-of-concept that solutions exist, I could define myself the following helper class:

class FooResult {
    private final Foo<?> foo;

    public <FooType extends Foo<FooType>> FooResult(FooType foo) {
        this.foo = foo;
    }

    @SuppressWarnings("unchecked")
    public <FooType extends Foo<FooType>> FooType getFoo() {
        return (FooType) foo;
    }
}

Then I can require my getFoo method to be of this type:

public FooResult getFoo(boolean which) {
    return which ? new FooResult(new TrueFoo()) : new FooResult(new WrongFoo());
}

That way, I will not be able to return a WrongFoo from my get method.

But this, clearly, is a little too convoluted to be as elegant as Java Generics usually tend to make code (in my experience)... So, can this be shortened somehow?

EDIT 3:

I found another way to provide a check for someone implementing the interface by defining it like this:

interface Foo<FooType extends Foo<FooType>> { 
    // Other interface definitions

    /**
     * Please implement with just this line in it:<br/><br/>
     * &nbsp;&nbsp;&nbsp;&nbsp;<code>return this;</code>
     * @return <code>this</code>
     */
    public FooType returnThis();
}

Now, if someone tries to implement the WrongFoo class as described above, he will have to provide the method FalseFoo returnThis() and if he simply implements it as return this as requested in the doctype, that line will throw an error.

It's not a guarantee, but it's a pretty good dummy-check against mistakes that come from careless copying of a class... And in a case where a message with this signature is required anyways, it would be a great solution.

Any more ideas?


回答1:


The issue you're having with generics is the symptom, not the problem: You're asking your interface to become dependent upon your concrete implementation. If you want your getFoo interface method to return something that is not a WrongFoo, you must return something that is a TrueFoo or a FalseFoo but not a WrongFoo. Your class hierarchy does not have this particular indirection.

To add it, you would need to do something like the following:

class TrueFoo extends SpecialFoo {...}

class FalseFoo extends SpecialFoo {...}

class WrongFoo implements Foo<WrongFoo> {...}

class SpecialFoo implements Foo<SpecialFoo> {  
  SpecialFoo getFoo() {...}
}

Remember that Java supports covariant return types, so by returning SpecialFoo we can implement the contract created in the Foo interface: Foo getFoo() {...}

If you were to need behavior specific to TrueFoo and FalseFoo, you could further parameterize SpecialFoo, and so on, turtles all the way down.

EDIT:

After reading your edit, I don't believe what you're trying to accomplish is supported by the language. It seems like you want to constrain the interface based on the concrete implementation, which would by definition make it not an interface. I think the following example is as close as you're going to get to what you're looking for:

interface CanCopy<T extends CanCopy<T>> {
       T copy();
}

interface FooCanCopy extends CanCopy<Foo> {
}

interface BarCanCopy extends CanCopy<Bar> {
}

class Foo implements FooCanCopy {

    public Foo copy() {
        return null;
    }
}

class Bar implements BarCanCopy {

    public Bar copy() {
        return null;
    }
}

I hope its clear now why the approach doesn't work, even here you still can't prevent someone from doing something like class Baz implements FooCanCopy either. I'd be more curious to know why you're trying to do this? If its to protect developers from making a mistake, there might be other options, i.e. introspecting unit tests or packaging changes.



来源:https://stackoverflow.com/questions/13637882/enforcing-java-method-return-type-to-fit-certain-generic-signature

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!