Java 中文官方教程 2022 版(四十)(1)

简介: Java 中文官方教程 2022 版(四十)

课程:XML 的流式 API

原文:docs.oracle.com/javase/tutorial/jaxp/stax/index.html

本课程专注于 XML 的流式 API(StAX),这是一种基于 Java 技术的流式、事件驱动、拉取解析的 API,用于读取和写入 XML 文档。StAX 使您能够创建快速、相对易于编程且具有轻量级内存占用的双向 XML 解析器。

为什么选择 StAX?

原文:docs.oracle.com/javase/tutorial/jaxp/stax/why.html

StAX 项目由 BEA 主导,得到了 Sun Microsystems 的支持,JSR 173 规范于 2004 年 3 月通过了 Java 社区流程的最终批准投票。StAX API 的主要目标是通过公开一个简单的基于迭代器的 API,将“解析控制权交给程序员。这允许程序员请求下一个事件(拉取事件),并允许以过程化方式存储状态。” StAX 的创建是为了解决两种最常见解析 API,SAX 和 DOM,的限制。

流式处理与 DOM

一般来说,处理 XML 信息集有两种编程模型:流式处理文档对象模型(DOM)。

DOM 模型涉及创建代表整个文档树和 XML 文档的完整信息集状态的内存对象。一旦在内存中,DOM 树可以自由导航和任意解析,因此为开发人员提供了最大的灵活性。然而,这种灵活性的代价是潜在的大内存占用和显著的处理器需求,因为整个文档的表示必须作为对象在内存中保持,以便在文档处理期间使用。在处理小型文档时,这可能不是问题,但随着文档大小的增加,内存和处理器需求可能会迅速升高。

流式处理是指一种编程模型,在应用程序运行时串行传输和解析 XML 信息集,通常是实时的,并且通常来自动态来源,其内容事先并不完全知晓。此外,基于流的解析器可以立即开始生成输出,并且信息集元素在使用后可以立即丢弃和进行垃圾回收。虽然提供了较小的内存占用、降低的处理器需求和在某些情况下更高的性能,但流处理的主要折衷是您只能在文档中的一个位置看到信息集状态。您基本上受限于文档的“纸板筒”视图,这意味着您需要在阅读 XML 文档之前知道要进行哪些处理。

在处理 XML 时,流式处理模型特别适用于应用程序具有严格的内存限制,比如在运行 Java 平台微版(Java ME 平台)的手机上,或者当应用程序需要同时处理多个请求时,比如在应用服务器上。实际上,可以说大多数 XML 业务逻辑都可以从流式处理中受益,并且不需要在内存中维护整个 DOM 树。

拉取解析与推送解析

拉取解析是一种编程模型,其中客户端应用程序在需要与 XML 信息集交互时调用 XML 解析库的方法,即客户端只有在明确请求时才会获取(拉取)XML 数据。

推送解析是一种编程模型,其中 XML 解析器在遇到 XML 信息集中的元素时向客户端发送(推送)XML 数据,即使客户端此时还没有准备好使用它。

在处理 XML 流时,拉取解析相比于推送解析提供了几个优势:

  • 在拉取解析中,客户端控制应用程序线程,并且可以在需要时调用解析器的方法。相比之下,在推送处理中,解析器控制应用程序线程,客户端只能接受解析器的调用。
  • 拉取解析库可以比推送库更小,与这些库交互的客户端代码也更简单,即使对于更复杂的文档。
  • 拉取客户端可以使用单个线程同时读取多个文档。
  • StAX 拉取解析器可以过滤 XML 文档,使客户端不需要的元素被忽略,并且可以支持非 XML 数据的 XML 视图。

StAX 使用案例

StAX 规范定义了 API 的许多用例:

  • 数据绑定
  • 反编组 XML 文档
  • 将 XML 文档编组
  • 并行文档处理
  • 无线通信
  • 简单对象访问协议(SOAP)消息处理
  • 解析简单可预测的结构
  • 解析具有前向引用的图形表示
  • 解析 Web 服务描述语言(WSDL)
  • 虚拟数据源
  • 查看存储在数据库中的 XML 数据
  • 查看由 XML 数据绑定创建的 Java 对象中的数据
  • 将 DOM 树作为事件流导航
  • 解析特定的 XML 词汇
  • 管道化 XML 处理

对所有这些用例的完整讨论超出了本课程的范围。请参考 StAX 规范以获取更多信息。

将 StAX 与其他 JAXP API 进行比较

作为 JAXP 家族中的一个 API,StAX 可以与 SAX、TrAX 和 JDOM 等其他 API 进行比较。在后两者中,StAX 不像 TrAX 或 JDOM 那样强大或灵活,但也不需要太多内存或处理器负载才能发挥作用,并且在许多情况下,StAX 可以胜过基于 DOM 的 API。上面概述的相同论点,权衡 DOM 模型与流模型的成本/效益,在这里同样适用。

有鉴于此,最接近的比较可以在 StAX 和 SAX 之间进行,正是在这里 StAX 提供了许多情况下有益的功能;其中一些包括:

  • 使用 StAX 的客户端通常比使用 SAX 的客户端更容易编码。虽然可以说 SAX 解析器稍微更容易编写,但 StAX 解析器的代码可能更小,客户端与解析器交互所需的代码更简单。
  • StAX 是一个双向 API,意味着它既可以读取又可以写入 XML 文档。SAX 只能读取,所以如果你想要写入 XML 文档,就需要另一个 API。
  • SAX 是一个推送 API,而 StAX 是一个拉取 API。上面概述的推送和拉取 API 之间的权衡在这里也适用。

以下表格总结了 StAX、SAX、DOM 和 TrAX 的比较特性。(表格改编自 Jeff Ryan 的文章Does StAX Belong in Your XML Toolbox?)。

XML 解析器 API 特性摘要

特性 StAX SAX DOM TrAX
API 类型 拉取,流式 推送,流式 内存树 XSLT 规则
使用便捷性
XPath 能力
CPU 和内存效率 良好 良好 各异 各异
仅向前
读取 XML
写入 XML
创建,读取,更新,删除

StAX API

原文:docs.oracle.com/javase/tutorial/jaxp/stax/api.html

StAX API 公开了用于 XML 文档的迭代式、基于事件的处理的方法。XML 文档被视为一系列经过过滤的事件,并且信息集状态可以以过程化方式存储。此外,与 SAX 不同,StAX API 是双向的,可以实现对 XML 文档的读取和写入。

StAX API 实际上是两个不同的 API 集:一个光标 API 和一个迭代器 API。这两个 API 集将在本课程的后面更详细地解释,但它们的主要特点如下所述。

光标 API

如其名称所示,StAX 光标 API 表示一个光标,您可以使用它从头到尾遍历 XML 文档。这个光标一次只能指向一件事,并且总是向前移动,从不后退,通常一次移动一个信息集元素。

两个主要的光标接口是XMLStreamReaderXMLStreamWriterXMLStreamReader包括了从 XML 信息模型中检索所有可能信息的访问方法,包括文档编码、元素名称、属性、命名空间、文本节点、起始标记、注释、处理指令、文档边界等等;例如:

public interface XMLStreamReader {
    public int next() throws XMLStreamException;
    public boolean hasNext() throws XMLStreamException;
    public String getText();
    public String getLocalName();
    public String getNamespaceURI();
    // ... other methods not shown
}

您可以在XMLStreamReader上调用诸如getTextgetName之类的方法,以获取当前光标位置的数据。XMLStreamWriter提供了与StartElementEndElement事件类型对应的方法;例如:

public interface XMLStreamWriter {
    public void writeStartElement(String localName) throws XMLStreamException;
    public void writeEndElement() throws XMLStreamException;
    public void writeCharacters(String text) throws XMLStreamException;
    // ... other methods not shown
}

光标 API 与 SAX 在许多方面相似。例如,可以直接访问字符串和字符信息的方法可用,并且可以使用整数索引访问属性和命名空间信息。与 SAX 一样,光标 API 方法将 XML 信息作为字符串返回,这减少了对象分配的需求。

迭代器 API

StAX 迭代器 API 将 XML 文档流表示为一组离散的事件对象。这些事件由应用程序拉取,并由解析器按照它们在源 XML 文档中读取的顺序提供。

基本的迭代器接口称为XMLEvent,并且为事件迭代器 API 表中列出的每种事件类型都有子接口。用于读取迭代器事件的主要解析器接口是XMLEventReader,用于写入迭代器事件的主要接口是XMLEventWriterXMLEventReader接口包含五种方法,其中最重要的是nextEvent,它返回 XML 流中的下一个事件。XMLEventReader实现了java.util.Iterator,这意味着从XMLEventReader返回的内容可以被缓存或传递给可以与标准 Java 迭代器一起工作的程序;例如:

public interface XMLEventReader extends Iterator {
    public XMLEvent nextEvent() throws XMLStreamException;
    public boolean hasNext();
    public XMLEvent peek() throws XMLStreamException;
    // ...
}

类似地,在迭代器 API 的输出端,你有:

public interface XMLEventWriter {
    public void flush() throws XMLStreamException;
    public void close() throws XMLStreamException;
    public void add(XMLEvent e) throws XMLStreamException;
    public void add(Attribute attribute) throws XMLStreamException;
    // ...
}

迭代器事件类型

XMLEvent在事件迭代器 API 中定义的类型

事件类型 描述
StartDocument 报告一组 XML 事件的开始,包括编码、XML 版本和独立属性。
StartElement 报告元素的开始,包括任何属性和命名空间声明;还提供了开始标记的前缀、命名空间 URI 和本地名称的访问。
EndElement 报告元素的结束标记。如果已在相应的 StartElement 上显式设置了命名空间,则在此处可以调用已经超出范围的命名空间。
Characters 对应于 XML CData 部分和 CharacterData 实体。请注意,可忽略的空格和重要的空格也被报告为 Character 事件。
EntityReference 字符实体可以作为独立事件报告,应用程序开发人员可以选择解析或传递未解析的实体。默认情况下,实体会被解析。或者,如果不想将实体报告为事件,则可以替换文本并报告为 Characters
ProcessingInstruction 报告底层处理指令的目标和数据。
Comment 返回注释的文本。
EndDocument 报告一组 XML 事件的结束。
DTD 报告与流相关联的(如果有的话)DTD 的信息,并提供一种返回在 DTD 中找到的自定义对象的方法。
Attribute 属性通常作为 StartElement 事件的一部分报告。然而,有时希望将属性作为独立的 Attribute 事件返回;例如,当命名空间作为 XQueryXPath 表达式的结果返回时。
Namespace 与属性一样,命名空间通常作为 StartElement 的一部分报告,但有时希望将命名空间作为独立的 Namespace 事件报告。

请注意,只有在处理的文档包含 DTD 时,才会创建 DTDEntityDeclarationEntityReferenceNotationDeclarationProcessingInstruction 事件。

事件映射示例

作为事件迭代器 API 如何映射 XML 流的示例,请考虑以下 XML 文档:

<?xml version="1.0"?>
<BookCatalogue >
    <Book>
        <Title>Yogasana Vijnana: the Science of Yoga</Title>
        <ISBN>81-40-34319-4</ISBN>
        <Cost currency="INR">11.50</Cost>
    </Book>
</BookCatalogue>

此文档将被解析为十八个主要和次要事件,如下表所示。请注意,通常从主要事件而不是直接访问,可以访问用大括号({})显示的次要事件。

迭代器 API 事件映射示例

# 元素/属性 事件
1 version="1.0" StartDocument
2 isCData = false data = "\n" IsWhiteSpace = true Characters
3 qname = BookCatalogue:http://www.publishing.org 属性 = null 命名空间 = {BookCatalogue" -> http://www.publishing.org"} StartElement
4 qname = 书 属性 = null 命名空间 = null StartElement
5 qname = 标题 属性 = null 命名空间 = null StartElement
6 isCData = false data = "Yogasana Vijnana: the Science of Yoga\n\t" IsWhiteSpace = false Characters
7 qname = Title namespaces = null EndElement
8 qname = ISBN attributes = null namespaces = null StartElement
9 isCData = false data = "81-40-34319-4\n\t" IsWhiteSpace = false Characters
10 qname = ISBN namespaces = null EndElement
11 qname = Cost attributes = {"currency" -> INR} namespaces = null StartElement
12 isCData = false data = "11.50\n\t" IsWhiteSpace = false Characters
13 qname = Cost namespaces = null EndElement
14 isCData = false data = "\n" IsWhiteSpace = true Characters
15 qname = Book namespaces = null EndElement
16 isCData = false data = "\n" IsWhiteSpace = true Characters
17 qname = BookCatalogue:http://www.publishing.org namespaces = {BookCatalogue" -> http://www.publishing.org"} EndElement
18 EndDocument

在这个例子中有几个重要的事项需要注意:

  • 事件按照文档中遇到相应的 XML 元素的顺序创建,包括元素的嵌套、打开和关闭元素、属性顺序、文档开始和文档结束等。
  • 与正确的 XML 语法一样,所有容器元素都有相应的开始和结束事件;例如,每个 StartElement 都有一个对应的 EndElement,即使是空元素也是如此。
  • Attribute 事件被视为次要事件,并且可以从其对应的 StartElement 事件中访问。
  • Attribute 事件类似,Namespace 事件被视为次要事件,但在事件流中出现两次,并且可以从它们对应的 StartElementEndElement 中分别访问两次。
  • 所有元素都指定了 Character 事件,即使这些元素没有字符数据。同样,Character 事件可以跨事件分割。
  • StAX 解析器维护一个命名空间堆栈,其中保存了当前元素及其祖先元素定义的所有 XML 命名空间信息。通过 javax.xml.namespace.NamespaceContext 接口暴露的命名空间堆栈可以通过命名空间前缀或 URI 访问。

在游标和迭代器 API 之间进行选择

此时合理地问一下,“我应该选择哪个 API?我应该创建 XMLStreamReader 还是 XMLEventReader 的实例?为什么会有两种类型的 API?”

开发目标

StAX 规范的作者针对三种类型的开发者:

  • 图书馆和基础设施开发者:创建应用服务器、JAXM、JAXB、JAX-RPC 等实现;需要高效、低级别的 API,并且具有最小的可扩展性要求。
  • Java ME 开发者:需要小型、简单的拉取解析库,并且具有最小的可扩展性需求。
  • Java 平台企业版(Java EE)和 Java 平台标准版(Java SE)开发人员:需要干净、高效的拉取解析库,同时需要灵活性来读取和写入 XML 流,创建新的事件类型,并扩展 XML 文档元素和属性。

鉴于这些广泛的开发类别,StAX 的作者认为定义两个小型、高效的 API 比过载一个更大、必然更复杂的 API 更有用。

比较游标和迭代器 API

在选择游标和迭代器 API 之间之前,你应该注意一些你可以使用迭代器 API 而不能使用游标 API 的事项:

  • XMLEvent子类创建的对象是不可变的,可以在数组、列表和映射中使用,并且可以在解析器继续处理后传递到你的应用程序中。
  • 你可以创建XMLEvent的子类型,这些子类型可以是全新的信息项,也可以是现有项目的扩展,但具有额外的方法。
  • 你可以以比游标 API 更简单的方式向 XML 事件流中添加和删除事件。

同样,在做出选择时,请记住一些一般性建议:

  • 如果你正在为特别受内存限制的环境编程,比如 Java ME,你可以使用游标 API 创建更小、更高效的代码。
  • 如果性能是你的最高优先级——例如,在创建低级库或基础设施时——游标 API 更有效率。
  • 如果你想创建 XML 处理管道,请使用迭代器 API。
  • 如果你想修改事件流,请使用迭代器 API。
  • 如果你希望你的应用程序能够处理事件流的可插拔处理,请使用迭代器 API。
  • 一般来说,如果你没有明确偏好,建议使用迭代器 API,因为它更灵活、可扩展,从而“未雨绸缪”你的应用程序。

使用 StAX

原文:docs.oracle.com/javase/tutorial/jaxp/stax/using.html

一般来说,StAX 程序员通过使用 XMLInputFactoryXMLOutputFactoryXMLEventFactory 类来创建 XML 流读取器、写入器和事件。通过在工厂上设置属性来进行配置,可以通过在工厂上使用 setProperty 方法将特定于实现的设置传递给底层实现。类似地,可以使用 getProperty 工厂方法查询特定于实现的设置。

下面描述了 XMLInputFactoryXMLOutputFactoryXMLEventFactory 类,然后讨论了资源分配、命名空间和属性管理、错误处理,最后使用游标和迭代器 API 读取和写入流。

StAX 工厂类

StAX 工厂类。XMLInputFactoryXMLOutputFactoryXMLEventFactory,让您定义和配置 XML 流读取器、流写入器和事件类的实现实例。

XMLInputFactory

XMLInputFactory 类允许您配置由工厂创建的 XML 流读取器处理器的实现实例。通过在类上调用 newInstance 方法来创建抽象类 XMLInputFactory 的新实例。然后使用静态方法 XMLInputFactory.newInstance 来创建新的工厂实例。

派生自 JAXP,XMLInputFactory.newInstance 方法通过以下查找过程确定要加载的特定 XMLInputFactory 实现类:

  1. 使用 javax.xml.stream.XMLInputFactory 系统属性。
  2. 使用 Java SE 平台的 Java Runtime Environment (JRE) 目录中的 lib/xml.stream.properties 文件。
  3. 如果可用,使用 Services API 通过查找 JRE 中可用的 JAR 文件中的 META-INF/services/javax.xml.stream.XMLInputFactory 文件确定类名。
  4. 使用平台默认的 XMLInputFactory 实例。

在获取适当的 XMLInputFactory 引用之后,应用程序可以使用工厂来配置和创建流实例。以下表格列出了 XMLInputFactory 支持的属性。详细列表请参阅 StAX 规范。

javax.xml.stream.XMLInputFactory 属性

属性 描述
isValidating 打开实现特定的验证。
isCoalescing (必需) 要求处理器合并相邻的字符数据。
isNamespaceAware 关闭命名空间支持。所有实现必须支持命名空间。对非命名空间感知文档的支持是可选的。
isReplacingEntityReferences (必需) 要求处理器用其替换值替换内部实体引用,并将其报告为字符或描述实体的事件集。
isSupportingExternalEntities (必需) 要求处理器解析外部解析实体。
reporter (必需) 设置并获取XMLReporter接口的实现。
resolver (必需) 设置并获取XMLResolver接口的实现。
allocator (必需) 设置并获取XMLEventAllocator接口的实现。

XMLOutputFactory

通过在类上调用newInstance方法来创建抽象类XMLOutputFactory的新实例。然后使用静态方法XMLOutputFactory.newInstance来创建一个新的工厂实例。用于获取实例的算法与XMLInputFactory相同,但引用javax.xml.stream.XMLOutputFactory系统属性。

XMLOutputFactory 只支持一个属性,即javax.xml.stream.isRepairingNamespaces。此属性是必需的,其目的是创建默认前缀并将其与命名空间 URI 关联起来。有关更多信息,请参阅 StAX 规范。

XMLEventFactory

通过在类上调用newInstance方法来创建抽象类XMLEventFactory的新实例。然后使用静态方法XMLEventFactory.newInstance来创建一个新的工厂实例。此工厂引用javax.xml.stream.XMLEventFactory属性来实例化工厂。用于获取实例的算法与XMLInputFactoryXMLOutputFactory相同,但引用javax.xml.stream.XMLEventFactory系统属性。

XMLEventFactory 没有默认属性。

Java 中文官方教程 2022 版(四十)(2)https://developer.aliyun.com/article/1488166

相关文章
|
7月前
|
JavaScript NoSQL Java
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
363 96
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
|
3月前
|
Oracle Java 关系型数据库
java 编程基础入门级超级完整版教程详解
这份文档是针对Java编程入门学习者的超级完整版教程,涵盖了从环境搭建到实际项目应用的全方位内容。首先介绍了Java的基本概念与开发环境配置方法,随后深入讲解了基础语法、控制流程、面向对象编程的核心思想,并配以具体代码示例。接着探讨了常用类库与API的应用,如字符串操作、集合框架及文件处理等。最后通过一个学生成绩管理系统的实例,帮助读者将理论知识应用于实践。此外,还提供了进阶学习建议,引导学员逐步掌握更复杂的Java技术。适合初学者系统性学习Java编程。资源地址:[点击访问](https://pan.quark.cn/s/14fcf913bae6)。
306 2
|
8月前
|
消息中间件 Java 数据库
自研Java框架 Sunrays-Framework使用教程「博客之星」
### Sunrays-Framework:助力高效开发的Java微服务框架 **Sunrays-Framework** 是一款基于 Spring Boot 构建的高效微服务开发框架,深度融合了 Spring Cloud 生态中的核心技术组件。它旨在简化数据访问、缓存管理、消息队列、文件存储等常见开发任务,帮助开发者快速构建高质量的企业级应用。 #### 核心功能 - **MyBatis-Plus**:简化数据访问层开发,提供强大的 CRUD 操作和分页功能。 - **Redis**:实现高性能缓存和分布式锁,提升系统响应速度。 - **RabbitMQ**:可靠的消息队列支持,适用于异步
自研Java框架 Sunrays-Framework使用教程「博客之星」
|
9月前
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
8416 5
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
8月前
|
Java 数据库连接 数据处理
探究Java异常处理【保姆级教程】
Java 异常处理是确保程序稳健运行的关键机制。它通过捕获和处理运行时错误,避免程序崩溃。Java 的异常体系以 `Throwable` 为基础,分为 `Error` 和 `Exception`。前者表示严重错误,后者可细分为受检和非受检异常。常见的异常处理方式包括 `try-catch-finally`、`throws` 和 `throw` 关键字。此外,还可以自定义异常类以满足特定需求。最佳实践包括捕获具体异常、合理使用 `finally` 块和谨慎抛出异常。掌握这些技巧能显著提升程序的健壮性和可靠性。
133 4
|
8月前
|
存储 移动开发 算法
【潜意识Java】Java基础教程:从零开始的学习之旅
本文介绍了 Java 编程语言的基础知识,涵盖从简介、程序结构到面向对象编程的核心概念。首先,Java 是一种高级、跨平台的面向对象语言,支持“一次编写,到处运行”。接着,文章详细讲解了 Java 程序的基本结构,包括包声明、导入语句、类声明和 main 方法。随后,深入探讨了基础语法,如数据类型、变量、控制结构、方法和数组。此外,还介绍了面向对象编程的关键概念,例如类与对象、继承和多态。最后,针对常见的编程错误提供了调试技巧,并总结了学习 Java 的重要性和方法。适合初学者逐步掌握 Java 编程。
145 1
|
9月前
|
NoSQL Java 关系型数据库
Liunx部署java项目Tomcat、Redis、Mysql教程
本文详细介绍了如何在 Linux 服务器上安装和配置 Tomcat、MySQL 和 Redis,并部署 Java 项目。通过这些步骤,您可以搭建一个高效稳定的 Java 应用运行环境。希望本文能为您在实际操作中提供有价值的参考。
556 26
|
8月前
|
前端开发 Java 开发工具
Git使用教程-将idea本地Java等文件配置到gitte上【保姆级教程】
本内容详细介绍了使用Git进行版本控制的全过程,涵盖从本地仓库创建到远程仓库配置,以及最终推送代码至远程仓库的步骤。
415 0
|
9月前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
|
9月前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)

热门文章

最新文章