Обобщённый метод для чтения данных из файла

264
29 августа 2017, 10:38

Здравствуйте. Подскажите, как написать обобщённый метод чтения данных из файла. Например, в одном случае нужно считать строки, в другом - целые числа. Как написать метод типа такого:

    public static <T> List<T> readFromFile(Path pathIn, TypeValue typeValue){...};

Корректен ли такой код:

public static List readFromFile(Path pathIn, TypeValue typeValue){
        try(Scanner in = new Scanner(pathIn)) {
            if (typeValue == TypeValue.typeInt) {
                List<Integer> list = new ArrayList<>();
                while (in.hasNextInt()) {
                    list.add(in.nextInt());
                }
                return list;
            } else {
                List<String> list = Files.readAllLines(pathIn);
                return list;
            }
        } catch (IOException e){ 
            return null;
        }
    }

Здесь pathIn - путь к файлу с данными, typeValue - флаг для обозначения данных (Integer, String и т.д.). Смущает, что возвращается не параметризованный List. Как вообще решается задача чтения из файла, если формат данных может быть разным - Integer, Double, String и т.д.? Или следует писать для каждого типа данных свой код для чтения? Как-то громоздко тогда получается.

Answer 1

Функционал из Java 8 Stream & Lambda позволяет передавать в метод функциональный интерфейс, который будет применен к потоку данных:

public void start() throws IOException {
    Path path = Paths.get("test");
    List<String> resultStringList = readFile(path, Function.identity());
    List<Integer> resultIntList = readFile(path, Integer::parseInt);
}
public <T> List<T> readFile(Path path, Function<String, T> func) throws IOException {
    return Files.lines(path)
            .map(func)
            .collect(Collectors.toList());
}

}

Answer 2

Или следует писать для каждого типа данных свой код для чтения?

Не самый плохой вариант: написать отдельные методы для каждого типа (в Scanner, например, так и сделали) .

Как-то громоздко тогда получается.

В Вашем варианте вместо нескольких коротких методов используется один, в котором в одном большом if будет код всех этих методов.

Альтернатива

Можно обернуть метод в интерфейс и применить полиморфизм:

//интерфейс
interface ListReader<T> {
    List<T> read(String path);
}
//все общие функции вынесем в отдельный базовый класс
abstract class BaseListReader<T> implements ListReader<T> {
    @Override
    public List<T> read(String path) {
        try (Scanner in = new Scanner(path)) {
            List<T> list = new ArrayList<>();
            while (hasNext(in)) {
                list.add(next(in));
            }
            return list;
        } catch (IOException e) {
            return null;
        }
    }
    abstract boolean hasNext(Scanner in) throws IOException;
    abstract T next(Scanner in) throws IOException;
}
class IntListReader extends BaseListReader<Integer> {
    @Override
    boolean hasNext(Scanner in) throws IOException {
        return in.hasNextInt();
    }
    @Override
    Integer next(Scanner in) throws IOException {
        return in.nextInt();
    }
}
class StringListReader extends BaseListReader<String> {
    @Override
    boolean hasNext(Scanner in) throws IOException {
        return in.hasNext();
    }
    @Override
    String next(Scanner in) throws IOException {
        return in.nextLine();
    }
}

Затем создать фабрику:

//Метод для создания
public ListReader createReader(Class clazz) {
    if (clazz == Integer.class) {
        return new IntListReader();
    } else if (clazz == String.class) {
        return new StringListReader();
    }
    throw new IllegalArgumentException("clazz");
}

В такой реализации обработка разных типов будет отделена друг от друга и от классов-клиентов (публичным может быть только интерфейс ListReader). Это позволит протеститровать их отдельно от других классов приложения/использовать класы повторно.

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

Смущает, что возвращается не параметризованный List

Эта проблема не решена (возвращается не параметризованный ListReader). Решение зависит от того как используется результат метода. Если полученный список обрабатывается как List<Object> то метод можно оставить непараметризованным. Если в дальнейшем объекты из списка приводятся к типу, то стоить рассмотреть возможность параметризации фабрик и клиентского кода.

READ ALSO
Как обновить БД SQLite

Как обновить БД SQLite

Есть БДПосле создания появляется необходимость в добавлении таблиц но для этого надо вызвать метод onUpgrade и когда вызовается этот метод просто...

294
Forge SidedProxy is deprecated

Forge SidedProxy is deprecated

В главном файле мода:

245
Servlet &ldquo;не видит&rdquo; атрибут

Servlet “не видит” атрибут

Создаю сервлет, в классе добавляю в request атрибуты,

238
не запускается java класс после билда в idea

не запускается java класс после билда в idea

Решил подключить к своему приложению базу данных PostgreSQLдобавил в настройках идеи нужный jar архив, для проверки написал такую строку и запустил:

216