Search code examples
javagenericslambdajava-8functional-interface

Why can't i assign a lambda to an untyped predicate reference? but can assign an initialized typed predicate reference to it?


I noticed something strange while trying to create custom predicate references that are used based on a method argument type.

I have an object called AffiliateLinkSubset and it has a boolean getter called isGeneral. When i try to do the following:

Predicate<?> partitionPredicate = AffiliateLinkSubset::isGeneral;

I get the error non-static method cannot be referenced from a static context.

But when i assign the generic type AffiliateLinkSubset to the Predicate it works, this is nothing special.. What is special however is that the following also works:

Predicate<AffiliateLinkSubset> partitionPredicate = affiliateLinkSubset::isGeneral;
Predicate<?> test = partitionPredicate;

The IDE gives no error for this! Even though I am effectively assigning the same lambda to the untyped predicate test. How is this possible? And will the predicate work during runtime? I assume it will because during compilation all types are erased and replaced with type Object. This is why I don't understand why I can't assign an untyped predicate a lambda. Can anyone explain?

AffiliateLinkSubset is an abbreviation of the actual class, here it is:

import POJOs.PojoENUMS.LocalizedStorefront;

import java.util.Map;
import java.util.Set;

public class AffiliateLinkSubsetForStatisticsCalculation {
    private Long id;
    private String title;
    private Double productValue;
    private boolean general;
    private Set<String> keywords;
    private Map<String, Boolean> booleanKeywords;
    private LocalizedStorefront localizedStorefront;

    public AffiliateLinkSubsetForStatisticsCalculation(Long id, String title, Double productValue, boolean general, Set<String> keywords, Map<String, Boolean> booleanKeywords, LocalizedStorefront localizedStorefront) {
        this.id = id;
        this.title = title;
        this.productValue = productValue;
        this.general = general;
        this.keywords = keywords;
        this.booleanKeywords = booleanKeywords;
        this.localizedStorefront = localizedStorefront;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Double getProductValue() {
        return productValue;
    }

    public void setProductValue(Double productValue) {
        this.productValue = productValue;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public boolean isGeneral() {
        return general;
    }

    public void setGeneral(boolean general) {
        this.general = general;
    }

    public Set<String> getKeywords() {
        return keywords;
    }

    public void setKeywords(Set<String> keywords) {
        this.keywords = keywords;
    }

    public Map<String, Boolean> getBooleanKeywords() {
        return booleanKeywords;
    }

    public void setBooleanKeywords(Map<String, Boolean> booleanKeywords) {
        this.booleanKeywords = booleanKeywords;
    }

    public LocalizedStorefront getLocalizedStorefront() {
        return localizedStorefront;
    }

    public void setLocalizedStorefront(LocalizedStorefront localizedStorefront) {
        this.localizedStorefront = localizedStorefront;
    }
}

Solution

  • Firstly the Predicate<?> predicate cant work since the unknown type. Any of them will work:

    Predicate<? extends AffiliateLinkSubset> predicate = AffiliateLinkSubset::isGeneral;
    Predicate<AffiliateLinkSubset> predicate = AffiliateLinkSubset::isGeneral;
    

    The method reference shortcut AffiliateLinkSubset::isGeneral is understood as object -> object.isGeneral() where object is a type of AffiliateLinkSubset. This is why the first predicate couldn't work, because of <?> which didn't defined the type of AffiliateLinkSubset. The isGeneral() method is not defined in Object.

    Let's continue. If you type:

    Predicate<?> partitionPredicate = object -> AffiliateLinkSubset.isGeneral();
    

    This will not compile since you treat the class method isGeneral() the same way is it were a static one. For this you need an instance of a class which can call this method:

    AffiliateLinkSubset affiliateLinkSubset = new AffiliateLinkSubset();
    Predicate<?> partitionPredicate = object -> affiliateLinkSubset.isGeneral();
    

    Now, object is an instance of Object and the result relies on the right side of the lambda which doesn't touch the object and doesn't matter the input, thus this might work as well:

    Supplier<Boolean> supplier = () -> affiliateLinkSubset.isGeneral();
    

    It makes not much sense so I guess the solution you really have been mentioned already:

    Predicate<? extends AffiliateLinkSubset> predicate = AffiliateLinkSubset::isGeneral;