1

Suppose I have classes Circle, Rectangle, and Triangle.

Based on input from a data file, I want to create the appropriate object. For instance, if the first line of shapes.dat is C.5 0 0, I will create a Circle object with radius 5. If the next line is R.5 3 0, I will create a Rectangle object with length 5 and width 3.

I know I could use basic if-else logic, but I was wondering whether there's a way to use the string as a means of instantiating a new object. Sort of like the exec() method in Python. Here's the code snippet describing what I want:

    Scanner file = new Scanner (new File("shapes.dat"));
    String s;       

    Map<Character,String> dict = new HashMap<Character,String>();
    dict.put('C', "Circle");
    dict.put('R', "Rectangle");
    dict.put('T', "Triangle");

    while (file.hasNextLine())
    {
        s = file.nextLine().trim();

        String name = dict.get(s.toCharArray()[0]);
        String data = s.split(".")[1];
        String code = name + " x = new " + name + "(data);";

        SYS.exec(code); //???

        ...

    }
actinidia
  • 236
  • 3
  • 17

4 Answers4

3

I'm not sure I understand correctly, seems weird no one else mentioned this yet:

Map<Character, ShapeFactory> dict = new HashMap<>();
dict.put('C', new CircleFactory());
dict.put('R', new RectangleFactory());
dict.put('T', new TriangleFactory());

...

ShapeFactory factory = dict.get(symbol);
Shape shape = factory.create(data);
Shadov
  • 5,421
  • 2
  • 19
  • 38
  • Same idea as yours but I took the time to explain why avoiding other alternatives. I would probably have not done it :) – davidxxx Jan 23 '18 at 15:44
2

You can make use of reflection to create instance dynamically.

String className = "com.shape.Triangle";
Class classDefinition = Class.forName(className);
Object obj = classDefinition.newInstance();

Or

Just use if-else to create instance of specific class.

Ravi
  • 30,829
  • 42
  • 119
  • 173
  • Is the way you named the Triangle class arbitrary? Why "com.shape.Triangle" as opposed to just "Triangle"? – actinidia Jan 23 '18 at 15:31
  • @TiwaAina Just to let you know, if it is reside inside some package, then you should use complete class name. – Ravi Jan 23 '18 at 15:32
  • @TiwaAina Read what the JavaDoc has to say about [`Class#forName`](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#forName-java.lang.String-). – Cardinal System Jan 23 '18 at 15:33
  • @CardinalSystem Oh I see, we need the fully qualified name of the class. – actinidia Jan 23 '18 at 15:34
1

Exec in Python executes code.
You can do the same thing with Java, for example with javassist.
You could read the data file, compile the statements and insert them in your own class.
But it seems overkill.

You could also use java reflection but it will produce a brittle and unclear code.

Instead if else if, I think that you should use abstraction and create a factory class by type of object.

It could look like :

Scanner file = new Scanner (new File("shapes.dat"));
String s;       

Map<Character, ShapeBuilder> dict = new HashMap<Character,String>();
dict.put('C', new CircleBuilder());
dict.put('R', new RectangleBuilder());
dict.put('T', new TriangleBuilder());

while (file.hasNextLine()){
    s = file.nextLine().trim();
    char shapeSymbol = ...; // computed from s
    ShapeBuilder builder = dict.get(shapeSymbol);
    Shape shape = builder.build(s);
}
davidxxx
  • 125,838
  • 23
  • 214
  • 215
1

You can actually use polymorphism to avoid if-else statements. So, you can create objects that actually do those two jobs you want, match a line and create a shape. So you could use something like the following code.

public class Program {

    public static void main() throws FileNotFoundException {
        Scanner file = new Scanner(new File("shapes.dat"));

        while (file.hasNextLine()) {
            String line = file.nextLine().trim();
            Shape shape = new Matches(
                new RectangleMatch(),
                new TriangleMatch(),
                new SquareMatch(),
                new CircleMatch()
            ).map(line);
        }
    }


    public interface ShapeMatch {
        boolean matches(String line);

        Shape shape(String line);
    }


    public static final class RectangleMatch implements ShapeMatch {
        @Override
        public boolean matches(String line) {
            return line.startsWith("R");
        }

        @Override
        public Shape shape(String line) {
            String[] dimensions = line.substring(2).split(" ");
            return new Rectangle(
                Integer.parseInt(dimensions[0]),
                Integer.parseInt(dimensions[1]),
                Integer.parseInt(dimensions[2])
            );
        }
    }


    public static final class CircleMatch implements ShapeMatch {
        @Override
        public boolean matches(String line) {
            return line.startsWith("C");
        }

        @Override
        public Shape shape(String line) {
            return new Circle(Integer.parseInt(line.substring(2, line.indexOf(" "))));
        }
    }


    public interface ShapeMapping {
        Shape map(String line);
    }

    public static final class Matches implements ShapeMapping {
        private final Iterable<ShapeMatch> matches;

        public Matches(ShapeMatch... matches) {
            this(Arrays.asList(matches));
        }

        public Matches(Iterable<ShapeMatch> matches) {
            this.matches = matches;
        }

        @Override
        public Shape map(String line) {
            for (ShapeMatch match : matches) {
                if (match.matches(line)) {
                    return match.shape(line);
                }
            }

            throw new RuntimeException("Invalid shape entry line.");
        }
    }
}