How to refer to a class when both simple and fully-qualified names clash
Asked Answered
J

4

16

Consider the following pathological example:

class Ideone {
  static class ArrayList<T> {
    ArrayList() {
      System.out.println("!!");
    }
  }

  static class java {
    static class util {
      static class ArrayList<T> {
        ArrayList() {
          System.out.println("Here");
        }
      }
    }
  }

  public static void main(String[] args) {
    new ArrayList<>();
    new java.util.ArrayList<>();
    // Can I refer to the "usual" java.util.ArrayList?
  }
}

The two instances created in the constructor are of the nested classes.

But how might I refer to the java.util.ArrayList that we all know and love in the same class? We can't import it, and we can't use the fully-qualified name, as the nested class symbols would be used instead.

What can we do in this case? (Other than the obvious - stop using such wantonly evil names for the nested classes).

Jaimiejain answered 31/5, 2018 at 22:39 Comment(6)
Do you have a reason to believe there's some solution?Lanna
@Lanna not especially. I'm just thinking up bizarre corner cases for imports, whilst working on some Error Prone checks.Jaimiejain
I'd love to see one of the language architects answer this question.Crashing
That problem can't happen if you follow standard Java Naming Conventions, i.e. package names in all lowercase, and class names in mixed case. Naming conventions exist for a reason.Duffey
@Duffey I agree; but I am asking about what is permitted by the language, not what is used by convention.Jaimiejain
@Lanna in C# there is a solution. You can specify global:: prefix to reference an original Java class. I.e. new global::java.util.ArrayList<>();Rosierosily
B
3

You can no longer directly reference java.util.ArrayList if you've done the 2 things you've done:

  1. Hide the simple name ArrayList with a static nested class in scope.
  2. Hide the fully qualified name java.util.ArrayList with a class ArrayList nested within class util, nested within nested class java.

You can't even "split" the import in an attempt to use a "partially qualified" import.

import java.*;

...

// This doesn't work!
new util.ArrayList<>();

You can import java.*;, but that is worthless; no classes are defined in the java package directly.

However, you can reference the class java.util.ArrayList indirectly because it's not final. Outside the scope of the class Ideone, declare a subclass with a different name.

class AnArrayList<T> extends java.util.ArrayList<T> {}

Then you can refer to that class and program to the interface:

List<Integer> al = new AnArrayList<>();  // won't print !! or Here
Brigand answered 31/5, 2018 at 23:11 Comment(3)
Of course, you could get an actual java.util.ArrayList by using something like Stream.of().collect(Collectors.toList()).Jaimiejain
@AndyTurner That sounds like you should answer your own question. I looked up the source code for Collectors.toList, and it uses ArrayList::new, confirming your claim.Brigand
maybe. It's not really an answer to the question I'm intending to ask, which is can you refer to the type, not how do you get an instance of the type.Jaimiejain
C
3

I'm going to go out on a limb and say that it's not possible to refer to it from within Ideone. The only solution that I see is to create another class Idetwo (which is a nestmate of Ideone (i.e. it resides in the same file)) and return a java.util.ArrayList from there for use in Ideone:

import java.util.ArrayList;

class Ideone {
    ...
}

class Idetwo {
    static ArrayList<Integer> getList() {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        return list;
    }
}

And you'd just change your Ideone's main method to something like:

public static void main(String[] args) throws Exception {
    new Ideone.ArrayList<>();
    new Ideone.java.util.ArrayList<>();
    Idetwo.getList().forEach(System.out::println);
}

Output:

!!
Here
1
2
3

Note: With this method, importing java.util.ArrayList will work fine. However, if you were to call List<Integer> list = Idetwo.getList(); inside Ideone's main method, you need to import java.util.*, as individual imports will not work (interestingly).

Crashing answered 31/5, 2018 at 23:9 Comment(2)
Nice try, but I said "in the same class", not "in the same compilation unit" ;)Jaimiejain
@AndyTurner Darn, missed that ;)Crashing
B
3

You can no longer directly reference java.util.ArrayList if you've done the 2 things you've done:

  1. Hide the simple name ArrayList with a static nested class in scope.
  2. Hide the fully qualified name java.util.ArrayList with a class ArrayList nested within class util, nested within nested class java.

You can't even "split" the import in an attempt to use a "partially qualified" import.

import java.*;

...

// This doesn't work!
new util.ArrayList<>();

You can import java.*;, but that is worthless; no classes are defined in the java package directly.

However, you can reference the class java.util.ArrayList indirectly because it's not final. Outside the scope of the class Ideone, declare a subclass with a different name.

class AnArrayList<T> extends java.util.ArrayList<T> {}

Then you can refer to that class and program to the interface:

List<Integer> al = new AnArrayList<>();  // won't print !! or Here
Brigand answered 31/5, 2018 at 23:11 Comment(3)
Of course, you could get an actual java.util.ArrayList by using something like Stream.of().collect(Collectors.toList()).Jaimiejain
@AndyTurner That sounds like you should answer your own question. I looked up the source code for Collectors.toList, and it uses ArrayList::new, confirming your claim.Brigand
maybe. It's not really an answer to the question I'm intending to ask, which is can you refer to the type, not how do you get an instance of the type.Jaimiejain
L
2

Have you tried

Class cls = ClassLoader.getSystemClassLoader().loadClass("java.util.ArrayList");
List arrayList = cls.newInstance();

It's been a long time since I've thought about classloaders, but IIRC .loadClass() will preferentially try to load from the most fundamental classloader, and the real java.util package should be provided by the bootstrap classloader, which gives it higher precedence than anything you define in your application.

Lcm answered 31/5, 2018 at 23:0 Comment(1)
This should work if you call loadClass instead of findClass, which is protected.Brigand
T
1

That's why packages start with lower case letters, and types with upper case letters, since the very first edition of the official code conventions for java. I don't think I have ever seen java code break this convention.

But you can refer to the inner type from the outside using it's fully qualified name:

com.whatever.bad.project.SomeClass.java.util.ArrayList

When you have to refer to the outer type from inside, you can probably change that source file to comply with Java naming guidelines.

Transmundane answered 31/5, 2018 at 23:10 Comment(1)
I refer you to the my reply to @Andreas' comment: "I am asking about what is permitted by the language, not what is used by convention".Jaimiejain

© 2022 - 2024 — McMap. All rights reserved.