No coercion in for-each loop?

D

Dave Stallard

OK, I finally caught up to Java 1.5/6. (Delay had to do with available
tools and my intolerance for Eclipse, but I've reconciled myself to it now).

Question: Eclipse gives me a compiler error for:

List list = new ArrayList();
for (String s : list)
System.out.println(s);

It says "Type mismatch: Cannot convert from element type Object to
String". It's fine if I change decl of list to List<String>, or coerce
list to List<String> in the loop decl;

Is this an *actual* compiler error in the Java spec? Is Java actually
that hissy about the for-each variable? The compiler doesn't mind the
code below, even though it causes a runtime type-cast error. So why
can't it insert the cast to String in the loop setup? Am I missing
something?

Dave

List ar = new ArrayList();
ar.add(1);
List<String> list = ar;
for (String s : list)
Utils.log(s);
 
L

Lew

Dave said:
OK, I finally caught up to Java 1.5/6. (Delay had to do with available
tools and my intolerance for Eclipse, but I've reconciled myself to it
now).

So don't use Eclipse!

There are plenty of good alternatives, NetBeans to name one.

What does Eclipse have to do with what version of Java you use, and why are
other free tools not available to you?
 
L

Lew

Dave said:
Question: Eclipse gives me a compiler error for:

Actually, Java gives you that error, not Eclipse.
List list = new ArrayList();
for (String s : list)
System.out.println(s);

It says "Type mismatch: Cannot convert from element type Object to
String". It's fine if I change decl of list to List<String>, or coerce
list to List<String> in the loop decl;

Is this an *actual* compiler error in the Java spec? Is Java actually
Yes.

that hissy about the for-each variable? The compiler doesn't mind the

"Hissy"?

Since when do we expect compilers to be imprecise?
code below, even though it causes a runtime type-cast error. So why
can't it insert the cast to String in the loop setup? Am I missing
something?

You sure are. The for-each idiom requires that the loop variable ('s' in your
example) be the same type as the base type of the array or collection. The
base type of a raw List is Object, so
for ( String s : list )
is not legal Java for list. Remember that you are essentially declaring list
to be of type List<Object>, so the compiler has no knowledge that the base
type is meant to be String.

When you use a List<String>, the compiler knows the base type is String and
happily implements the intended for-each.
 
M

Mark Rafn

Dave Stallard said:
OK, I finally caught up to Java 1.5/6. (Delay had to do with available
tools and my intolerance for Eclipse, but I've reconciled myself to it now).

Umm, there's LOTs of alternatives to eclipse. You can still use
vi/emacs/notepad if you're so inclined.
Question: Eclipse gives me a compiler error for:
List list = new ArrayList();
for (String s : list)
System.out.println(s);
It says "Type mismatch: Cannot convert from element type Object to
String". It's fine if I change decl of list to List<String>, or coerce
list to List<String> in the loop decl;

Right. The java compiler doesn't do as much type inference as it could,
unfortunately.
Is this an *actual* compiler error in the Java spec?

Umm, how hard is this to check for yourself? Seriously - you can't go saying
you dislike eclipse then not spend the 2 minutes (tops!) it takes to make a
test case and hit it with javac.
Is Java actually that hissy about the for-each variable?

Short answer: yes. It's hissy about casting things you haven't told it to.
doesn't mind the code below, even though it causes a runtime type-cast error.

Well, there you go. The whole point of generics is to make it easier to write
code that does NOT get runtime cast errors.
 
L

Lew

It is worth noting though that Eclipse uses its own compiler not
a standard JDK compiler.

I did not assert otherwise, only that it is the rules of the Java language
that determined the error. The Eclipse compiler is "a standard JDK compiler"
in the sense that it complies with the Java standard. Since the code was not
legal Java, one should not walk away thinking it's only Eclipse that would object.
 
D

Dave Stallard

Lew said:

Yes, "hissy".
Since when do we expect compilers to be imprecise?

We don't expect them to be imprecise. We expect them to do things
automatically for us, rather than make us do them. That's what
compilers are for. In this case, the compiler has all the knowledge it
needs right there in the code to insert the cast *itself*. It has the
declaration of the iteration variable as String. It can see that the
list variable is a raw-type list:

for (String s : (List<String>)l) ...

It's the same mentality that forces us to write:

Foo f = (Foo)..expression..

It sees that f is a Foo. It doesn't know that expression is a Foo. So
it can see that a typecast is needed, and even tells you so. But it
makes YOU type it in. What sense is there in that? It should at most
be a compiler warning, not an error. This may seem small, but iterated
millions of times over one's life span, it becomes very annoying. It's
one of the things that's making people rail against "strong typing", and
driving them to scripting languages like Ruby. Strong typing is good;
unnecessary typing (pun intended) is not.

What's more, it's not even consistent, as I showed in the other snippet.
It's still possible to generate run-time class cast exceptions
You sure are. The for-each idiom requires that the loop variable ('s'

No, you're missing something. This rule means that in order to use the
for-each loop in a useful way, you have to buy into Java generics!
That's no good. So the one thing they did to make your life easier, and
should have done from the beginning - obviating the need for Iterator
everywhere - they fucked up.

By the way, and I don't mean to wound your feelings here, but in C# this
works fine. Oh, and they also give you '[]' for array fetching like a
NORMAL programming language.

Don't get me wrong. I still use Java for its compatibility on Windows
and Linux, and for its now-quite-respectable speed relative to C++. I
used it back when no one else in my group used it, and I will continue
to use it. But with respect to Java's progress as a *programming
language* I am bitterly disappointed.

Dave
 
L

Lew

Dave said:
Yes, "hissy". [and more]

Take it easy, man. The Java language is what it is, just like Basic is what
it is. It violates the rules of Java to ask a for-each to infer that a
List said:
No, you're missing something. This rule means that in order to use the for-each loop in a useful way, you have to buy into Java generics! That's no good. So the one thing they did to make your life easier, and should have done from the beginning - obviating the need for Iterator everywhere - they fucked up.

"buy into"?

The for-each loop was introduced with generics, and they are meant to go
together. The fact that they work well with each other isn't a bad thing,
it's a good thing.

No one is saying Java is perfect, but the rules of the language are clear.
Rant all you like, the fact is that if you want for ( String s : coll ) to
work, the collection must be declared with the base type <String>. It's a
shame if you don't like it, but sometimes reality bites.
 
P

Patricia Shanahan

Dave said:
OK, I finally caught up to Java 1.5/6. (Delay had to do with available
tools and my intolerance for Eclipse, but I've reconciled myself to it
now).

Question: Eclipse gives me a compiler error for:

List list = new ArrayList();
for (String s : list)
System.out.println(s);

You are required to make the variable match the base type of the
collection or array in this case Object, but you can do what you like
with each value:

for (Object o : list){
String s = (String)o;
...
}

Patricia
 
C

Chronic Philharmonic

Patricia Shanahan said:
You are required to make the variable match the base type of the
collection or array in this case Object, but you can do what you like
with each value:

for (Object o : list){
String s = (String)o;
...
}

Or, you could say

for (Object o : list){
String s = o.toString();
...
}

since all objects support toString, including um, Strings. Of course, this
only works for Strings, but Strings are pretty common in the wild.
 
P

Patricia Shanahan

Chronic said:
Or, you could say

for (Object o : list){
String s = o.toString();
...
}

since all objects support toString, including um, Strings. Of course, this
only works for Strings, but Strings are pretty common in the wild.

The downside of that approach is that if something has gone wrong, and
the List accidentally contains an Integer, there will be no exception
and the program will go on running, possibly with nonsense data.

I would only do it if I really meant that it was OK for any reference
type to be in the List and I just wanted to work with its toString
representation.

Patricia
 
C

Chronic Philharmonic

Patricia Shanahan said:
The downside of that approach is that if something has gone wrong, and
the List accidentally contains an Integer, there will be no exception
and the program will go on running, possibly with nonsense data.

Fortunately, generics make this approach and the need for typecasting mostly
obsolete.
I would only do it if I really meant that it was OK for any reference
type to be in the List and I just wanted to work with its toString
representation.

I tend to do this when I just want to enumerate the contents of a list and
print a description to System.out or a logger.
 
C

crazzybugger

OK, I finally caught up to Java 1.5/6. (Delay had to do with available
tools and my intolerance for Eclipse, but I've reconciled myself to it now).

Question: Eclipse gives me a compiler error for:

List list = new ArrayList();
for (String s : list)
System.out.println(s);

It says "Type mismatch: Cannot convert from element type Object to
String". It's fine if I change decl of list to List<String>, or coerce
list to List<String> in the loop decl;

Is this an *actual* compiler error in the Java spec? Is Java actually
that hissy about the for-each variable? The compiler doesn't mind the
code below, even though it causes a runtime type-cast error. So why
can't it insert the cast to String in the loop setup? Am I missing
something?

Dave

List ar = new ArrayList();
ar.add(1);
List<String> list = ar;
for (String s : list)
Utils.log(s);

It is true that the compiler does not mind when you code
List<String> list=ar even though ar may contain non String objects. It
does so because, you are knowingly doing it and telling the compiler,
"See compiler, I am sure this list contains only String items and if
any error occurs I will manage it". However, when you try
List ar=new ArrayList()l
for(String s: ar)
Utils.log(s);
The compiler wont make any stupid assumptions, it wont cast Object
into a String since its not the kind of job compiler will do
implicitly!!! Compiler is not an AI machine and second, it does what
you tell it to do. Also generics are just compile time features, the
information is not present in the run time. So stop complaining and
start thinking.
 
R

rossum

We don't expect them to be imprecise. We expect them to do things
automatically for us, rather than make us do them. That's what
compilers are for.
We expect compilers to conform to the language specification, which
the Java compiler does.
In this case, the compiler has all the knowledge it
needs right there in the code to insert the cast *itself*. It has the
declaration of the iteration variable as String. It can see that the
list variable is a raw-type list:

for (String s : (List<String>)l) ...

It's the same mentality that forces us to write:

Foo f = (Foo)..expression..

It sees that f is a Foo. It doesn't know that expression is a Foo. So
it can see that a typecast is needed, and even tells you so. But it
makes YOU type it in. What sense is there in that? It should at most
be a compiler warning, not an error.
Java is a strongly typed language. If you do not like the effects of
strong typing then don't use a strongly typed language, use Python or
whatever instead.

rossum
 
B

Bent C Dalager

It is true that the compiler does not mind when you code
List<String> list=ar even though ar may contain non String objects. It
does so because, you are knowingly doing it and telling the compiler,
"See compiler, I am sure this list contains only String items and if
any error occurs I will manage it".

I don't see that you are saying this in the above code. What you are
saying is "As far as I recall, 'ar' is actually a List<String> so this
should work a treat" - much as if you had written
Object o = ...;
String text = o;

The compiler doesn't accept the latter, why should it accept the
former? Shouldn't it /require/ you to write
List<String> list = (List<String>) ar;
in the same way that it requires
String text = (String) o;
?

(Not that the cast would do you any good though, since
List ar = new ArrayList<Integer>();
ar.add(1);
List<String> list = (List<String>) ar;
for (String s : list)
System.out.println(s);

doesn't actually barf on the cast, but on the for statement. This
might be the reason I suppose - Java doesn't require the explicit cast
because it's unable to verify it anyway. This doesn't really improve
the situation in my eyes. But we may just be rehashing old complaints
about the generics system here.)

Cheers,
Bent D
 
R

Roedy Green

List list = new ArrayList();
for (String s : list)
System.out.println(s);

It says "Type mismatch: Cannot convert from element type Object to
String".

It is indeed a type mismatch. By avoiding declaring the type of
ArrayList with a generic, the type is the default Object.

So you to use the for:each syntax, you would have to say:

for ( Object o: list )
{
System.out.println( o );
}

or
System.out.println( o.toString() );

or
System.out.println( (String) o );

But the most idiomatic and no-run-time-overhead way is to use the ugly
<String> Syntax to declare:

List<String> list = new ArrayList<String>();
for ( String s : list )
{
System.out.println( s );
}
 
M

Mike Schilling

Dave said:
Yes, "hissy".


We don't expect them to be imprecise. We expect them to do things
automatically for us, rather than make us do them.

But Java doesn't insert casts automatically[1], and never has.

Object o;
String s = o;

won't compile. Why would you expect different behavior in a for loop?

1. Except to the extent that generics are implemented by doing so.
 
D

Daniel Pitts

Chronic said:
Fortunately, generics make this approach and the need for typecasting mostly
obsolete.


I tend to do this when I just want to enumerate the contents of a list and
print a description to System.out or a logger.
If thats the case, you'll get more NPEs than by using String.valueOf(o);
 
D

Daniel Pitts

Roedy said:
It is indeed a type mismatch. By avoiding declaring the type of
ArrayList with a generic, the type is the default Object.

Actually, there is a difference between declaring things with List<?>,
List, and List<Object>
Where the generic type appears, they all look like Objects, but

This whole thread is probably best answered by Sun's Generics Tutorial
on how to deal with legacy code.
<http://java.sun.com/docs/books/tutorial/extra/generics/legacy.html>

Hope this helps,
Daniel.
 
C

Chronic Philharmonic

Daniel Pitts said:
If thats the case, you'll get more NPEs than by using String.valueOf(o);

All engineering is a compromise. For some things casting is better. For
others, using o.toString polymorphism is better. In a vacuum, I cannot say
which would be better. It depends on the design, how the collection is being
used, whether you expect nulls in the collection, whether you control the
things you put in the collection, etc. etc. etc. The o.toString pattern is
one of many that I keep in my arsenal. I find it useful at times.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top