0

I am currently attempting to write a method which returns a new array of a generic type filled with objects of random values but I am struggling with the creation of the Objects.

Let's say I have a class Rectangle and a class Cricle which both can only be initialised by their constructor and lack an empty constructor. Is it possible to access the constructor of those example classes when working with a generic method?

Rectangle.java

public class Rectangle
{
  private double width;
  private double height;
  private double area;

  public Rectangle( double width, double height )
  {
    this.width = width;
    this.height = height;
    this.area = width * height;
  }
  // Getter....
}

Circle.java

public class Circle
{
  private double area;
  private double radius;

  public Circle( double radius )
  {
    this.radius = radius;
    this.area = Math.PI * Math.pow( radius, 2 );
  }
  // Getter....
}

What I hoped could work out somehow:

ArrayFactory.java

public class ArrayFactory<T>
{

  @SuppressWarnings ( "unchecked")
  public T[] getArray( int size, int min, int max )
  {
    Random ran = new Random();
    double var1 = (ran.nextDouble() * max) - min;
    double var2 = (ran.nextDouble() * max) - min;

    T[] temp = (T[]) new Object[size];

    for ( int i = 0; i < t.length; i++ )
    {
      // this does obviously not work 
      // because the constructor of java.lang.Object takes no arguments
      temp[ i ] = (T) new Object(var1,var2);
    }
    return temp;
  }
}
GregT
  • 1,039
  • 4
  • 14
  • 34
L.Spillner
  • 1,772
  • 10
  • 19
  • Use a factory class – Maurice Perry Apr 06 '18 at 12:24
  • @MauricePerry But how would I decide which Factory to use? It would depend on the type `T`, wouldn't it? – L.Spillner Apr 06 '18 at 12:25
  • Yes it would... – Maurice Perry Apr 06 '18 at 12:29
  • 1
    `T` exists only at compilation time. At runtime it is erased to `Object`. If you want to preserve information about `T` at runtime you may need to have additional argument of type `Class`. For instance it is used in reflection via `Arrays.newInstance(String.class, 5)` which creates `new String[5]` instead of `new Object[5]`. Such class literal can be used to access available constructors (read more at [Can I use Class.newInstance() with constructor arguments?](https://stackoverflow.com/q/234600)) – Pshemo Apr 06 '18 at 12:33
  • @Pshemo thank you for the quick explanation. I knew about `Class.getConstructor(...).newInstance(...)` but I thought/hoped that I could somehow retriev this information from the generic. But as you pointed out the information gets erased. – L.Spillner Apr 06 '18 at 12:37

2 Answers2

2

This is hopeless because neither Rectangle nor Circle share a common base class or interface. Generics cannot help you here.

This looks like inheritance 101 Shape example. (Animal is the other common one.)

You can do this:

public interface Shape {
    double area();
    double perimeter();
}

public class Rectangle implements Shape {
    private double width;
    private double height;

    public Rectangle(double w, double h) {
        this.width = w;
        this.height = h;
    }

    public double area() { return w*h; }
    public double perimeter() { return 2.0*(w+h); }
}

public class ShapeFactory() { 
    public Shape createShape(double ... args) {
        if (args.length == 1) {
            return new Circle(args[0]);
        } else if (args.length == 2) { 
            return new Rectangle(args[0], args[1]);
        } else {
            throw new IllegalArgumentException("Wrong number of arguments");
        }
    }
}

This factory will "work", but it's limited. You can't distinguish between Shapes that have two ctor arguments (e.g. Rectangle, Square, Triangle, Rhombus, 3D cylinder, etc.) For those you'll have to pass a Class, in addition to the variable number of dimension parameter values, and use instanceOf.

Using instanceOf is usually a dead giveaway that your use of polymorphism is broken, but I would say it's acceptable in this case, as long as it's isolated to the factory method.

duffymo
  • 305,152
  • 44
  • 369
  • 561
1

You could use a factory class implementing:

public interface ArrayFactory<T> {
    public T newElement();
    public T[] newArray(int size);
}

example:

class RectangleArrayFactory implements ArrayFactory<Rectangle> {
    private final Random ran = new Random();
    private final int min;
    private final int max;

    public RectangleArrayFactory(int min, int max) {
        this.min = min;
        this.max = max;
    }

    @Override
    public Rectangle newElement() {
        double var1 = (ran.nextDouble() * max) - min;
        double var2 = (ran.nextDouble() * max) - min;
        return new Rectangle(var1, var2);
    }

    @Override
    public Rectangle[] newArray(int size) {
        return new Rectangle[size];
    }
}

To create and fill an array, you could do:

public <T> T[] newArray(ArrayFactory<T> fac, int size) {
    T[] result = fac.newArray(size);
    for (int i = 0; i < size; ++i) {
        result[i] = fac.newElement();
    }
    return result;
}
Maurice Perry
  • 9,261
  • 2
  • 12
  • 24