Можно ли объединить два метода, которые в основном отличаются от типа?

21

У меня есть два похожих, но разных типа, блоков кода в 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;
}

Можно ли сделать только один метод, который будет работать для них обоих?     

задан Salar 03.01.2018 в 14:05
источник
  • Прочитайте в строке и затем проанализируйте ее. –  MikeTheLiar 03.01.2018 в 14:07
  • Да, но вам нужно передать методу аргумент, указывающий, какой тип вы читаете, и у него будет больше строк с if-операторами. Учитывая, что существует мало общего кода, его, вероятно, будет сложнее поддерживать, чем два отдельных метода. –  Erwin Bolwidt 03.01.2018 в 14:07
  • Эта временная переменная бесполезна и что если / else не требуется, просто используйте while (! scanner.hasNextInt ()) {scanner.next (); } return scanner.nextInt () ;. И «нет», используя только один метод, на самом деле не работает здесь, или не очень помогает. –  Tom 03.01.2018 в 14:13
  • Это не оптимизация. –  Koray Tugay 03.01.2018 в 14:22
  • @KorayTugay Это не оптимизация производительности. –  Michael 03.01.2018 в 14:46
Показать остальные комментарии

7 ответов

38

Я бы сказал, используйте общий метод в сочетании с функцией более высокого порядка .

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;
}

Вызов кода:

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

Таким образом, метод readInteger() может быть адаптирован следующим образом:

private Integer readInteger() {
    return read(Scanner::hasNextInt, Scanner::nextInt);
}
    
ответ дан Ward 03.01.2018 в 14:27
источник
  • становится немного раздражающим и подверженным ошибкам, если у вас более одного чтения каждого типа. Затем связь между hasNext и следующими методами будет дублироваться. Он будет обертывать read (Scanner :: hasNextInt, Scanner :: nextInt) в своей собственной функции int readInt (), и то же самое для других –  Michael 03.01.2018 в 14:39
  • Это действительно может быть расширение. Можно даже определить интерфейс Reader, с реализацией IntReader, который хранит оба метода и т. Д. ... :-) –  Ward 03.01.2018 в 14:43
  • Хорошее решение. Пары функций могут быть сохранены в перечислении для повышения надежности, например. чтобы никто не мог позвонить со Сканером :: hasNextInt и сканер: hasNextFloat. –  Henrik 03.01.2018 в 14:48
  • Нет. Проверьте, читает ли он следующий файлVal.apply (сканер) ==>, только тогда фактический объект сканера привязан к функции. scanner :: hasNextInt не является функцией <Scanner, Integer>, а скорее поставщиком <Integer>. –  Ward 03.01.2018 в 16:03
  • Я бы предпочел версию в исходном посте. Чтобы прочитать простой Integer или Double из файла, мне нужно работать с Predicate, Function и Generics? По-моему, это не смысл. Что не так с readInteger ()? Как читать (Scanner :: hasNextInt, Scanner :: nextInt); лучше по сравнению с readInteger ()? –  Koray Tugay 04.01.2018 в 10:50
Показать остальные комментарии
5

У вас может быть что-то с тремя способами:

  • Тот, который говорит, что есть значение правильного типа
  • Другой, который получает значение правильного типа.
  • Другой, который отбрасывает любой токен, который у вас есть.

Например:

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

Вы можете передать это в свой метод:

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;
}

А затем просто реализуйте Frobnitz для своих Double и Integer .

Честно говоря, я не уверен, что это вас очень сильно, особенно если у вас только два случая; Я был бы склонен просто всасывать небольшое количество дублирования.

    
ответ дан Andy Turner 03.01.2018 в 14:18
источник
5

Многие люди ответили, что вы можете использовать дженерики, но вы также можете просто удалить метод readInteger и использовать только readDouble , поскольку целые числа могут быть преобразованы в двойные файлы без потери данных.

    
ответ дан Aashish Bharadwaj 03.01.2018 в 17:07
источник
  • Подождите, это очень верно '-' –  Nathan 04.01.2018 в 10:05
3

Это примерно дублирование кода .

Общий подход состоит в том, чтобы превратить аналогичный код (у вас) в равный код , который может быть извлечен в общий параметризованный метод .

В вашем случае отличием двух сокращенных кодов является доступ к методам Scanner . Вы должны инкапсулировать их как-то. Я бы предложил сделать это с помощью Java8 Функциональных интерфейсов следующим образом:

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

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

Затем замените фактический вызов методов в сканере с помощью функционального интерфейса:

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;
}

Еще одна проблема заключается в том, что тип переменной value отличается. Поэтому мы заменяем его своим общим супертипом:

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;
}

Теперь вам нужно разместить большие равные разделы. Вы можете выбрать один из этих разделов, начиная с Number value = null; , заканчивающегося } до return ... и вызывать ваши IDE автоматический рефакторинг метод extract :

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());
}

Комментарии противоречат использованию пользовательских интерфейсов для предопределенных интерфейсов из JVM.

Но моя точка зрения в этом ответе заключалась в том, как превратить аналогичный код в равный код , чтобы он мог быть извлечен для одного метода, а именно для предоставления конкретного решения для этого случайная проблема.

    
ответ дан Timothy Truckle 03.01.2018 в 14:32
источник
  • Почему бы не использовать стандартные функциональные интерфейсы Predicate и Function? –  Ward 03.01.2018 в 14:33
  • @ Хороший момент! Пока функция <T, R> решит неприятный корпус Predicate.test (), сделает код менее читаемым, IMHO ... –  Timothy Truckle 03.01.2018 в 14:38
  • Я действительно не согласен (но никто не сказал, что я должен ;-)): вы отменяете читаемость кода вызова (кастинг и т. д.) для удобочитаемости реализующего кода (hasNext / test stuff ). Я бы предпочел простоту вызывающего кода. –  Ward 03.01.2018 в 14:46
  • @Ward Как написано, согласитесь с вами в отношении функции <T, R>. Но, на мой взгляд, Predicate.test () не повышает удобочитаемость ни в одном месте ... –  Timothy Truckle 03.01.2018 в 14:59
  • Действительно, я прочитал ваш комментарий быстро, по-видимому, мой плохой –  Ward 03.01.2018 в 15:13
Показать остальные комментарии
2

Совершенно другой подход из моего другого ответа (и других ответов): не используйте generics, а вместо этого просто пишите методы более кратко, поэтому вы действительно не замечаете дублирование.

TL; DR: перепишите методы как

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

Общая цель - записать ее как единый метод - возможна только в том случае, если вы принимаете некоторое количество дополнительного трещин.

Подписи метода Java не учитывают тип возвращаемого значения, поэтому невозможно, чтобы метод next() возвращал Integer в одном контексте и Double в другом (не доходя до общего супертипа).

Таким образом, вы должны иметь что-то на сайтах вызовов, чтобы различать эти случаи:

  • Возможно, вы рассмотрите что-то вроде Integer.class или Double.class . Это имеет то преимущество, что вы можете использовать generics, чтобы знать, что возвращаемое значение соответствует этому типу. Но вызывающие могут передать что-то еще: как бы вы обрабатывали Long.class или String.class ? Либо вам нужно все обрабатывать, либо вы не выполняете во время выполнения (не очень хороший вариант). Даже при более жесткой привязке (например, Class<? extends Number> ) вам все равно нужно обрабатывать более Integer и Double .

    (Не говоря уже о том, что написание Integer.class и Double.class везде действительно многословно)

  • Возможно, вы захотите сделать что-то вроде ответа Варда (что мне нравится, кстати: если вы собираетесь делать это с помощью дженериков, делать это так) и передавать функциональные объекты, которые могут иметь дело с типом интереса, а также предоставлять информацию типа, указывающую тип возврата.

    Но, опять же, вам нужно передать эти функциональные объекты на каждом сайте вызова, что действительно многословно.

При принятии любого из этих подходов вы можете добавить вспомогательные методы, которые передают соответствующие параметры в «общий» метод read . Но это выглядит как шаг назад: вместо уменьшения количества методов до 1, увеличено до 3.

Кроме того, теперь вам нужно как-то различать эти вспомогательные методы на сайтах вызовов, чтобы иметь возможность вызвать соответствующий:

  • У вас могут быть перегрузки с параметром value , а не тип класса, например

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

    , а затем наберите Double d = read(0.0); Integer i = read(0); . Но кто-нибудь, кто читает этот код, будет удивлен, что такое магическое число в коде - есть ли какое-либо значение для 0 ?

  • Или проще, просто вызовите две перегрузки что-то другое:

    Double readDouble()
    Integer readInteger()
    

    Это приятно и просто: хотя он немного более подробный, чем read(0.0) , он читается; и это более кратким, чем read(Double.class) .

Итак, это вернет нас к сигнатурам метода в коде OP. Но это, надеюсь, оправдывает, почему вы все еще хотите сохранить эти два метода. Теперь для обращения к содержимому методов:

Поскольку Scanner.nextX() не возвращает нулевые значения, метод можно переписать как:

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

Итак, это действительно легко продублировать это для двух случаев:

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

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

Если вы хотите, вы можете вытащить метод dropUntil(Predicate<Scanner>) , чтобы избежать дублирования цикла, но я не уверен, что это действительно так сильно вас спасает.

Единственная (близкая) дублированная строка менее обременительна в коде, чем все эти дженерики и функциональные параметры. Это просто старый код, который оказывается более кратким (и, вероятно, более эффективным), чем «новые» способы его написания.

Другим преимуществом этого подхода является то, что вам не нужно использовать типы в штучной упаковке - вы можете заставить методы возвращать int и double и не платить налог на бокс, если вы на самом деле этого не хотите.

Это может оказаться непригодным для OP, поскольку исходные методы возвращают тип в штучной упаковке; Я не знаю, действительно ли это действительно желательно, или просто артефакт того, как был написан цикл. Однако полезно вообще не создавать эти объекты, если они вам действительно не нужны.

    
ответ дан Andy Turner 04.01.2018 в 08:57
источник
1

Не идеальное решение, но оно по-прежнему обеспечивает необходимое удаление дублированного кода и имеет дополнительное преимущество, не требующее 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();
}

Есть некоторые незначительные проблемы с этим, такие как кастинг, но это должен быть разумный вариант.

    
ответ дан OldCurmudgeon 03.01.2018 в 14:57
источник
  • Статический финальный сканер убивает это для меня. Не проверяем. –  Michael 03.01.2018 в 16:30
  • @Michael - Вероятно, было бы лучше передать его как параметр, но в качестве демонстрации этой техники я не хотел загромождать код. –  OldCurmudgeon 03.01.2018 в 17:12
0

Отражение - альтернатива, если вы не заботитесь о производительности.

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;
}

Затем вы вызываете

Integer i = read("Int");
    
ответ дан zhh 03.01.2018 в 14:38
источник
  • Это может быть альтернатива, но это последнее средство, а не то, что должно прийти на ум в качестве первого выбора ... –  Timothy Truckle 03.01.2018 в 14:41