DZone
Java Zone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
  • Refcardz
  • Trend Reports
  • Webinars
  • Zones
  • |
    • Agile
    • AI
    • Big Data
    • Cloud
    • Database
    • DevOps
    • Integration
    • IoT
    • Java
    • Microservices
    • Open Source
    • Performance
    • Security
    • Web Dev
DZone > Java Zone > You Will Regret Applying Overloading with Lambdas!

You Will Regret Applying Overloading with Lambdas!

Lukas Eder user avatar by
Lukas Eder
·
Feb. 06, 15 · Java Zone · Interview
Like (0)
Save
Tweet
3.15K Views

Join the DZone community and get the full member experience.

Join For Free

writing good apis is hard. extremely hard. you have to think of an incredible amount of things if you want your users to love your api. you have to find the right balance between:

  1. usefulness
  2. usability
  3. backward compatibility
  4. forward compatibility

we’ve blogged about this topic before, in our article: how to design a good, regular api . today, we’re going to look into how…

java 8 changes the rules

yes!

overloading is a nice tool to provide covenience in two dimensions:

  • by providing argument type alternatives
  • by providing argument default values

examples for the above from the jdk include:

public class arrays {
 
    // argument type alternatives
    public static void sort(int[] a) { ... }
    public static void sort(long[] a) { ... }
 
    // argument default values
    public static intstream stream(int[] array) { ... }
    public static intstream stream(int[] array, 
        int startinclusive, 
        int endexclusive) { ... }
}

the jooq api is obviously full of such convenience. as jooq is a dsl for sql , we might even abuse a little bit:

public interface dslcontext {
    <t1> selectselectstep<record1<t1>> 
        select(selectfield<t1> field1);
 
    <t1, t2> selectselectstep<record2<t1, t2>> 
        select(selectfield<t1> field1, 
               selectfield<t2> field2);
 
    <t1, t2, t3> selectselectstep<record3<t1, t2, t3>> s
        select(selectfield<t1> field1, 
               selectfield<t2> field2, 
               selectfield<t3> field3);
 
    <t1, t2, t3, t4> selectselectstep<record4<t1, t2, t3, t4>> 
        select(selectfield<t1> field1, 
               selectfield<t2> field2, 
               selectfield<t3> field3, 
               selectfield<t4> field4);
 
    // and so on...
}

languages like ceylon take this idea of convenience one step further by claiming that the above is the only reasonable reason why overloading is be used in java. and thus, the creators of ceylon have completely removed overloading from their language, replacing the above by union types and actual default values for arguments. e.g.

// union types
void sort(int[]|long[] a) { ... }
 
// default argument values
intstream stream(int[] array,
    int startinclusive = 0,
    int endinclusive = array.length) { ... }

read “top 10 ceylon language features i wish we had in java” for more information about ceylon.

in java, unfortunately, we cannot use union types or argument default values. so we have to use overloading to provide our api consumers with convenience methods.

if your method argument is a functional interface , however, things changed drastically between java 7 and java 8, with respect to method overloading. an example is given here from javafx.

javafx’s “unfriendly” observablelist

javafx enhances the jdk collection types by making them “observable”. not to be confused with observable , a dinosaur type from the jdk 1.0 and from pre-swing days.

javafx’s own observable essentially looks like this:

public interface observable {
  void addlistener(invalidationlistener listener);
  void removelistener(invalidationlistener listener);
}

and luckily, this invalidationlistener is a functional interface:

@functionalinterface
public interface invalidationlistener {
  void invalidated(observable observable);
}

this is great, because we can do things like:

observable awesome = 
    fxcollections.observablearraylist();
awesome.addlistener(fantastic -> splendid.cheer());

(notice how i’ve replaced foo/bar/baz with more cheerful terms. we should all do that. foo and bar are so 1970 )

unfortunately, things get more hairy when we do what we would probably do, instead. i.e. instead of declaring an observable , we’d like that to be a much more useful observablelist :

observablelist<string> awesome = 
    fxcollections.observablearraylist();
awesome.addlistener(fantastic -> splendid.cheer());

but now, we get a compilation error on the second line:

awesome.addlistener(fantastic -> splendid.cheer());
//      ^^^^^^^^^^^ 
// the method addlistener(listchangelistener<? super string>) 
// is ambiguous for the type observablelist<string>

because, essentially…

public interface observablelist<e> 
extends list<e>, observable {
    void addlistener(listchangelistener<? super e> listener);
}

and…

@functionalinterface
public interface listchangelistener<e> {
    void onchanged(change<? extends e> c);
}

now again, before java 8, the two listener types were completely unambiguously distinguishable, and they still are. you can easily call them by passing a named type. our original code would still work if we wrote:

observablelist<string> awesome = 
    fxcollections.observablearraylist();
invalidationlistener hearye = 
    fantastic -> splendid.cheer();
awesome.addlistener(hearye);

or...

observablelist<string> awesome = 
    fxcollections.observablearraylist();
awesome.addlistener((invalidationlistener) 
    fantastic -> splendid.cheer());

or even...

observablelist<string> awesome = 
    fxcollections.observablearraylist();
awesome.addlistener((observable fantastic) -> 
    splendid.cheer());

all of these measures will remove ambiguity. but frankly, lambdas are only half as cool if you have to explicitly type the lambda, or the argument types. we have modern ides that can perform autocompletion and help infer types just as much as the compiler itself.

imagine if we really wanted to call the other addlistener() method, the one that takes a listchangelistener. we’d have to write any of

observablelist<string> awesome = 
    fxcollections.observablearraylist();
 
// agh. remember that we have to repeat "string" here
listchangelistener<string> hearye = 
    fantastic -> splendid.cheer();
awesome.addlistener(hearye);

or...

observablelist<string> awesome = 
    fxcollections.observablearraylist();
 
// agh. remember that we have to repeat "string" here
awesome.addlistener((listchangelistener<string>) 
    fantastic -> splendid.cheer());

or even...

observablelist<string> awesome = 
    fxcollections.observablearraylist();
 
// wtf... "extends" string?? but that's what this thing needs...
awesome.addlistener((change<? extends string> fantastic) -> 
    splendid.cheer());

overload you shan’t. be wary you must.

api design is hard. it was hard before, it has gotten harder now. with java 8, if any of your api methods’ arguments are a functional interface, think twice about overloading that api method. and once you’ve concluded to proceed with overloading, think again, a third time whether this is really a good idea.

not convinced? have a close look at the jdk. for instance the java.util.stream.stream type. how many overloaded methods do you see that have the same number of functional interface arguments, which again take the same number of method arguments (as in our previous addlistener() example)?

zero.

there are overloads where overload argument numbers differ. for instance:

<r> r collect(supplier<r> supplier,
              biconsumer<r, ? super t> accumulator,
              biconsumer<r, r> combiner);
 
<r, a> r collect(collector<? super t, a, r> collector);

you will never have any ambiguity when calling collect() .

but when the argument numbers do not differ, and neither do the arguments’ own method argument numbers, the method names are different. for instance:

<r> stream<r> map(function<? super t, ? extends r> mapper);
intstream maptoint(tointfunction<? super t> mapper);
longstream maptolong(tolongfunction<? super t> mapper);
doublestream maptodouble(todoublefunction<? super t> mapper);

now, this is super annoying at the call site, because you have to think in advance what method you have to use based on a variety of involved types.

but it’s really the only solution to this dilemma. so, remember:

you will regret applying overloading with lambdas!

Java (programming language) JavaFX Ceylon (programming language) Interface (computing) Compilation error Forward compatibility Backward compatibility Design Compatibility (chemical) Creator (software)

Published at DZone with permission of Lukas Eder, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • The End of the Beginning for Apache Cassandra
  • Ultra-Fast Microservices: When Microstream Meets Wildfly
  • 7 Traits of an Effective Software Asset Manager
  • Take Control of Your Application Security

Comments

Java Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • MVB Program
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends:

DZone.com is powered by 

AnswerHub logo