• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Jeanne Boyarsky
  • Ron McLeod
  • Paul Clapham
  • Liutauras Vilda
Sheriffs:
  • paul wheaton
  • Rob Spoor
  • Devaka Cooray
Saloon Keepers:
  • Stephan van Hulst
  • Tim Holloway
  • Carey Brown
  • Frits Walraven
  • Tim Moores
Bartenders:
  • Mikalai Zaikin

Unexpected difference in Function<String, String> vs.UnaryOperator<String> behaviour

 
Ranch Hand
Posts: 91
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Below is a snippet from a Java-7-to-8 certification mock question, which asked for the return type for the expression on the second line. I correctly anticipated that a type of Function<String, String> would compile. Exploring further, I thought it would be possible to simplify it to UnaryOperator<String>, but it seems this is not the case, and I can’t understand the compiler error message (too long to reproduce here). I then coded a simple demo of a situation where thetypes are interchangable below that, just to console myself that I wasn’t barking (mad) up the wrong tree completely. Any attempts to explain the failure of the more complex assignment welcome  

 
Marshal
Posts: 79153
377
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Adding discussion to our Streams forum.

Where did you get that question from? From the name of the class it might have been WhizzLabs.
Which is the “second line”? Line 10? Right: Line 10 contains a Function from Integer to String. You can try it on JShell (Java9+ only), and you get this sort of result:-

jshell> Function<Integer, String> fun = s -> s.toString();
fun ==> $Lambda$17/1076496284@59ec2012
. . .
jshell> fun.apply(123)
$6 ==> "123"

You have declared the input type of that Function as Integer, so 123 is boxed from int to Integer, and it all runs nicely.
I am rather surprised that you are getting line 12 to compile; not that it is easy to get to run. Because you declared it as Function from String to String, it appears to have altered the input type from fun to take a String; you can see what happens on JShell:-

Function<String, String> fun2 = fun.andThen((String s) -> s + "2").compose((String s) -> Integer.parseInt(s)); // --yes, compiles
fun2 ==> java.util.function.Function$$Lambda$21/1887813102@1ce92674
. . .
jshell> fun2.apply(123)
|  Error:
|  incompatible types: int cannot be converted to java.lang.String
|  fun2.apply(123)
|             ^-^

jshell> fun2.apply("123")
$5 ==> "1232"

You can see that fun2 no longer takes an int, not even boxed, but a String as its input, and you get "1232" returned.
Now, let's look at the unary operator. No, the error message isn't too long to quote here; we can probably make more of it than you are. Please don't hide important details like that. You are passing a Function<T, R> to the andThen() or compose() methods. At this point I am getting confused about the generics myself.
But both andThen and compose() return Function references, not UnaryOperators, so that is going to be the wrong type. I think what the compiler is saying is that you are passing two actual type parameters to something which only requires one type parameter. We'd know more if we had your error messages.
 
Elaine Byrne
Ranch Hand
Posts: 91
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thanks Campbell

Sorry for not specifying the source of the original snippet, it's Question 71 of WhizLabs' OCPJP 8 Upgrade 1Z0-810 Final Test. I suppose I was unsure about the etiquitte re this and didn't want to draw too much attention to their proprietrary material, even though I wasn't asking about answering the Q as such.

Also, sorry about the line number reference - I initially anticipated just pasting the snippets without the enclosing class and imports, then forgot to update after using the code tags with the associated numbered lines. So, yes, Line 10.

Re the input-output types for the function: the WhizLabs explanation, and also this thread https://coderanch.com/t/679728/certification/Tricky-Function, which seems to discuss the same snippet/Q, and was presented to me after I posted here, agree that input is actually String, being first processed by parseInt(...).

I'm not sure what you mean about Line 12, which is a comment. Presuming that you mean Line 10 again, I agree that reference type for fun2 is Function<String, String>, and was not confused about that, or why it compiles.

"But both andThen and compose() return Function references, not UnaryOperators, so that is going to be the wrong type."
--Thanks, I realise my oversight now! [Note to admins - could we have an embarrassed emoticon, please?   ]


Just for competeness, the compile error is:

Whiz4q71.java:26: error: incompatible types: no instance(s) of type variable(s) V exist so that Function<V,String> conforms to UnaryOperator<String>
       UnaryOperator<String> fun3 = fun.andThen((String s) -> s + "2").compose((String s) -> Integer.parseInt(s)); // --does NOT compile!
                                                                              ^
 where V,T,R are type-variables:
   V extends Object declared in method <V>compose(Function<? super V,? extends T>)
   T extends Object declared in interface Function
   R extends Object declared in interface Function


...but the thread can probably be closed now, while I stew in my embarrassment.

Thanks again, Campbell

EB

 
Bartender
Posts: 5465
212
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hope I'm just in time before the closing:

if you DO want to use a UnaryOperator, this is possible:

Certainly a nice one for the OCPJP!    
 
Elaine Byrne
Ranch Hand
Posts: 91
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thanks Piet - if this wasn't programming, I'd say that was meta or somesuch
*mind officially blown*  
 
Campbell Ritchie
Marshal
Posts: 79153
377
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Good point, Piet. Please explain more so even I can understand it.
 
Piet Souris
Bartender
Posts: 5465
212
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
First of all, I was very surprised that line 10, the fun2, compiled, because it was starting with 'fun.' so I thought it should be a Function<Integer, String>. It became clear when I read that the compose function is applied first. Didn't know that, so thanks for the code snippet!

In

s2 is just a function that takes a String as input and returns another String, so just a normal UnaryOperator<String>, or in short:

The only remark that I have is that:

in the second case, we need a full type specification in the compose part, since Integer::parseInt gives an inference error, and when using s -> Integer.parseInt(s) , s is inferred as an Object, instead of a String.

 
Elaine Byrne
Ranch Hand
Posts: 91
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thanks again Piet - if I got that method reference fail correct in the exam it would only be by guessing. Maybe I'll eventually back to this at some  point and be able to think through the chain of type inferences sucessfully; maybe  
 
Campbell Ritchie
Marshal
Posts: 79153
377
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Piet Souris wrote:. . . the compose function is applied first. . . .

Aaaaaaaaaaaaaaaaaaaah! That explains it.
 
Ranch Hand
Posts: 80
1
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Lots of interesting comments, and I admit I might have skimmed them a little too much, but did anyone mention that this compiles (but does not run  )



(notice the only difference is the cast)

The point is that UnaryOperator is a sub interface of Function and constrains the argument and return type to be the same. But it's still a subclass, so since the thing on the right has the compile time type of Function (runtime type too, btw, which is why it doesn't *run*) the left is a more specific type, so the original assignment isn't legal. The cast, however, is credible (since there's a parent/child relationship between Function<E,E> and UnaryOperator<E>. However, since the type of an object is the type that it was created with, not whatever viewpoint(s) it happens to conform to (that is, Java is statically typed, not duck-typed like JavaScript etc.) the thing that was built is *not* in fact a UnaryOperator, so the cast fails at runtime.

In general, once a lambda (not what's being discussed here, has been compiled, its type is fixed, and is subject to all the normal rules of Java typing). However, before it's typed, its type is seemingly flexible, which is why you can do stuff like:



But it's really important to know that the types off those two lambdas, despite being "identical source code" are different. One is a Function object, the other is a UnaryOperator, and those are different classes (well, interfaces) even though they do have a parent/child relationship

In your "they're the same" comment, with blah1 and blah2, try these assignments:



and you'll see what I mean..

Anyway, if f I didn't miss it being already in there, I hope this might add a little to the discussion.

Cheers,
Toby
 
Toby Eggitt
Ranch Hand
Posts: 80
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Toby Eggitt wrote:Lots of interesting comments, and I admit I might have skimmed them a little too much, but did anyone mention that this compiles (but does not run  )

on second look, I've a feeling I did skim it too quickly and missed that this was pretty much already there. Sorry!

 
Elaine Byrne
Ranch Hand
Posts: 91
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Good points, Toby (though I won't claim to be able to follow all the details of the runtime issues yet)!
Yes, I anticipated your "blah2 = blah1;" would not compile before I put it in now (phew)

+ Thanks to the mod/admin who gave me my first cow earlier - and the more seasoned ranchers whose discussion sent it my way!  
 
reply
    Bookmark Topic Watch Topic
  • New Topic