1

I know the title is weird, I had no idea what else to write. I'm relatively new to java so I apologize if this is something very basic. I've tried my best to explain the point through code. The question here is -

This compiles,

// WAY 1
Map<MyType, MyType> myMap = (Map) new MyMap();

This does not,

// WAY 2
Map<MyType, MyType> myMap2 = (Map<MyType, MyType>) new MyMap();

Firstly, why such behavior. And secondly, way 2 allows me to write my wanted code in one line as written in method WhatIWant, while way 1 does not, again as written in method WhatIWant. Can I write that code in one line? If yes, how. If not, why not.

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;

// A class not created by me but in a library so i cannot use generic types as i cannot change stuff here
class MyMap extends LinkedHashMap<Object, Object> {
}

// has methods which i need for processing on myMap
class MyType {
    int myMethod() {
        return -1;
    }
}

class Scratch {
    public static void main(final String[] args) throws Exception {
        // WAY 1: 
        // compiles 
        // WARNING : Unchecked assignment: 'java.util.Map' to 'java.util.Map<MyType, MyType>'
        Map<MyType, MyType> myMap = (Map) new MyMap();

        // WAY 2:
        // does not compile 
        // ERROR: Inconvertible types; cannot case 'MyMap' to 'java.util.Map<MyType, MyType>'  
        Map<MyType, MyType> myMap2 = (Map<MyType, MyType>) new MyMap();
    }

    public static void WhatIWant() {
        // to be able to write code below in one line
        Map<MyType, MyType> myMap = (Map) new MyMap();
        myMap.entrySet().stream()
                .collect(Collectors.toMap(
                        entry -> entry.getKey().toString(),
                        entry -> entry.getValue().myMethod()
                ));

        // I thought it would work if i used WAY 2 like
        ((Map<MyType, MyType>) new MyMap()).entrySet().stream()
                .collect(Collectors.toMap(
                        entry -> entry.getKey().toString(),
                        entry -> entry.getValue().myMethod()
                ));
        // but as you saw above in WAY 2, it does not compile, how can i do this in one line
    }
}
behzad
  • 801
  • 3
  • 15
  • 33
Anil Kumar
  • 127
  • 8
  • 1
    just a question.... why do you want to iterate over an empty map? if you have just created the new myMap object it will not have any element – TheOni Dec 30 '19 at 12:05
  • Why do you need the `MyMap` class. Why no use `LinkedHashMap` directly? – Eran Dec 30 '19 at 12:06
  • @TheOni It's just an example. These are not the actual classes. There's a library whose classes are of similar types. MyMap and MyType are library classes. – Anil Kumar Dec 30 '19 at 12:23
  • @Eran There's a library which has it's own type similar to MyMap. It has a method which i use which returns MyMap. Hence i can only decide how to use it, not make changes in existing library. – Anil Kumar Dec 30 '19 at 12:24

2 Answers2

2

If you want it in "one line" (aka 1 statement, 5 lines), double-cast it:

((Map<MyType, MyType>) (Map) new MyMap()).entrySet().stream()
        .collect(Collectors.toMap(
                entry -> entry.getKey().toString(),
                entry -> entry.getValue().myMethod()
        ));

The first cast uses a raw Map, so the <Object, Object> generic type parameters are discarded. This is valid for backwards compatibility, but does generate a compiler warning saying you shouldn't do that:

Map is a raw type. References to generic type Map<K,V> should be parameterized

The second cast then applies a new set of generic type parameters. This is valid for backwards compatibility, but does generate a compiler warning saying that you're on your own, since the generic parameter types are not enforced:

Type safety: Unchecked cast from Map to Map<MyType,MyType>

The reason you can't cast directly is because Map<MyType,MyType> is not a subtype of Map<Object,Object>, i.e. it is an impossible cast in the Java type system. See Is List<Dog> a subclass of List<Animal>? Why are Java generics not implicitly polymorphic?

Andreas
  • 154,647
  • 11
  • 152
  • 247
0

you need a few changes in your class to workaround

// instead Object use your own type
    class MyMap extends LinkedHashMap<MyType, MyType> {
    }

now you can directly access/perform way 2 without casting :

Map<MyType, MyType> myMap2 = new MyMap();

and here as well:

new MyMap().entrySet().stream()
                .collect(Collectors.toMap(
                        entry -> entry.getKey().toString(),
                        entry -> entry.getValue().myMethod()
                ));
Navin Gelot
  • 1,264
  • 3
  • 13
  • 32
  • As i've written in my code. MyMap and MyType are library classes and i cannot touch them. All i can do is decide how to use them. I cannot make any changes to them. – Anil Kumar Dec 30 '19 at 12:22