简体   繁体   English

Kotlin是否从文件加载类的集合?

[英]Kotlin load a collection of a class from a file?

In C++ I can load a collection of a class from a file using a friend operator>> overload: 在C ++中,我可以使用友好运算符>>重载从文件中加载类的集合:

friend std::istream& operator>>(std::istream& is_a, College& college_a) {
    return is_a >> college_a.id_,
        is_a.get(),
        std::getline(is_a,college_a.name_);
}

std::set<College> colleges {};
std::copy(std::istream_iterator<College> {std::cin},
    std::istream_iterator<College> {},
    std::inserter(colleges, colleges.begin())); 

from a file like this: 来自这样的文件:

0707 Rowan Technical College
0980 University of Saskatchewan
1058 Belmont University
1072 Belmont Technical College

How would this be done with Kotlin? Kotlin将如何处理?

There are many ways to parse a file and split the lines in Kotlin / Java. 在Kotlin / Java中,有很多方法可以解析文件并拆分行。 The next example uses a Regex to split the line into an id and the name for the college: 下一个示例使用正则表达式将行拆分为id和大学名称:

data class College(val id: String, val name: String)

val colleges = File("input.txt").useLines { lines ->
    val regex = Regex("(\\d+) (.+)")
    lines.mapNotNull { line ->
        regex.matchEntire(line)?.let {
            College(it.groupValues[1], it.groupValues[2])
        }
    }.toList()
}

If you need to read from the standard input, then replace the third line with: 如果您需要从标准输入中读取内容,则将第三行替换为:

val colleges = InputStreamReader(System.`in`).useLines { lines ->

Update: Hide details 更新:隐藏详细信息

There is no general way to parse text files into object in Java / Kotlin. 在Java / Kotlin中,没有将文本文件解析为对象的通用方法。 For XML and Json files exists some standard APIs and you can register Serializer/Deserializer for each type. 对于XML和Json文件,存在一些标准API,您可以为每种类型注册Serializer / Deserializer。

If you need such an abstraction for simple line oriented text files you have to build it yourself. 如果您需要对面向行的简单文本文件进行抽象,则必须自己构建。 The next examples may give you an idea: 下面的示例可能会给您一个想法:

fun <T : Any> File.parseLines(lineParser: (String) -> T?): List<T> =
    useLines { it.mapNotNull(lineParser).toList() }

The function extend the File class with a parseLines method. 该函数使用parseLines方法扩展File类。

The implementation of the line parser for class College would be like this: College的行解析器的实现如下所示:

val collegeLineParser: (String) -> College? = { line ->
    val regex = Regex("(\\d+) (.+)")
    regex.matchEntire(line)?.let {
        College(it.groupValues[1], it.groupValues[2])
    }
}

Or if you like to cache the RegEx : 或者,如果您想缓存RegEx

val collegeLineParserCachedRegex = object : (String) -> College? {
    val regex = Regex("(\\d+) (.+)")
    override fun invoke(line: String): College? =
        regex.matchEntire(line)?.let {
            College(it.groupValues[1], it.groupValues[2])
        }
}

Now you can invoke the method parseLines on a File : 现在,您可以在File上调用方法parseLines

val colleges = File("input.txt").parseLines(collegeLineParser)

Update: Registry for parsers 更新:解析器注册表

If you do not like to provide the lineParser for every call, you can create a registry: 如果您不想为每个调用都提供lineParser ,则可以创建一个注册表:

object LineParserRegistry {
    val parsers = ConcurrentHashMap<KClass<*>, (String) -> Any?>()

    inline fun <reified T> register(noinline parser : (String) -> T?) {
        parsers[T::class] = parser
    }

    inline fun <reified T> get(): (String) -> T? {
        // force companion initializer
        Class.forName(T::class.java.name)
        return parsers[T::class] as (String) -> T??
    }
}

inline fun <reified T : Any> File.parseLines(): List<T> =
    useLines { it.mapNotNull(LineParserRegistry.get<T>()).toList() }

If you like to register the parsers automatically you need to add an companion object with an init method: 如果您想自动注册解析器,则需要使用init方法添加一个伴随对象:

data class College(val id: String, val name: String) {
    companion object {
        init {
            val collegeLineParser: (String) -> College? = { line ->
                val regex = Regex("(\\d+) (.+)")
                regex.matchEntire(line)?.let {
                    College(it.groupValues[1], it.groupValues[2])
                }
            }
            LineParserRegistry.register(collegeLineParser)
        }
    }
}

This init method is invoked the first time the College class is used. 首次使用College类时,将调用此init方法。 But reflection does not count, therefore we had to add Class.forName(T::class.java.name) into the get method of the registry. 但是反射并不重要,因此我们不得不将Class.forName(T::class.java.name)到注册表的get方法中。 This call forces the initialization of the companion object. 此调用将强制初始化伴随对象。

Now you can call the parser without any preparations: 现在,您无需进行任何准备就可以调用解析器:

val colleges = File("input.txt").parseLines<College>()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM