Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

java

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?

like image 433
Salar Avatar asked Jan 03 '18 13:01

Salar


People also ask

Can you combine methods in Java?

The combine() method of DoubleSummaryStatistics class in Java is used to combine the given other DoubleSummaryStatistics to this DoubleSummaryStatistics. Parameter: This method accepts otherDoubleSummaryStatistics as parameter that is to be combined into this DoubleSummaryStatistics.

Can you combine methods in Python?

combine() is a series mathematical operation method. This is used to combine two series into one.


4 Answers

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);
}
like image 56
Ward Avatar answered Oct 20 '22 09:10

Ward


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.

like image 38
Andy Turner Avatar answered Oct 20 '22 10:10

Andy Turner


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.

like image 5
Aashishkebab Avatar answered Oct 20 '22 09:10

Aashishkebab


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.

like image 3
Timothy Truckle Avatar answered Oct 20 '22 10:10

Timothy Truckle