4

Most people understand the innate benefits that enum brings into a program verses the use of int or String. See here and here if you don't know. Anyway, I came across a problem that I wanted to solve that kind of is on the same playing field as using int or String to represent a constant instead of using an enum. This deals specifically with String.format(...).

With String.format, there seems to be a large opening for programmatic error that isn't found at compile-time. This can make fixing errors more complex and / or take longer.

This was the issue for me that I set out to fix (or hack a solution). I came close, but I am not close enough. For this problem, this is more certainly over-engineered. I understand that, but I just want to find a good compile-time solution to this, that provides the least amount of boiler-plate code.

I was writing some non-production code just to write code with the following rules.

  • Abstraction was key.
  • Readability was very important

  • Yet the simplest way to the above was preferred.

I am running on...

  • Java 7 / JDK 1.7
  • Android Studio 0.8.2

These are unsatisfactory

My Solution

My solution uses the same idea that enums do. You should use enum types any time you need to represent a fixed set of constants...data sets where you know all possible values at compile time(docs.oracle.com). The first argument in String.format seems to fit that bill. You know the whole string beforehand, and you can split it up into several parts (or just one), so it can be represented as a fixed set of "constants".

By the way, my project is a simple calculator that you probably seen online already - 2 input numbers, 1 result, and 4 buttons (+, -, ×, and ÷). I also have a second duplicate calculator that has only 1 input number, but everything else is the same

Enum - Expression.java & DogeExpression.java

public enum Expression implements IExpression {

    Number1     ("%s"),
    Operator    (" %s "),
    Number2     ("%s"),
    Result      (" = %s");

    protected String defaultFormat;
    protected String updatedString = "";

    private Expression(String format) { this.defaultFormat = format; }

    // I think implementing this in ever enum is a necessary evil. Could use a switch statement instead. But it would be nice to have a default update method that you could overload if needed. Just wish the variables could be hidden.
    public <T> boolean update(T value) { 

        String replaceValue
                = this.equals(Expression.Operator)
                ? value.toString()
                : Number.parse(value.toString()).toString();

        this.updatedString = this.defaultFormat.replace("%s", replaceValue);

        return true;

    }

}

...and...

public enum DogeExpression implements IExpression {

    Total   ("Wow. Such Calculation. %s");

    // Same general code as public enum Expression

}

Current Issue

IExpression.java - This is a HUGE issue. Without this fixed, my solution cannot work!!

public interface IExpression {

    public <T> boolean update(T Value);

    class Update {  // I cannot have static methods in interfaces in Java 7. Workaround

        public static String print() {

            String replacedString = "";

            // for (Expression expression : Expression.values()) {   // ISSUE!! Switch to this for Expression
            for (DogeExpression expression : DogeExpression.values()) {

                replacedString += expression.updatedString;

            }

            return replacedString;
        }

    }

}

So Why Is This An Issues

With IExpression.java, this had to hacked to work with Java 7. I feel that Java 8 would have played a lot nicer with me. However, the issue I am having is paramount to getting my current implementation working The issue is that IExpression does not know which enum to iterate through. So I have to comment / uncomment code to get it to work now.

How can I fix the above issue??

Community
  • 1
  • 1
Christopher Rucinski
  • 4,737
  • 2
  • 27
  • 58
  • I read twice your question and feel want to answer but I am lazy to copy so many code and try it out. Is it possible for you create a sample project just to illustrate your problem? Preferable maven project. – hutingung Jul 20 '14 at 11:29
  • Is this question different from [the one you posted a few days ago](http://stackoverflow.com/questions/24769455/java-string-format-compile-time-error-checking)? If so, how? – user2357112 Jul 20 '14 at 11:33
  • 1
    This approach is actually significantly *worse* than using `String.format`, because `Number1`, `Operator`, `Number2`, and `Result` are global state. You can't build two strings at once with this code, meaning reentrancy and threading are off the table. Furthermore, you need to write a whole new class with a ton of boilerplate for *every* format string. It doesn't even provide any additional type safety, and you can *still* screw up writing `%s` in the enums without the compiler noticing. – user2357112 Jul 20 '14 at 11:47
  • @user2357112 This one is more specific; however, I was wondering if I could have this question only focus on my major issue, and I could retrofit my previous question to deal with my other issue. I **think** this question could be answered correctly with one answer dealing with the major issue, and the other answer dealing with the other issue. I think my other question is dead as is – Christopher Rucinski Jul 20 '14 at 11:48
  • please post more issues if you find any. I am aware of the boilerplate code. I don't like it either as much. I was thinking of adding Types for each enum constant that `%s` should be, but I feel like that might just add more boilerplate code - unless it can go in `IExpression` somehow. As far as threading right now that was not a concern. This would be single-thread only for now. You have to start somewhere. Brighter minds could take another step later – Christopher Rucinski Jul 20 '14 at 11:56
  • 4
    I'm sorry to say this, but I think your design is fundamentally broken. When I try to fix the issues in this code, I get a horribly unwieldy reimplementation of string concatenation. There is literally no advantage over `num1 + " " + operator.getSign() + " " + num2 + " = " + total`. Any apparent reduction in complexity at the call site is due to that complexity being shifted to a non-reusable `Expression` class. None of it is any safer than string concatenation. Trying to add compile-time safety results in the whole thing becoming a wrapper around string concatenation. – user2357112 Jul 20 '14 at 12:18
  • Sure there is. Just not all the huge advantages that you might wish with a baked in type-safe, compile-time. I can only work with what I have. There might be ways to add safety into it. Maybe starting with `Number1 ("%s", Double.class),` and adding the needed code. By the way, my code still does not work. I know you don't like the implementation, but if my major issue can be solved I can test it more. Don't like my try. Please try another way...this is really bothering me – Christopher Rucinski Jul 20 '14 at 12:37
  • 7
    I am sorry but this approach is horrible, unworkable, and altogether vastly more complicated than the minor issue which it tries and fails to solve. Don't do this. – Boann Jul 20 '14 at 14:40
  • @ChristopherRucinski: I tried! I gave it an honest shot! To actually gain the compile-time safety you're after, you have to take all information about the number and type of arguments out of the format string. Splitting it into chunks with one `%s` per chunk doesn't help. After trying to improve this code for code reusability and compile-time safety for a while, the interface evolved into `new Expression().update(num1).update(" ").update(operator.getSign()).update(" ").update(num2).update(" = ").update(total).toString()`. It always goes back to string concatenation. – user2357112 Jul 20 '14 at 16:40
  • So you have tried a non-enum implementation for Expression? If you still have the code I would like to look at it. – Christopher Rucinski Jul 20 '14 at 18:00
  • By the end of it, it was just one `StringBuilder` instance variable and a bunch of wrappers around `append`. I tried generics, but since you can't take a variable number of type arguments, that ended up with types that looked like `Builder>>>` and an awkward need to build the `Builder` from the end of the string to the front. [Here's what that looked like when I realized I could only add types at the front of the chain.](http://pastebin.com/BUUpBFwj) – user2357112 Jul 21 '14 at 08:27
  • 1
    Not an answer, but FindBugs Eclise plugin catches these errors – Venkata Raju Aug 22 '14 at 06:01

1 Answers1

1

How about something like this:


public enum Operator {
    addition("+"),
    subtraction("-"),
    multiplication("x"),
    division("÷");

    private final String expressed;
    private Operator(String expressed) { this.expressed = expressed; }

    public String expressedAs() { return this.expressed; }
}

public class ExpressionBuilder {
    private Number n1;
    private Number n2;
    private Operator o1;
    private Number r;
    public void setN1(Number n1) { this.n1 = n1; }
    public void setN2(Number n2) { this.n2 = n2; }
    public void setO1(Operator o1) { this.o1 = o1; }
    public void setR(Number r) { this.r = r; }
    public String build() {
        final StringBuilder sb = new StringBuilder();
        sb.append(format(n1));
        sb.append(o1.expressedAs());
        sb.append(format(n2));
        sb.append(" = ");
        sb.append(format(r));
        return sb.toString();
    }
    private String format(Number n) {
        return n.toString(); // Could use java.text.NumberFormat 
    }
}
Stewart
  • 17,616
  • 8
  • 52
  • 80