views:

211

answers:

6

The following method generates a warning, but it looks safe to me. I'm sure the problem is with me:

public <S extends CharSequence> S foo(S s) {
    return (S) new StringBuilder(s);
}

It looks like this will always return the argument s. Can anyone show an example that would cause this method to throw an exception?

Edit: I'm not particularly interested in the question of whether generics are necessary here. Rather, I'm looking for a demonstration of how this method is unsafe.

A: 

my Java is rusty, but would this method not throw whatever exceptions

new StringBuilder(s)

can throw?

Steven A. Lowe
I just looked that up. If `s` is null, this would generate an exception, but that would be true of skauffman's non-generic method too.
FarmBoy
+1  A: 

Hi,

foo("test");

is enough to make java try to cast a StringBuilder in a String.

Your code is guaranteed o be wrong, can you explain what you're trying to achieve plase ?

nraynaud
I just tried that, and it worked fine.
FarmBoy
OK, `foo("test")` wasn't enough by itself, but it if I tried to print it, or assign it to a string I get the error. As for my purpose, I'm trying to understand. I'm also trying to pass the SCJP exam, if that helps.
FarmBoy
+5  A: 

It's unsafe because while StringBuilder is a CharSequence, it isn't necessarily of type S (in fact, it almost certainly won't be). You can see this failing just by passing a String into your method. This will construct a StringBuilder using your String as an argument, and then try to cast your StringBuilder to String (which will fail).

There's probably no need to use generics here at all, this should work fine:

public CharSequence foo(CharSequence s) {
    return new StringBuilder(s);
}
skaffman
Still, I want to understand what's wrong with this. I'll confess that I'm studying for the SCJP exam.
FarmBoy
+1 I agree with you when you say that there is no need to use generics here
dfa
Your method has a return type S which is defined by the type of the object you pass in as its argument. The StringBuilder will be cast to that type when the method executes. If you pass in a String as the argument, then type S is actually String, and you can't cast StringBuilder to String.
skaffman
Try calling your method with a String arg:System.out.println(foo("hello"));Legal to the compiler.At runtime:Exception in thread "main" java.lang.ClassCastException: java.lang.StringBuilder cannot be cast to java.lang.StringDoes that help?
Brabster
Ok, that gave me an error. Thanks.
FarmBoy
+2  A: 

For example this will compile:

String a = foo("ss");

but it will fail at runtime:

ClassCastException: java.lang.StringBuilder cannot be cast to java.lang.String

since foo returns S, the type of your input parameter (String in this case).

I think that you don't need to use generics here (as skaffman said in his answer):

public StringBuilder foo(CharSequence s) {
    return new StringBuilder(s);
}
dfa
+1  A: 

The unsafety here lies not within the method itself (though it has its problems, too) but at the call site. The use of S for the input argument's type as well as for the return value tells the compiler, that whatever the type of object may be that is passed to the function, the result has the same type (or a derived type, actually).

Thus, the compiler is allowed to assume, that in the call

foo("hello, world")

the result will be a java.lang.String, while in the call

foo(new StringBuffer("hello, world"))

the result will be a StringBuffer, and so on. In both cases, however, your method does not return what it was supposed to return, namely, an object of the same type as the input argument. Instead, a StringBuilder is returned.

Actually, the only kind of input argument your method will work with is a StringBuilder, anything else will be doomed to crash with a ClassCastException sooner or later, as the compiler might (and often does) insert (hidden) casts at the call sites.

And of course, as others have already pointed out, the use of generics is not really necessary here, anyway.

Dirk
+1  A: 

The return type of your method code is ALWAYS a StringBuilder.

That is because the declared type of the expression 'new StringBuilder(x)' is ALWAYS a StringBuilder, whatever the X.

It is TOTALLY pointless to try and cast this to anything. The "S" information is part of the erasure, which exists only at compile-time and is erased by the time the program runs, that is, run-time. (Casting is a run-time thing exclusively, and casting something to some type/class whose identity has been erased a run-time, is indeed totally pointless.)