Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.java.programmer > #39945 > unrolled thread

Why does Java complain if you cast a Map but not an Object?

Started by"e.d.pro...@gmail.com" <e.d.programmer@gmail.com>
First post2023-04-19 08:16 -0700
Last post2023-04-23 14:30 +0200
Articles 5 — 3 participants

Back to article view | Back to comp.lang.java.programmer


Contents

  Why does Java complain if you cast a Map but not an Object? "e.d.pro...@gmail.com" <e.d.programmer@gmail.com> - 2023-04-19 08:16 -0700
    Re: Why does Java complain if you cast a Map but not an Object? Arne Vajhøj <arne@vajhoej.dk> - 2023-04-19 13:43 -0400
      Re: Why does Java complain if you cast a Map but not an Object? "e.d.pro...@gmail.com" <e.d.programmer@gmail.com> - 2023-04-19 11:19 -0700
        Re: Why does Java complain if you cast a Map but not an Object? Arne Vajhøj <arne@vajhoej.dk> - 2023-04-19 15:23 -0400
          Re: Why does Java complain if you cast a Map but not an Object? Michael Jung <miju@golem.phantasia.org> - 2023-04-23 14:30 +0200

#39945 — Why does Java complain if you cast a Map but not an Object?

From"e.d.pro...@gmail.com" <e.d.programmer@gmail.com>
Date2023-04-19 08:16 -0700
SubjectWhy does Java complain if you cast a Map but not an Object?
Message-ID<632f38ec-674f-44ad-b8d4-53aa32534c09n@googlegroups.com>
@SuppressWarnings("unchecked")
public static Map<MyObject,MyObject> testObjectUnchecked(Object in) {
  return (Map<MyObject,MyObject>) in; // warning
}
public static Map<MyObject,MyObject> testObjectChecked(Object in) {
  return testMap(in); // no warning
}
public static Map<MyObject,MyObject> testMap(Object in) {
  if (in instanceof Map) {
    Map<?,?> m1 = (Map<?,?>)in;
    Map<MyObject,MyObject> m2 = new HashMap<>();
    for (Entry<?,?> e : m1.entrySet()) {
      m2.put((MyObject)e.getKey(),(MyObject)e.getValue()); // no warning
    }
    return m2;
  }
  return Collections.<MyObject,MyObject>emptyMap();
}

[toc] | [next] | [standalone]


#39946

FromArne Vajhøj <arne@vajhoej.dk>
Date2023-04-19 13:43 -0400
Message-ID<u1p97t$4nqi$1@dont-email.me>
In reply to#39945
On 4/19/2023 11:16 AM, e.d.pro...@gmail.com wrote:
> @SuppressWarnings("unchecked")
> public static Map<MyObject,MyObject> testObjectUnchecked(Object in) {
>    return (Map<MyObject,MyObject>) in; // warning
> }
> public static Map<MyObject,MyObject> testObjectChecked(Object in) {
>    return testMap(in); // no warning
> }
> public static Map<MyObject,MyObject> testMap(Object in) {
>    if (in instanceof Map) {
>      Map<?,?> m1 = (Map<?,?>)in;
>      Map<MyObject,MyObject> m2 = new HashMap<>();
>      for (Entry<?,?> e : m1.entrySet()) {
>        m2.put((MyObject)e.getKey(),(MyObject)e.getValue()); // no warning
>      }
>      return m2;
>    }
>    return Collections.<MyObject,MyObject>emptyMap();
> }

I think it makes sense.

(MyObject)e.getKey() and (MyObject)e.getValue() will work
fine if they are a MyObject and throw a ClassCastException
if they are not a MyObject.

(Map<MyObject,MyObject>)in is worse:
- it will work if it is a Map<MyObject,MyObject>
- it will throw a ClassCastException it is not a Map
- it will seemingly work but likely cause a ClassCastException
   at some later point if it is a Map<X,Y>  but not a
   Map<MyObject,MyObject>

Try this example using List instead of Map:

import java.util.ArrayList;
import java.util.List;

public class ToWarnOrNotToWarn {
     private static String test1(Object o) {
         try {
             return (String)o;
         } catch(ClassCastException ex) {
             System.out.println(o.getClass().getName() + " is not a 
String");
             return null;
         }
     }
     private static List<String> test2(Object o) {
         try {
             return (List<String>)o;
         } catch(ClassCastException ex) {
             System.out.println(o.getClass().getName() + " is not a 
List<String>");
             return null;
         }
     }
     public static void main(String[] args) {
         Object o1 = "ABC";
         System.out.println(test1(o1));
         Object o2 = 123;
         System.out.println(test1(o2));
         System.out.println(test2(true));
         List<String> a1 = new ArrayList<String>();
         a1.add("ABC");
         System.out.println(test2(a1));
         System.out.println(test2(a1).get(0).substring(0, 1));
         List<Integer> a2 = new ArrayList<Integer>();
         a2.add(123);
         System.out.println(test2(a2));
         System.out.println(test2(a2).get(0).substring(0, 1));
     }
}

result:

ABC
java.lang.Integer is not a String
null
java.lang.Boolean is not a List<String>
null
[ABC]
A
[123]
Exception in thread "main" java.lang.ClassCastException: 
java.lang.Integer cannot be cast to java.lang.String
	at ToWarnOrNotToWarn.main(ToWarnOrNotToWarn.java:36)

the warning on test2 is justified because the cast in test2 cause
a ClassCastException in main.

Arne

[toc] | [prev] | [next] | [standalone]


#39947

From"e.d.pro...@gmail.com" <e.d.programmer@gmail.com>
Date2023-04-19 11:19 -0700
Message-ID<4ab3b92b-0570-439a-9022-5498dc95e9d3n@googlegroups.com>
In reply to#39946
On Wednesday, April 19, 2023 at 1:43:39 PM UTC-4, Arne Vajhøj wrote:

> System.out.println(test2(a2)); 
> System.out.println(test2(a2).get(0).substring(0, 1)); 

So no warnings about casting one thing to another possibly compatible thing (Object to String) because the cast immediately throws exception if Object is not a String, but using containers (List, Map) it warns because the cast hides the error; it actually allows incompatible casting (List<Integer> to List<String>) and can print the resulting object, and crashes when you try to return an element into a typed variable. Of course we don't need to worry about immediate casting exceptions because it's Runtime.
If you take a List<Integer> and cast it to a List<String>, you can call get(idx) and return the value into a variable declared as String and crash (but it compiles). If you return the value into a variable declared as Object, it works, so when you called .get(0) it didn't care that it wasn't a String until it tried to process the .substring. As long as you treat that result as Object, it's fine.
System.out.println(test2(a2).get(0).getClass()); // List<String> doesn't care that element is Integer

[toc] | [prev] | [next] | [standalone]


#39948

FromArne Vajhøj <arne@vajhoej.dk>
Date2023-04-19 15:23 -0400
Message-ID<u1pf3r$5ksa$1@dont-email.me>
In reply to#39947
On 4/19/2023 2:19 PM, e.d.pro...@gmail.com wrote:
> On Wednesday, April 19, 2023 at 1:43:39 PM UTC-4, Arne Vajhøj wrote:
> 
>> System.out.println(test2(a2)); 
>> System.out.println(test2(a2).get(0).substring(0, 1));
> 
> So no warnings about casting one thing to another possibly compatible
> thing (Object to String) because the cast immediately throws
> exception if Object is not a String, but using containers (List, Map)
> it warns because the cast hides the error; it actually allows
> incompatible casting (List<Integer> to List<String>) and can print
> the resulting object, and crashes when you try to return an element
> into a typed variable. Of course we don't need to worry about
> immediate casting exceptions because it's Runtime. If you take a
> List<Integer> and cast it to a List<String>, you can call get(idx)
> and return the value into a variable declared as String and crash
> (but it compiles). If you return the value into a variable declared
> as Object, it works, so when you called .get(0) it didn't care that
> it wasn't a String until it tried to process the .substring. As long
> as you treat that result as Object, it's fine. 
> System.out.println(test2(a2).get(0).getClass()); // List<String>
> doesn't care that element is Integer

Yes. That is how I see it all work.

Arne

[toc] | [prev] | [next] | [standalone]


#39950

FromMichael Jung <miju@golem.phantasia.org>
Date2023-04-23 14:30 +0200
Message-ID<87r0saaihs.fsf@golem.phantasia.org>
In reply to#39948
Arne Vajhøj <arne@vajhoej.dk> writes:
> On 4/19/2023 2:19 PM, e.d.pro...@gmail.com wrote:
>> On Wednesday, April 19, 2023 at 1:43:39 PM UTC-4, Arne Vajhøj wrote:
>> So no warnings about casting one thing to another possibly compatible
>> thing (Object to String) because the cast immediately throws
>> exception if Object is not a String, but using containers (List, Map)
>> it warns because the cast hides the error; it actually allows
>> incompatible casting (List<Integer> to List<String>) and can print
>> the resulting object, and crashes when you try to return an element
>> into a typed variable. Of course we don't need to worry about
>> immediate casting exceptions because it's Runtime. If you take a
>> List<Integer> and cast it to a List<String>, you can call get(idx)
>> and return the value into a variable declared as String and crash
>> (but it compiles). If you return the value into a variable declared
>> as Object, it works, so when you called .get(0) it didn't care that
>> it wasn't a String until it tried to process the .substring. As long
>> as you treat that result as Object, it's
>> fine. System.out.println(test2(a2).get(0).getClass()); //
>> List<String>
>> doesn't care that element is Integer
> Yes. That is how I see it all work.

Think of "Object" is the supertype of all objects and "?" as the
(generic) subtype of all objects. When you cast Object to Map that is
worth a warning because it is a downcast. Casting an arbitrary Map to
Map<?,?> is okay, because the Map *must* map some ? to (another) ?.
Later, when you cast ? to MyObject that is an upcast and that is okay.
The compiler apparently "loses" the reference that this specific
instance of ? could already have been bound to a superclass.

Maybe if you replace all "?" with "? extends Object" the compiler knows
that the subtype could be Object itself and it issues the warning?

It is not "because cast hides errors".

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.java.programmer


csiph-web