Salar
Salar

Reputation: 243

Can we combine two methods that differ largely based on type?

I have two similar, but of different types, blocks of code in Java:

private Integer readInteger() {
    Integer value = null;
    while (value == null) {
        if (scanner.hasNextInt()) {
            value = scanner.nextInt();
        } else {
            scanner.next();
        }
    }

    return value;
}

private Double readDouble() {
    Double value = null;
    while (value == null) {
        if (scanner.hasNextDouble()) {
            value = scanner.nextDouble();
        } else {
            scanner.next();
        }
    }

    return value;
}

Is it possible to make just one method which would work for both of them?

Upvotes: 22

Views: 2820

Answers (7)

Ward
Ward

Reputation: 2828

I'd say, use a generic method, combined with the functional interfaces introduced in Java 8.

The method read now becomes a higher order function.

private <T> T read(Predicate<Scanner> hasVal, Function<Scanner, T> nextVal) {
    T value = null;
    while (value == null) {
        if (hasVal.test(scanner)) {
            value = nextVal.apply(scanner);
        } else {
            scanner.next();
        }
    }

    return value;
}

Calling code becomes:

read(Scanner::hasNextInt, Scanner::nextInt);
read(Scanner::hasNextDouble, Scanner::nextDouble);
read(Scanner::hasNextFloat, Scanner::nextFloat);
// ...

So the readInteger() method can be adapted as follows:

private Integer readInteger() {
    return read(Scanner::hasNextInt, Scanner::nextInt);
}

Upvotes: 39

Andy Turner
Andy Turner

Reputation: 140544

A totally different approach from my other answer (and the other answers): don't use generics, but instead just write the methods more concisely, so you don't really notice the duplication.

TL;DR: rewrite the methods as

while (!scanner.hasNextX()) scanner.next();
return scanner.nextX();

The overall goal - write it as a single method - is only possible if you accept some amount of additional cruft.

Java method signatures do not take into account the return type, so it's not possible to have a next() method return an Integer in one context, and Double in another (short of returning a common supertype).

As such, you have to have something at the call sites to distinguish these cases:

  • You might consider passing something like Integer.class or Double.class. This does have the advantage that you can use generics to know that the returned value matches that type. But callers could pass in something else: how would you handle Long.class, or String.class? Either you need to handle everything, or you fail at runtime (not a good option). Even with a tighter bound (e.g. Class<? extends Number>), you still need to handle more than Integer and Double.

    (Not to mention that writing Integer.class and Double.class everywhere is really verbose)

  • You might consider doing something like @Ward's answer (which I do like, BTW: if you're going to do it with generics, do it like that), and pass in functional objects which are able to deal with the type of interest, as well as providing the type information to indicate the return type.

    But, again, you've got to pass these functional objects in at each call site, which is really verbose.

In taking either of these approaches, you can add helper methods which pass the appropriate parameters to the "generic" read method. But this feels like a backwards step: instead of reducing the number of methods to 1, it's increased to 3.

Additionally, you now have to distinguish these helper methods somehow at the call sites, in order to be able to call the appropriate one:

  • You could have overloads with a parameter of value type, rather than class type, e.g.

    Double read(Double d)
    Integer read(Integer d)
    

    and then call like Double d = read(0.0); Integer i = read(0);. But anybody reading this code is going to be left wondering what that magic number in the code is - is there any significance to the 0?

  • Or, easier, just call the two overloads something different:

    Double readDouble()
    Integer readInteger()
    

    This is nice and easy: whilst it's slightly more verbose than read(0.0), it's readable; and it's way more concise that read(Double.class).


So, this has got us back to the method signatures in OP's code. But this hopefully justifies why you still want to keep those two methods. Now to address the contents of the methods:

Because Scanner.nextX() doesn't return null values, the method can be rewritten as:

while (!scanner.hasNextX()) scanner.next();
return scanner.nextX();

So, it's really easy to duplicate this for the two cases:

private Integer readInteger() {
  while (!scanner.hasNextInt()) scanner.next();
  return scanner.nextInt();
}

private Double readDouble() {
  while (!scanner.hasNextDouble()) scanner.next();
  return scanner.nextDouble();
}

If you want, you could pull out a method dropUntil(Predicate<Scanner>) method to avoid duplicating the loop, but I'm not convinced it really saves you that much.

A single (near-)duplicated line is way less burdensome in your code than all those generics and functional parameters. It's just plain old code, which happens to be more concise (and, likely, more efficient) than "new" ways to write it.

The other advantage of this approach is that you don't have to use boxed types - you can make the methods return int and double, and not have to pay the boxing tax unless you actually need it.

This may not be of advantage to OP, since the original methods do return the boxed type; I don't know if this is genuinely desired, or merely an artefact of the way the loop was written. However, it is useful in general not to create those objects unless you really need them.

Upvotes: 2

Aashishkebab
Aashishkebab

Reputation: 347

A lot of people have answered that you can use generics, but you can also simply remove the readInteger method, and only use the readDouble, as integers can be converted to doubles without data loss.

Upvotes: 5

Timothy Truckle
Timothy Truckle

Reputation: 15634

This is about code duplication.

The general approach is to turn similar code (you have) into equal code that can be extracted to a common parameterized method.

In your case what make the two code snipped differ is the access to methods of Scanner. You have to encapsulate them somehow. I'd suggest to do this with Java8 Functional interfaces like this:

@FunctionalInterface
interface ScannerNext{
   boolean hasNext(Scanner scanner);
}

@FunctionalInterface
interface ScannerValue{
   Number getNext(Scanner scanner);
}

Then replace the actual invocation of methods in scanner with the functional interface:

private Integer readInteger() {
    ScannerNext scannerNext = (sc)->sc.hasNextInt();
    ScannerValue scannerValue = (sc)-> sc.nextInt();
    Integer value = null;
    while (value == null) {
        if (scannerNext.hasNext(scanner)) {
            value = scannerValue.getNext(scanner);
        } else {
            scanner.next();
        }
    }
    return value;
}

There is one more problem that the type of the value variable differs. So we replace it with its common supertype:

private Integer readInteger() {
    ScannerNext scannerNext = (sc)->sc.hasNextInt();
    ScannerValue scannerValue = (sc)-> sc.nextInt();
    Number value = null;
    while (value == null) {
        if (scannerNext.hasNext(scanner)) {
            value = scannerValue.getNext(scanner);
        } else {
            scanner.next();
        }
    }
    return (Integer)value;
}

Now you have to places with a big equal section. You can select one of those sections starting with Number value = null; ending with the } before return ... and invoke your IDEs automated refactoring extract method:

private Number readNumber(ScannerNext scannerNext,  ScannerValue scannerValue) {
    Number value = null;
    while (value == null) {
        if (scannerNext.hasNext(scanner)) {
            value = scannerValue.getNext(scanner);
        } else {
            scanner.next();
        }
    }
    return value;
}

private Integer readInteger() {
    return (Integer) readNumber( (sc)->sc.hasNextInt(), (sc)-> sc.nextInt());
}
private Double readDouble() {
    return (Double) readNumber( (sc)->sc.hasNextDouble(), (sc)-> sc.nextDouble());
}

Comments argue against the use of custom interfaces against predefined interfaces from the JVM.

But my point in this answer was how to turn similar code into equal code so that it can be extracted to a single method rather that giving a concrete solution for this random problem.

Upvotes: 3

OldCurmudgeon
OldCurmudgeon

Reputation: 65889

Not an ideal solution but it still achieves the necessary removal of duplicate code and has the added benefit of not requiring Java-8.

// This could be done better.
static final Scanner scanner = new Scanner(System.in);

enum Read{
    Int {
        @Override
        boolean hasNext() {
            return scanner.hasNextInt();
        }

        @Override
        <T> T next() {
            return (T)Integer.valueOf(scanner.nextInt());
        }

    },
    Dbl{
        @Override
        boolean hasNext() {
            return scanner.hasNextDouble();
        }

        @Override
        <T> T next() {
            return (T)Double.valueOf(scanner.nextDouble());
        }

    };

    abstract boolean hasNext();
    abstract <T> T next();

    // All share this method.
    public <T> T read() {
        T v = null;
        while (v == null) {
            if ( hasNext() ) {
                v = next();
            } else {
                scanner.next();
            }
        }
        return v;
    }
}

public void test(String[] args) {
    Integer i = Read.Int.read();
    Double d = Read.Dbl.read();
}

There are some minor issues with this such as the casting but it should be a reasonable option.

Upvotes: 2

zhh
zhh

Reputation: 2406

Reflection is an alternative if you don't care about performance.

private <T> T read(String type) throws Exception {
    Method readNext = Scanner.class.getMethod("next" + type);
    Method hasNext = Scanner.class.getMethod("hasNext" + type);
    T value = null;
    while (value == null) {
        if ((Boolean) hasNext.invoke(scanner)) {
            value = (T) readNext.invoke(scanner);
        } else {
            scanner.next();
        }
    }
    return value;
}

Then you call

Integer i = read("Int");

Upvotes: 0

Andy Turner
Andy Turner

Reputation: 140544

You could have something with three methods:

  • One which says if there is a value of the right type
  • Another which gets the value of the right type.
  • Another which discards whatever token you have.

For example:

interface Frobnitz<T> {
  boolean has();
  T get();
  void discard();
}

You can pass this into your method:

private <T> T read(Frobnitz<? extends T> frob) {
    T value = null;
    while (value == null) {
        if (frob.has()) {
            value = frob.get();
        } else {
            frob.discard();
        }
    }

    return value;
}

And then just implement Frobnitz for your Double and Integer cases.

To be honest, I'm not sure this gets you very much, especially if you've only got two cases; I'd be inclined just to suck up the small amount of duplication.

Upvotes: 5

Related Questions