50

If I have an instance of Class, is there a way of obtaining a Class instance for its array type? What I'm essentially asking for is the equivalent of a method getArrayType which is the inverse of the getComponentType() method, such that:

array.getClass().getComponentType().getArrayType() == array.getClass()
Tom Castle
  • 2,198
  • 1
  • 16
  • 20

5 Answers5

62

One thing that comes to mind is:

java.lang.reflect.Array.newInstance(componentType, 0).getClass();

But it creates an unnecessary instance.

Btw, this appears to work:

Class clazz = Class.forName("[L" + componentType.getName() + ";");

Here is test. It prints true:

Integer[] ar = new Integer[1];
Class componentType = ar.getClass().getComponentType();
Class clazz = Class.forName("[L" + componentType.getName() + ";");

System.out.println(clazz == ar.getClass());

The documentation of Class#getName() defines strictly the format of array class names:

If this class object represents a class of arrays, then the internal form of the name consists of the name of the element type preceded by one or more '[' characters representing the depth of the array nesting.

The Class.forName(..) approach won't directly work for primitives though - for them you'd have to create a mapping between the name (int) and the array shorthand - (I)

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
  • 1
    The first version (using `Array.newInstance(...).getClass()`) *does* work for primitives. – finnw Feb 04 '11 at 18:12
  • This is very useful, thanks. For my purposes, I don't need to handle primitives so either approach is useable. – Tom Castle Feb 04 '11 at 18:24
  • 6
    The `Class.forName()` approach also produces temporary objects for the string concatenation (e.g. new StringBuilder().append("[L").append(componentType.getName()).append(";").toString()). So I imagine the first approach produces fewer allocations, even with the unnecessary one. – ɲeuroburɳ May 23 '13 at 20:11
  • 3
    You may want to use Class.forName("[L" + componentType.getName() + ";", false, componentType.getClassLoader()) – Alice Purcell Jul 07 '14 at 11:26
  • Yes, the class loader is important here. – Paŭlo Ebermann Sep 15 '16 at 16:15
  • Can anyone here explain me what does this mean? Class arrayClass = (Class) Class.forName("[L" + anyClazz.getName() + ";"); – FearX Jul 29 '19 at 12:30
  • 3
    As @ɲeuroburɳ said, the `Class.forName(…)` approach creates even more temporary objects, is more expensive (the longer the class name, the more expensive), further, the zero-sized array has much better chances of getting removed by the optimizer, but even worse, the `Class.forName(…)` approach not only doesn’t work for primitive types, it also doesn’t work for array types (when trying to get a multi-dimensional array type). While `Array.newInstance(componentType, 0).getClass()` just does the job. With Java 12+, you can just use `componentType.arrayType()`; guess what it does under the hood… – Holger Mar 29 '21 at 14:42
16

Actually due to ClassLoader, primitives and multi-dimensional arrays, the answer is a little more complex:

public static Class<?> getArrayClass(Class<?> componentType) throws ClassNotFoundException{
    ClassLoader classLoader = componentType.getClassLoader();
    String name;
    if(componentType.isArray()){
        // just add a leading "["
        name = "["+componentType.getName();
    }else if(componentType == boolean.class){
        name = "[Z";
    }else if(componentType == byte.class){
        name = "[B";
    }else if(componentType == char.class){
        name = "[C";
    }else if(componentType == double.class){
        name = "[D";
    }else if(componentType == float.class){
        name = "[F";
    }else if(componentType == int.class){
        name = "[I";
    }else if(componentType == long.class){
        name = "[J";
    }else if(componentType == short.class){
        name = "[S";
    }else{
        // must be an object non-array class
        name = "[L"+componentType.getName()+";";
    }
    return classLoader != null ? classLoader.loadClass(name) : Class.forName(name);
}
FroMage
  • 5,038
  • 2
  • 20
  • 13
  • 4
    In the last line, you could also use the forName method with class loader parameter (which also works for `null`, thus avoiding the case distinction). – Paŭlo Ebermann Sep 16 '16 at 09:53
  • The last line did not work for me, but forName with the classloader as parameter did. – robinr Jan 15 '21 at 11:31
9

You can do the following

array.getClass() == 
    Array.newInstance(array.getClass().getComponentType(), 0).getClass()

Usually, you don't need to know the type, you just want to create the array.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
4

Java 12 introduced arrayType()

String.class.arrayType() == String[].class;
Aleksandr Dubinsky
  • 22,436
  • 15
  • 82
  • 99
2

Another possible refactoring is to use a generic superclass and pass in two class objects to the constructor.

protected AbstractMetaProperty(Class<T> valueClass, Class<T[]> valueArrayClass) {
  this.valueClass = valueClass;
  this.valueArrayClass = valueArrayClass;
}

Then in subclasses:

public IntegerClass() {
  super(Integer.class, Integer[].class);
}

Then in the abstract class you can use valueClass.cast(x), valueArrayClass.isInstance(x) etc.