Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Subclasses with Java 8 lambdas and Optional

Tags:

java

java-8

I don't understand why the following code doesn't compile:

private ResponseEntity<JSendResponse> buildResponse(RequestModel requestModel,
                                                    RequestModelParamConverter paramConverter,
                                                    Supplier<String> xsdSupplier,
                                                    Supplier<String> xmlTemplateSupplier) {

    return Optional.ofNullable(new RequestErrorHandler<>().validate(validator, requestModel))
            .map(validationErrors -> new ResponseEntity<>(validationErrors, HttpStatus.BAD_REQUEST))
            .orElse(this.buildResponseForValidRequest(requestModel, paramConverter, xsdSupplier, xmlTemplateSupplier));
}

Compile error:

orElse (org.springframework.http.ResponseEntity<com.company.util.response.JSendFailResponse>) in Optional cannot be applied to (org.springframework.http.ResponseEntity<com.company.util.response.JSendResponse>)

While this code (which I think is logically the same code) does compile:

private ResponseEntity<JSendResponse> buildResponse(RequestModel requestModel,
                                                    RequestModelParamConverter paramConverter,
                                                    Supplier<String> xsdSupplier,
                                                    Supplier<String> xmlTemplateSupplier) {

    JSendResponse validationErrors = new RequestErrorHandler<>().validate(validator, requestModel);

    if(validationErrors == null) {
        return this.buildResponseForValidRequest(requestModel, paramConverter, xsdSupplier, xmlTemplateSupplier);
    }
    else
    {
        return new ResponseEntity<>(validationErrors, HttpStatus.BAD_REQUEST);
    }
}

The problem seems to be that the RequestErrorHandler<>().validate method returns a class called JSendFailResponse if the validation fails. JSendFailResponse is a subclass of JSendResponse, which is what is ultimately returned. It seems like the lambda code is not able to understand that JSendFailResponse is a JSendResponse.

I can get it to compile if I cast validationErrors to a JSendResponse in the map lambda, but then I lose some of the fields on the JSendFailResponse.

EDIT: This code also fails to compile:

private ResponseEntity<? extends JSendResponse> buildResponse(RequestModel requestModel,
                                                    RequestModelParamConverter paramConverter,
                                                    Supplier<String> xsdSupplier,
                                                    Supplier<String> xmlTemplateSupplier) {

    return Optional.ofNullable(new RequestErrorHandler<>().validate(validator, requestModel))
            .map(validationErrors -> new ResponseEntity<>(validationErrors, HttpStatus.BAD_REQUEST))
            .orElse(this.buildResponseForValidRequest(requestModel, paramConverter, xsdSupplier, xmlTemplateSupplier));
}

EDIT2: Here is a simplified example you can copy/paste into your IDE to see for yourself.

import java.util.*;

public class GemLamDemo {

    public static void main(String... args) {

        GemLamDemo gld = new GemLamDemo();

        gld.getList(null);

    }

    public List<? extends TypeA> getList(TypeA ta) {

        return Optional.ofNullable(ta)
                .map(a -> new ArrayList<TypeA>(Arrays.asList(a)))
                .orElse(new ArrayList<TypeB>(Arrays.asList(new TypeB())));
    }

    public class TypeA {

    }

    public class TypeB extends TypeA {

    }
}

EDIT3: I was thinking I understood this issue based on the help I've received so far, but the following code compiles and works.

Optional.ofNullable(val1)
                .map(a -> new TypeA())
                .orElse(new TypeB());

So the issue does not seem to be that the map and the orElse must return the same type, it seems to be related to paramterization. So, map can emit TypeA and orElse can emit TypeB if its a subclass of TypeA. But they can not emit differing parameterized types of List. List<TypeA> and List<TypeB> don't seem to be considered subtypes of each other and now that I think about it, they aren't.

ResponseEntity<JSendResponse> is a different type than ResponseEntity<JSendFailResponse>. If I were returning plain JSendResponse and JSendFailResponse from the lambdas, that would probably work. But I'm not, I'm emitting different versions of ResponseEntity, which are not really related by hierarchy. So I guess it comes down to how Optional supports (or doesn't support) wildcard generics. I can't set the type of the Optional to ResponseEntity<? extends JSendResponse>, so I am limited to strict type hierarchies.

EDIT4:

The above example is incorrect because the types are switched from the original case. I think I get it now, thanks everybody.

like image 625
JCN Avatar asked Dec 10 '18 21:12

JCN


2 Answers

The type emitted from map() is inferred as JSendFailResponse, but you’re offering a different type in orElse() and both types must agree.

Explicitly type to call to map() with a common type:

.<JSendResponse>map(validationErrors -> new ResponseEntity<>(validationErrors, HttpStatus.BAD_REQUEST))
like image 162
Bohemian Avatar answered Sep 20 '22 02:09

Bohemian


If you check the oracle documention : https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html,

The signature of .ofNullable() method is : public static <T> Optional<T> ofNullable(T value)

and orElse() : public T orElse(T other)

So the parameter on the both methods type is T and orElse() does not have <? extends T> as parameter they should both have T as type, so your method shall not work i guess,

you should try something this ( using your simplified example) :

public List<TypeA> getList(TypeA ta) {

        ArrayList<TypeA> typeAinstance = new ArrayList<>();
        return Optional.ofNullable(ta)
                .map(a -> new ArrayList<TypeA>(Arrays.asList(a)))
                .orElse(typeAinstance.getClass().cast(Arrays.asList(new TypeB())));
    }

    public class TypeA {

    }

    public class TypeB extends TypeA {

    }

Hope this helps

like image 29
B.M Avatar answered Sep 19 '22 02:09

B.M