JDK-8244074 : Using newInstance constructor breaks type safety
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 8u251
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: os_x
  • CPU: x86
  • Submitted: 2020-04-28
  • Updated: 2020-04-29
  • Resolved: 2020-04-29
Related Reports
Duplicate :  
Description
A DESCRIPTION OF THE PROBLEM :
Using the newInstance constructor it is possible to construct a map with keys that do not extend the declared key type.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Full source code provided.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
newInstance should have thrown a runtime exception since the input types do not match the declared types. With the given source code ideally "Exception was caught" should be printed.
ACTUAL -
newInstance creates a map with keys whose type does not extend the declared key type of the map. With the given source code we see a map of <Dog, String> contain a key of type Cat.

---------- BEGIN SOURCE ----------
import java.util.HashMap;
import java.util.Map;

public class Main {

    public static void main(String[] args) {
        final Map<Animal, String> animalToName = new HashMap<>();
        final Cat cat = new Cat( 1, "fish" );
        final Dog dog = new Dog( 2, 10 );
        animalToName.put( cat, "catName" );
        animalToName.put( dog, "dogName" );

        try {
            final DogNames dogNames = (DogNames) DogNames.class.getConstructors()[0].newInstance( animalToName );
            System.out.println( "No Exception caught" );
            System.out.println( dogNames.getDogToName() );
        } catch( final Exception e ) {
            //Ideally an exception would be thrown here and this statement would be printed, however that is not the case.
            System.out.println( "Exception was caught" );
        }
    }

    private static class DogNames {

        private final Map<Dog, String> dogToName;

        public DogNames( final Map<Dog, String> dogToName ) {
            this.dogToName = dogToName;
        }

        public Map<Dog, String> getDogToName() {
            return this.dogToName;
        }

    }

    private static class Dog extends Animal {

        private final int woofLoudness;

        public Dog( final int weight, final int woofLoudness ) {
            super( weight );
            this.woofLoudness = woofLoudness;
        }

        @Override
        public String toString() {
            return String.format( "Dog with weight: %d, woof loudness: %d", this.weight, this.woofLoudness );
        }

    }

    private static class Cat extends Animal {

        private final String favoriteFood;

        public Cat( final int weight, final String favoriteFood ) {
            super( weight );
            this.favoriteFood = favoriteFood;
        }

        @Override
        public String toString() {
            return String.format( "Cat with weight: %d, favorite food: %s", this.weight, this.favoriteFood );
        }

    }

    private static class Animal {

        protected final int weight;

        public Animal( final int weight ) {
            this.weight = weight;
        }

    }

}

---------- END SOURCE ----------

FREQUENCY : always



Comments
If instead of the reflective constructor call final DogNames dogNames = (DogNames) DogNames.class.getConstructors()[0].newInstance( animalToName ); one instead uses the direct call final DogNames dogNames = new DogNames( animalToName ); the expected compile-time error is reported: Main.java:16: error: incompatible types: Map<Animal,String> cannot be converted to Map<Dog,String> final DogNames dogNames = new DogNames( animalToName ); ^ Generics in Java are erased, not reified, so the same type information is not available for runtime reflection. Closing as a dupe of a reification issue.
29-04-2020

Issue is reproduced, "No Exception caught" is observed everytime. jdk 8u251- Fail jdk 14.0.1 - Fail jdk 15ea18 - Fail Moving to Dev team for further evaluation.
29-04-2020