跨境互联网 跨境互联网
首页
  • AI 工具

    • 绘图提示词工具 (opens new window)
    • ChatGPT 指令 (opens new window)
  • ChatGPT

    • ChatGP T介绍
    • ChatGPT API 中文开发手册
    • ChatGPT 中文调教指南
    • ChatGPT 开源项目
  • Midjourney

    • Midjourney 文档
  • Stable Diffusion

    • Stable Diffusion 文档
  • 其他

    • AIGC 热门文章
    • 账号合租 (opens new window)
    • 有趣的网站
  • Vue

    • Vue3前置
  • JAVA基础

    • Stream
    • Git
    • Maven
    • 常用第三方类库
    • 性能调优工具
    • UML系统建模
    • 领域驱动设计
    • 敏捷开发
    • Java 测试
    • 代码规范及工具
    • Groovy 编程
  • 并发编程&多线程

    • 并发编程
    • 高性能队列 Disruptor
    • 多线程并发在电商系统下的应用
  • 其他

    • 面试题
  • 消息中间中间件

    • Kafka
    • RabbitMQ
    • RocketMQ
  • 任务调度

    • Quartz
    • XXL-Job
    • Elastic-Job
  • 源码解析

    • Mybatis 高级使用
    • Mybatis 源码剖析
    • Mybatis-Plus
    • Spring Data JPA
    • Spring 高级使用
    • Spring 源码剖析
    • SpringBoot 高级使用
    • SpringBoot 源码剖析
    • Jdk 解析
    • Tomcat 架构设计&源码剖析
    • Tomcat Web应用服务器
    • Zookeeper 高级
    • Netty
  • 微服务框架

    • 分布式原理
    • 分布式集群架构场景化解决方案
    • Dubbo 高级使用
    • Dubbo 核心源码剖析
    • Spring Cloud Gateway
    • Nacos 实战应用
    • Sentinel 实战应用
    • Seata 分布式事务
  • 数据结构和算法的深入应用
  • 存储

    • 图和Neo4j
    • MongoDB
    • TiDB
    • MySQL 优化
    • MySQL 平滑扩容实战
    • MySQL 海量数据存储与优化
    • Elasticsearch
  • 缓存

    • Redis
    • Aerospike
    • Guava Cache
    • Tair
  • 文件存储

    • 阿里云 OSS 云存储
    • FastDF 文件存储
  • 基础

    • Linux 使用
    • Nginx 使用与配置
    • OpenResty 使用
    • LVS+Keepalived 高可用部署
    • Jekins
  • 容器技术

    • Docker
    • K8S
    • K8S
  • 01.全链路(APM)
  • 02.电商终极搜索解决方案
  • 03.电商亿级数据库设计
  • 04.大屏实时计算
  • 05.分库分表的深入实战
  • 06.多维系统下单点登录
  • 07.多服务之间分布式事务
  • 08.业务幂等性技术架构体系
  • 09.高并发下的12306优化
  • 10.每秒100W请求的秒杀架构体系
  • 11.集中化日志管理平台的应用
  • 12.数据中台配置中心
  • 13.每天千万级订单的生成背后痛点及技术突破
  • 14.红包雨的架构设计及源码实现
  • 人工智能

    • Python 笔记
    • Python 工具库
    • 人工智能(AI) 笔记
    • 人工智能(AI) 项目笔记
  • 大数据

    • Flink流处理框架
  • 加密区

    • 机器学习(ML) (opens new window)
    • 深度学习(DL) (opens new window)
    • 自然语言处理(NLP) (opens new window)
AI 导航 (opens new window)

Revin

首页
  • AI 工具

    • 绘图提示词工具 (opens new window)
    • ChatGPT 指令 (opens new window)
  • ChatGPT

    • ChatGP T介绍
    • ChatGPT API 中文开发手册
    • ChatGPT 中文调教指南
    • ChatGPT 开源项目
  • Midjourney

    • Midjourney 文档
  • Stable Diffusion

    • Stable Diffusion 文档
  • 其他

    • AIGC 热门文章
    • 账号合租 (opens new window)
    • 有趣的网站
  • Vue

    • Vue3前置
  • JAVA基础

    • Stream
    • Git
    • Maven
    • 常用第三方类库
    • 性能调优工具
    • UML系统建模
    • 领域驱动设计
    • 敏捷开发
    • Java 测试
    • 代码规范及工具
    • Groovy 编程
  • 并发编程&多线程

    • 并发编程
    • 高性能队列 Disruptor
    • 多线程并发在电商系统下的应用
  • 其他

    • 面试题
  • 消息中间中间件

    • Kafka
    • RabbitMQ
    • RocketMQ
  • 任务调度

    • Quartz
    • XXL-Job
    • Elastic-Job
  • 源码解析

    • Mybatis 高级使用
    • Mybatis 源码剖析
    • Mybatis-Plus
    • Spring Data JPA
    • Spring 高级使用
    • Spring 源码剖析
    • SpringBoot 高级使用
    • SpringBoot 源码剖析
    • Jdk 解析
    • Tomcat 架构设计&源码剖析
    • Tomcat Web应用服务器
    • Zookeeper 高级
    • Netty
  • 微服务框架

    • 分布式原理
    • 分布式集群架构场景化解决方案
    • Dubbo 高级使用
    • Dubbo 核心源码剖析
    • Spring Cloud Gateway
    • Nacos 实战应用
    • Sentinel 实战应用
    • Seata 分布式事务
  • 数据结构和算法的深入应用
  • 存储

    • 图和Neo4j
    • MongoDB
    • TiDB
    • MySQL 优化
    • MySQL 平滑扩容实战
    • MySQL 海量数据存储与优化
    • Elasticsearch
  • 缓存

    • Redis
    • Aerospike
    • Guava Cache
    • Tair
  • 文件存储

    • 阿里云 OSS 云存储
    • FastDF 文件存储
  • 基础

    • Linux 使用
    • Nginx 使用与配置
    • OpenResty 使用
    • LVS+Keepalived 高可用部署
    • Jekins
  • 容器技术

    • Docker
    • K8S
    • K8S
  • 01.全链路(APM)
  • 02.电商终极搜索解决方案
  • 03.电商亿级数据库设计
  • 04.大屏实时计算
  • 05.分库分表的深入实战
  • 06.多维系统下单点登录
  • 07.多服务之间分布式事务
  • 08.业务幂等性技术架构体系
  • 09.高并发下的12306优化
  • 10.每秒100W请求的秒杀架构体系
  • 11.集中化日志管理平台的应用
  • 12.数据中台配置中心
  • 13.每天千万级订单的生成背后痛点及技术突破
  • 14.红包雨的架构设计及源码实现
  • 人工智能

    • Python 笔记
    • Python 工具库
    • 人工智能(AI) 笔记
    • 人工智能(AI) 项目笔记
  • 大数据

    • Flink流处理框架
  • 加密区

    • 机器学习(ML) (opens new window)
    • 深度学习(DL) (opens new window)
    • 自然语言处理(NLP) (opens new window)
AI 导航 (opens new window)
  • 基础

    • 零零散散
    • Stream
    • UML系统建模
    • 领域驱动设计
    • 敏捷开发
    • 阿里系常用代码规范及工具
    • Git
    • Maven
    • Java测试
    • 常用第三方类库
    • Groovy
      • 1. Groovy概述
        • 1.1 Groovy概述
        • 1.2 为什么要学习Groovy
      • 2. Groovy环境
        • 2.1 下载
        • 2.2 解压
        • 2.3 配置环境变量
        • 2.4 验证
        • 2.5 集成IDEA
        • 2.5.1 创建项目
        • 2.5.2 新建测试:
      • 3. Groovy语法
        • 3.1 Hello World
        • 3.2 数据类型
        • 3.3 Groovy 变量
        • 3.3.1 变量声明
        • 3.4 Groovy 字符串
        • 3.4 Groovy 运算符
        • 3.5 Groovy 循环
        • 3.5.1 while循环
        • 3.5.2 for循环
        • 3.5.3 for-in循环
        • 3.6 Groovy 方法
        • 3.6.1 方法参数
        • 3.6.2 默认参数
        • 3.6.3 方法返回值
        • 3.6.4 实例方法
        • 3.6.5 本地和外部参数名称
        • 3.7 Groovy 列表
        • 3.7.1 集合定义
        • 3.7.2 获取指定角标下的元素
        • 3.7.3 获取指定范围的元素
        • 3.7.4 添加元素
        • 3.7.5 移除元素
        • 3.7.6 元素遍历
        • 3.8 Groovy 映射
        • 3.8.1 Map定义
        • 3.8.2 获取元素值
        • 3.8.3 添加元素
        • 3.8.4 Map 遍历
        • 3.9 Groovy 面向对象
        • 3.9.1 getter和setter方法
        • 3.9.2 实例方法
        • 3.9.3 继承
        • 3.9.4 内部类
        • 3.9.5 抽象类
        • 3.9.6 接口
      • 4. 闭包
        • 4.1 什么是闭包
        • 4.1.1 闭包特点
        • 4.2 如何定义
        • 4.2.1 直接定义
        • 4.2.2 传递参数
        • 4.2.3 接受不同类型参数
        • 4.2.4 使用隐含变量 it
        • 4.2.5 作为一个对象使用
        • 4.3 闭包的调用
        • 4.3.1 直接调用
        • 4.3.2 参数传递
        • 4.3.3 闭包返回结果
        • 4.3.4 通过call调用
        • 4.3.5 闭包的参数
      • 5. Groovy 文件I/O操作
        • 5.1 读取文件
        • 5.2 读取文件的内容到字符串
        • 5.3 写入文件
        • 5.4 测试文件是否是目录
        • 5.5 创建目录
        • 5.6 删除文件
        • 5.7 复制文件
        • 5.8 获取目录内容
      • 6. Java运行Groovy脚本
        • 6.1 核心涉及
        • 6.2 环境搭建
        • 6.2.1 添加maven环境
        • 6.2.2 创建Groovy脚本装载类
        • 6.3 定义代码
        • 6.3.1 定义java接口
        • 6.3.2 定义Groovy实现
        • 6.3.3 Java进行调用
        • 6.4 测试
        • 6.4.1 测试代码
        • 6.5 动态修改
        • 6.5.1 再次测试
  • 设计模式

  • 并发编程

  • JVM与性能调优

  • 字节码增强技术

  • java
  • 基础
Revin
2023-08-02
目录

Groovy

hm

# 1. Groovy概述

# 1.1 Groovy概述

Apache Groovy 编程语言 (groovy-lang.org) (opens new window)

image-20220704100412771

​ Groovy是用于Java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言中的其他特性。

​ Groovy是从Java衍生出来的,并且运行在Java虚拟机上的语言.其目标是不管作为脚本语言,还是编程语言,都可以简单、直接使用。Groovy 也并不会替代 Java,而是相辅相成、互补的关系,具体使用哪门语言这取决于要解决的问题和使用的场景。

# 1.2 为什么要学习Groovy

  • Groovy基于JVM,这使我能够调用产品的Java代码,也能够调用Java标准库里的代码。除些之外,还可以通过Maven或Gradle使用大量的第三方Java库。
  • Groovy是动态语言,扩展了Java的容器类,提供了完善的函数式编程和元编程支持。这让我们可以写出简洁而富有表现力的代码。
  • Groovy提供了大量的语法糖。与Java自身繁冗的代码相比,这些语法糖大大节约了我们编写脚本的时间,减少了我的脚本的代码量。

# 2. Groovy环境

官网下载地址:https://groovy.apache.org/download.html

# 2.1 下载

image-20220704102135636

# 2.2 解压

image-20220704102521552

# 2.3 配置环境变量

  • 在 Path 环境变量中添加 Groovy 的bin 目录路径

image-20220704102946430

WM_Groovy(1)_Page2_04

# 2.4 验证

启动命令窗口,输入groovy -version指令,打印如下图,即为安装成功。

image-20220704103212343

# 2.5 集成IDEA

# 2.5.1 创建项目

新建Project,选择Groovy,注意要选择Project SDK和Groovy library,默认是没有Groovy library的,选择右边的 create 按钮 ,之后选择自己安装groovy的目录即可,然后点击next

image-20220704103908208

# 2.5.2 新建测试:

public class Groovy_H {


    public static void main(String[] args) {
        System.out.println("hello groovy");
    }
}
1
2
3
4
5
6
7

# 3. Groovy语法

# 3.1 Hello World

image-20220704105528154

class Groovy_T {


    // Groovy注释标记和Java一样,支持 //或者/**/
     static void main(String[] args) {
         // 使用 println 就可打印输出,并且类和方法默认就是public,可以不用写 (分号也可省略)
        println("hello groovy")


    }
1
2
3
4
5
6
7
8
9
10
  • 使用 println 就可打印输出,并且类和方法默认就是public,可以不用写
  • Groovy注释标记和Java一样,支持 //或者/**/
  • Groovy语句可以不用分号结尾

# 3.2 数据类型

Groovy 的内置数据类型和 Java 一样有8种。byte、short、int、long、float、double、char、boolean并且都有其对应的封装类

此外,以下类可用于支持高精度计算 -

名称 描述 例如
java.math.BigInteger 不可变的任意精度的有符号整数数字 30克
java.math.BigDecimal 不可变的任意精度的有符号十进制数 3.5克

# 3.3 Groovy 变量

Groovy中的变量可以通过两种方式定义 - 使用数据类型的本地语法,或者使用def关键字。对于变量定义,必须明确提供类型名称或在替换中使用"def"。这是Groovy解析器需要的。

Groovy 提供了def关键字供使用,它可以省略变量类型的定义,根据变量的值进行类型推导。

# 3.3.1 变量声明

变量声明告诉编译器为变量创建存储的位置和大小。

下面是一个变量声明的例子 -

class Example { 
   static void main(String[] args) { 
      // x is defined as a variable 
       String x = "Hello";
       def var = "测试变量"
        
      // The value of the variable is printed to the console 
       println(x);
       println(var)
   }
}
1
2
3
4
5
6
7
8
9
10
11

# 3.4 Groovy 字符串

Groovy提供了多种表示String字面量的方法。 Groovy中的字符串可以用单引号('),双引号(“)或三引号(”“”)括起来。

  • 单引号''-->所见即所得,'不支持'变量内插,有'特殊字符'同样的通过'反斜杠转义'
  • 双引号""-->可以通过${var}进行"变量内插(GString)" -->'常用
  • 三引号"""-->可以改变输出格式

GStrings是groovy.lang.GString的实例,并且允许文本中包含占位符,GStrings并不是String的子类,因为String类是最终类(final class)不能被继承。然而,GString与一般的字符串一样,因为Groovy能将GStrings转型为Java strings。

GString 适用于编写模板代码,因为必须动态构建字符串,上面的字符串拼接可以优化为:

       @Test
        void test99(){
            String price = '999'
            String abc2 = "价格是: ${price}";
            println(abc2)
        }
1
2
3
4
5
6

# 3.4 Groovy 运算符

运算符是一个符号,通知编译器执行特定的数学或逻辑操作。

大部分运算符和 Java 一样,如:算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符

与 Java 不同点在于,新增了 范围运算符

Groovy支持范围的概念,并在..符号的帮助下提供范围运算符的符号。下面给出了范围运算符的一个简单示例。

def range = 0..5 
1

这只是定义了一个简单的整数范围,存储到一个局部变量称为范围内的下限为0和上限为5。

以下代码段显示了如何使用各种运算符。

class Example { 
   static void main(String[] args) { 
      def range = 5..10; 
      println(range); 
      println(range.get(2)); 
   } 
}
1
2
3
4
5
6
7

当我们运行上面的程序,我们会得到以下结果 -

从println语句中,可以看到显示在range语句中定义的整个数字范围。

get语句用于从定义的范围中获取一个对象,它将索引值作为参数。

[5, 6, 7, 8, 9, 10] 
7
1
2

# 3.5 Groovy 循环

# 3.5.1 while循环

while语句首先通过计算条件表达式(布尔值)来执行,如果结果为真,则执行while循环中的语句。

class Example {
   static void main(String[] args) {
      int count = 0;
        
      while(count<5) {
         println(count);
         count++;
      }
   }
}
1
2
3
4
5
6
7
8
9
10

通过首先计算条件表达式(布尔值)来执行 while 语句,如果结果为true,则执行while循环中的语句。从while语句中的条件的评估开始重复该过程 此循环继续,直到条件计算为false。当条件变为假时,循环终止。 然后程序逻辑继续紧跟在while语句之后的语句

# 3.5.2 for循环

for语句用于遍历一组值。

class Example { 
   static void main(String[] args) {
    
      for(int i = 0;i<5;i++) {
         println(i);
      }     
   }
} 
1
2
3
4
5
6
7
8

# 3.5.3 for-in循环

class Example { 
   static void main(String[] args) { 
      int[] array = [0,1,2,3]; 
        
      for(int i in array) { 
         println(i); 
      } 
   } 
}
1
2
3
4
5
6
7
8
9

在上面的例子中,我们首先初始化一个具有0,1,2和3的4个值的整数数组。然后我们使用for循环语句首先定义一个变量i,然后遍历数组中的所有整数 并相应地打印值。

for-in 语句也可用于循环范围。以下示例说明如何完成此操作:

class Example {
   static void main(String[] args) {
    
      for(int i in 1..5) {
         println(i);
      }
        
   } 
} 
1
2
3
4
5
6
7
8
9

for-in 语句也可用于循环访问Map

class Example {
   static void main(String[] args) {
      def employee = ["Ken" : 21, "John" : 25, "Sally" : 22];
        
      for(emp in employee) {
         println(emp);
      }
   }
}
1
2
3
4
5
6
7
8
9

# 3.6 Groovy 方法

Groovy 中的方法是使用返回类型或使用 def 关键字定义的。方法可以接收任意数量的参数。定义参数时,不必显式定义类型。可以添加修饰符,如 public,private 和 protected。默认情况下,如果未提供可见性修饰符,则该方法为 public。

最简单的方法是没有参数的方法,如下所示:

def methodName() { 
   //Method code 
}
1
2
3

下面是一个简单方法的例子

class Example {
   static def DisplayName() {
      println("This is how methods work in groovy");
      println("This is an example of a simple method");
   } 
    
   static void main(String[] args) {
      DisplayName();
   } 
}
1
2
3
4
5
6
7
8
9
10

在上面的例子中,DisplayName 是一个简单的方法,它由两个 println 语句组成,用于向控制台输出一些文本。在我们的静态 main 方法中,我们只是调用 DisplayName 方法。上述方法的输出将是 -

This is how methods work in groovy 
This is an example of a simple method
1
2

# 3.6.1 方法参数

如果一个方法的行为由一个或多个参数的值确定,则它通常是有用的。我们可以使用方法参数将值传递给被调用的方法。请注意,参数名称必须彼此不同。

使用参数的最简单的方法类型,如下所示 −

def methodName(parameter1, parameter2, parameter3) { 
   // Method code goes here 
}
1
2
3

以下是使用参数的简单方法的示例

class Example {
   static void sum(int a,int b) {
      int c = a+b;
      println(c);
   }  
    
   static void main(String[] args) {
      sum(10,5);
   } 
}
1
2
3
4
5
6
7
8
9
10

在这个例子中,我们创建一个带有 2 个参数 a 和 b 的 sum 方法。两个参数都是 int 类型。然后我们从我们的 main 方法中调用 sum 方法,并将值传递给变量 a 和 b。

然后我们从我们的 main 方法中调用 sum 方法,并将值传递给变量 a 和 b。

上述方法的输出将是值 15。

# 3.6.2 默认参数

Groovy 中还有一个规定来指定方法中的参数的默认值。 如果没有值传递给参数的方法,则使用缺省值。 如果使用非默认和默认参数,则必须注意,默认参数应在参数列表的末尾定义。

以下是使用参数的简单方法的示例 -

def someMethod(parameter1, parameter2 = 0, parameter3 = 0) { 
   // Method code goes here 
} 
1
2
3

让我们看看我们之前看到的添加两个数字的相同示例,并创建一个具有一个默认和另一个非默认参数的方法 -

class Example { 
   static void sum(int a,int b = 5) { 
      int c = a+b; 
      println(c); 
   } 
    
   static void main(String[] args) {
      sum(6); 
   } 
}
1
2
3
4
5
6
7
8
9
10

在这个例子中,我们创建一个具有两个参数 a 和 b 的 sum 方法。两个参数都是 int 类型。此示例和上一个示例的区别在于,在这种情况下,我们将 b 的默认值指定为5。 因此,当我们从 main 方法中调用 sum 方法时,我们可以选择只传递一个值为6的值,并将其分配给 sum 方法中的参数 a。

上述方法的输出将为值 11。

class Example {
   static void sum(int a,int b = 5) {
      int c = a+b;
      println(c);
   } 
    
   static void main(String[] args) {
      sum(6,6);
   } 
}
1
2
3
4
5
6
7
8
9
10

我们也可以通过传递 2 个值来调用 sum 方法,在上面的例子中,我们传递 2 个值 6 第二个值 6 实际上将替换分配给参数 b 的默认值。

上述方法的输出将是值 12。

# 3.6.3 方法返回值

方法也可以将值返回到调用程序。 这在现在编程语言中是必需的,其中方法执行某种计算,然后将所需值返回到调用方法。

下面是一个带有返回值的简单方法的例子。

class Example {
   static int sum(int a,int b = 5) {
      int c = a+b;
      return c;
   } 
    
   static void main(String[] args) {
      println(sum(6));
   } 
}
1
2
3
4
5
6
7
8
9
10

在我们上面的例子中,注意这次我们为我们的方法 sum 指定一个类型为 int 的返回类型。 在方法中,我们使用 return 语句将 sum 值发送到调用主程序。 由于方法的值现在可用于 main 方法,因此我们使用 println 函数在控制台中显示该值。

在前面的例子中,我们将我们的方法定义为静态方法,这意味着我们可以直接从类中访问这些方法。方法的下一个示例是实例方法,其中通过创建类的对象来访问方法。我们将在后面的章节中看到类,现在我们将演示如何使用方法。

上述方法的输出将为值 11。

# 3.6.4 实例方法

方法通常在 Groovy 中的类中实现,就像 Java 语言一样。类只是一个蓝图或模板,用于创建定义其属性和行为的不同对象。类对象显示由其类定义的属性和行为。因此,通过在类中创建方法来定义行为。

以下是如何实现方法的示例。

class Example { 
   int x; 
    
   public int getX() { 
      return x; 
   } 
    
   public void setX(int pX) { 
      x = pX; 
   } 
    
   static void main(String[] args) { 
      Example ex = new Example(); 
      ex.setX(100); 
      println(ex.getX()); 
   } 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

在我们上面的例子中,这次我们没有为类方法指定静态属性。在我们的 main 函数中,我们实际上创建了一个 Example 类的实例,然后调用 'ex' 对象的方法。

上述方法的输出将是值 100。

# 3.6.5 本地和外部参数名称

Groovy 提供的设施就像java一样具有本地和全局参数。在下面的示例中,lx 是一个局部参数,它只具有 getX() 函数内的作用域,x 是一个全局属性,可以在整个 Example 类中访问。如果我们尝试访问 getX() 函数之外的变量 lx,我们将得到一个错误。

class Example { 
   static int x = 100; 
    
   public static int getX() { 
      int lx = 200; 
      println(lx); 
      return x; 
   } 
    
   static void main(String[] args) { 
      println getX() 
   }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13

当我们运行上面的程序,我们会得到以下结果。

200 
100
1
2

# 3.7 Groovy 列表

# 3.7.1 集合定义

定义一个列表集合的方式有点像 Java 中的定义数组一样,默认的类型就是 ArrayList

//1. 在 Groovy 中定义的集合默认就是对应于 Java 中 ArrayList 集合
def list = [1,2,3,4,5]
println list
//2. 在集合中可以介绍任意类型的数据,例如当前传入的是数字,字符串,boolean值
list = ['张三',true,5]
println list
1
2
3
4
5
6

# 3.7.2 获取指定角标下的元素

可以通过角标访问集合指定位置的元素,正数角标是从0位置左往右算起,负数角标是从0位置往反方向算。

下面的代码片段中出现的负数角标,就有别于 JAVA ,因为在 JAVA 中出现负数角标,基本就会报异常了,0 就是第一个位置的元素,-1就是最后一个位置的元素,一次类推即可

def list = [1,2,3,4,5]
//从头开始获取到第二个元素
println list[1]
//从末尾开始获取倒数第一个元素
println list[-1]
1
2
3
4
5

# 3.7.3 获取指定范围的元素

list[index1..index2]取出指定范围的元素

def list = [1,2,3,4,5]
//打印第二个元素到第四个元素
println list[2..4]
1
2
3

# 3.7.4 添加元素

在列表集合中添加元素的方式有以下三种

def list = [1, 2, 3, 4, 5]
//add 添加元素
list.add 6
//leftShift 添加元素
list.leftShift 7
//<< 添加元素
list << 8


print list
1
2
3
4
5
6
7
8
9
10

# 3.7.5 移除元素

def list = [1, 2, 3, 4, 5]
//通过下标移除元素
list.remove 2
//移除元素为2的对象
list.remove((Object)2)
//删除最后一个元素
list.removeLast()
println list
1
2
3
4
5
6
7
8

# 3.7.6 元素遍历

在 Groovy 中使用 each 来遍历集合,在遍历时,可以选择是否带有角标来选择不同的遍历方法

def list = [1, 2, 3, 4, 5]
//不带有角标的遍历,类似于 java 中的 foreach
list.each { println "元素的结果是${it}" }
//带有角标的遍历,类似于普通的for循环
list.eachWithIndex { int value, int index ->
    println "value is ${value} and index is ${index}"
}
1
2
3
4
5
6
7

# 3.8 Groovy 映射

映射(也称为关联数组,字典,表和散列)是对象引用的无序集合。Map集合中的元素由键值访问。 Map中使用的键可以是任何类。当我们插入到Map集合中时,需要两个值:键和值。

# 3.8.1 Map定义

Map 集合的定义有别于 Java 的定义方式,格式如下def map = [key1:value1,key2:value2,...]

Groovy 中定义的 Map 默认类型是 java.util.LinkedHashMap

def map = [name: "张三", age: 18]
println map.getClass()
1
2

WM_Groovy(1)_Page13_01

# 3.8.2 获取元素值

Map 集合中指定 key 下的值有有两种方式

def map = [name: "张三", age: 25]
//通过java的方式获取value
println map.get("name")
//通过中括号方式获取value
println map["name"]
//通过"."的方式获取value
println map.name
//还可以通过GString的方式进行访问
println "姓名是:${map['name']},年龄是:${map.age}"
1
2
3
4
5
6
7
8
9

# 3.8.3 添加元素

有两种方式可以添加map的值

def map = [name: "张三", age: 25]
//通过中括号进行设置值
map['sex'] = "男"
//通过”.“的方式来设置值
map.level = 10
println map
1
2
3
4
5
6

# 3.8.4 Map 遍历

跟 List 集合一样,Map 集合的遍历也是使用 each 方法来实现。

def map = [name: "张三", age: 25]
//不带角标的遍历
map.each { println "key:${it.key},value:${it.value}" }
//带角标的方式遍历
map.eachWithIndex { Map.Entry entry, int i ->
    {
        println entry.key + "-" + entry.value + " index = " + i
    }
}
1
2
3
4
5
6
7
8
9

# 3.9 Groovy 面向对象

在Groovy中,如在任何其他面向对象语言中一样,存在类和对象的概念以表示编程语言的对象定向性质。Groovy类是数据的集合和对该数据进行操作的方法。在一起,类的数据和方法用于表示问题域中的一些现实世界对象。

Groovy中的类声明了该类定义的对象的状态(数据)和行为。因此,Groovy类描述了该类的实例字段和方法。

以下是Groovy中的一个类的示例。类的名称是Student,它有两个字段 - StudentID和StudentName。在main函数中,我们创建一个这个类的对象,并将值分配给对象的StudentID和StudentName。

class Student {
   int StudentID;
   String StudentName;
    
   static void main(String[] args) {
      Student st = new Student();
      st.StudentID = 1;
      st.StudentName = "Joe"     
   } 
}
1
2
3
4
5
6
7
8
9
10

# 3.9.1 getter和setter方法

在任何编程语言中,总是使用private关键字隐藏实例成员,而是提供getter和setter方法来相应地设置和获取实例变量的值。以下示例显示如何完成此操作。

class Student {
   private int StudentID;
   private String StudentName;
    
   void setStudentID(int pID) {
      StudentID = pID;
   }
    
   void setStudentName(String pName) {
      StudentName = pName;
   }
    
   int getStudentID() {
      return this.StudentID;
   }
    
   String getStudentName() {
      return this.StudentName;
   }
    
   static void main(String[] args) {
      Student st = new Student();
      st.setStudentID(1);
      st.setStudentName("Joe");
        
      println(st.getStudentID());
      println(st.getStudentName());
   } 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

当我们运行上面的程序,我们将得到以下结果 -

1 
Joe 
1
2

请注意以下关于上述程序的要点 -

  • 在类中,studentID和studentName都标记为private,这意味着无法从类外部访问它们。
  • 每个实例成员都有自己的getter和setter方法。getter方法返回实例变量的值,例如方法int getStudentID()和setter方法设置实例ID的值,例如method - void setStudentName(String pName)

# 3.9.2 实例方法

在类中包含更多的方法通常是一个很自然的事情,它实际上为类实现了一些功能。在我们的学生示例中,让我们添加Marks1,Marks2和Marks3的实例成员,以表示学生在3个科目中的标记。然后我们将添加一个新的实例方法,计算学生的总分。以下是代码的外观。

在下面的示例中,Total方法是一个额外的Instance方法,它内置了一些逻辑。

class Student {
   int StudentID;
   String StudentName;
    
   int Marks1;
   int Marks2;
   int Marks3;
    
   int Total() {
      return Marks1+Marks2+Marks3;
   }
    
   static void main(String[] args) {
      Student st = new Student();
      st.StudentID = 1;
      st.StudentName="Joe";
        
      st.Marks1 = 10;
      st.Marks2 = 20;
      st.Marks3 = 30;
        
      println(st.Total());
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

当我们运行上面的程序,我们将得到以下结果 -

60
1

# 3.9.3 继承

继承可以定义为一个类获取另一个类的属性(方法和字段)的过程。通过使用继承,信息以分级顺序可管理。

继承其他属性的类称为子类(派生类,子类),属性继承的类称为超类(基类,父类)。

extends是用于继承类的属性的关键字。下面给出了extends关键字的语法。在下面的例子中,我们做了以下事情 -

  • 创建一个名为Person的类。这个类有一个名为name的实例成员。
  • 创建一个名为Student的类,它从Person类继承。请注意,在Person类中定义的名称实例成员在Student类中继承。
  • 在Student类构造函数中,我们调用了基类构造函数。
  • 在我们的Student类中,我们添加了2个StudentID和Marks1的实例成员。
class Example {
   static void main(String[] args) {
      Student st = new Student();
      st.StudentID = 1;
        
      st.Marks1 = 10;
      st.name = "Joe";
        
      println(st.name);
   }
} 


class Person {
   public String name;
   public Person() {}  
} 


class Student extends Person {
   int StudentID
   int Marks1;
    
   public Student() {
      super();
   } 
}   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

当我们运行上面的程序,我们将得到以下结果 -

Joe
1

# 3.9.4 内部类

内部类在另一个类中定义。封闭类可以像往常一样使用内部类。另一方面,内部类可以访问其封闭类的成员,即使它们是私有的。不允许除封闭类之外的类访问内部类。

下面是一个外部和内部类的例子。在下面的例子中,我们做了以下事情 -

  • 创建一个名为Outer的类,它将是我们的外部类。
  • 在Outer类中定义名为name的字符串。
  • 在我们的外类中创建一个内部或嵌套类。
  • 请注意,在内部类中,我们可以访问在Outer类中定义的名称实例成员。
class Example { 
   static void main(String[] args) { 
      Outer outobj = new Outer(); 
      outobj.name = "tom"; 
      outobj.callInnerMethod() 
   } 
} 


class Outer { 
   String name;
    
   def callInnerMethod() { 
      new Inner().methodA() 
   } 
    
   class Inner {
      def methodA() { 
         println(name); 
      } 
   } 
    
}   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

当我们运行上面的程序,我们将得到以下结果 -

tom
1

# 3.9.5 抽象类

抽象类表示通用概念,因此,它们不能被实例化,被创建为子类化。他们的成员包括字段/属性和抽象或具体方法。抽象方法没有实现,必须通过具体子类来实现。抽象类必须用抽象关键字声明。抽象方法也必须用抽象关键字声明。

在下面的示例中,请注意,Person类现在是一个抽象类,不能被实例化。还要注意,在抽象类中有一个名为DisplayMarks的抽象方法,没有实现细节。在学生类中,必须添加实现细节。

class Example { 
   static void main(String[] args) { 
      Student st = new Student(); 
      st.StudentID = 1;
        
      st.Marks1 = 10; 
      st.name="Joe"; 
        
      println(st.name); 
      println(st.DisplayMarks()); 
   } 
} 


abstract class Person { 
   public String name; 
   public Person() { } 
   abstract void DisplayMarks();
}
 
class Student extends Person { 
   int StudentID 
   int Marks1; 
    
   public Student() { 
      super(); 
   } 
    
   void DisplayMarks() { 
      println(Marks1); 
   }  
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

当我们运行上面的程序,我们将得到以下结果 -

Joe 
10 
1
2

# 3.9.6 接口

接口定义了类需要遵守的契约。接口仅定义需要实现的方法的列表,但是不定义方法实现。需要使用interface关键字声明接口。接口仅定义方法签名。接口的方法总是公开的。在接口中使用受保护或私有方法是一个错误。

以下是groovy中的接口示例。在下面的例子中,我们做了以下事情 -

  • 创建一个名为Marks的接口并创建一个名为DisplayMarks的接口方法。
  • 在类定义中,我们使用implements关键字来实现接口。 因为我们是实现
  • 因为我们正在实现接口,我们必须为DisplayMarks方法提供实现。
class Example {
   static void main(String[] args) {
      Student st = new Student();
      st.StudentID = 1;
      st.Marks1 = 10;
      println(st.DisplayMarks());
   } 
} 


interface Marks { 
   void DisplayMarks(); 
} 


class Student implements Marks {
   int StudentID
   int Marks1;
    
   void DisplayMarks() {
      println(Marks1);
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

当我们运行上面的程序,我们将得到以下结果 -

10
1

# 4. 闭包

闭包是Groovy语言的精髓之一,Groovy的闭包大大简化了容器的遍历,提升了代码的可扩展性,使代码更加简洁优雅,闭包在Groovy编程中几乎无处不在。

# 4.1 什么是闭包

闭包(Closure)是很多编程语言中很重要的概念,那么Groovy中闭包是什么,官方定义是“Groovy中的闭包是一个开放,匿名的代码块,可以接受参数,返回值并分配给变量”,简而言之,他说一个匿名的代码块,可以接受参数,有返回值,那么到底是怎么样的

# 4.1.1 闭包特点

groovy 中的闭包是一个开放的匿名代码块,可以接受参数,返回值可以赋值给变量。

我们通常学习的 lamb 表达式是有一定封闭空间,无法访问闭包的变量

int anser = 42;
Supplier supplier = () -> anser;
//这一行注释解开那将会报错,不能进行编译
//anser = 45;
System.out.println(supplier.get());
1
2
3
4
5

而在 groovy 的闭包是可以访问到外部变量的,虽然这样更自由但是也打破这个封闭空间

int anser = 42;
def supplier = { anser }
anser = 45;
println supplier.call()
1
2
3
4

# 4.2 如何定义

定义闭的语意 :{ [closureParameters -> ] statements },其中[closureParameters->]代表参数们,多参数用逗号分割,用->隔开参数与内容,没有参数可以不写->,其中[]内是可选的闭包参数,可省略。当闭包带有参数,就需要->来将参数和闭包体相分离

# 4.2.1 直接定义

直接用大括号定义就是一个最简单的闭包

def hello = {println "hello world"}
hello()
1
2

# 4.2.2 传递参数

闭包是可以传递参数的,类似于lamda表达式的使用

def hello = {x -> println "参数值:${x}"}
hello(100)
1
2

# 4.2.3 接受不同类型参数

def info = { String name,int age -> name + "," + age}
println info("matthew",30)
1
2

# 4.2.4 使用隐含变量 it

有时候我们看到闭包没有传递参数但是可以使用参数it,这是使用到了闭包的隐含参数

{it -> println "she is ${it}" }
//上面的闭包可以利用隐含参数简化为
{println "she is ${it}"}
1
2
3

# 4.2.5 作为一个对象使用

闭包在groovy中是groovy.lang.Closure类的实例,这使得闭包可以赋值给变量或字段。

def  closure  = {println "hello world"}
assert closure instanceof Closure
Closure callback = {println "方法执行成功"}
Closure<Boolean> isOK = {boolean flag->  if(flag) return true}
1
2
3
4

# 4.3 闭包的调用

# 4.3.1 直接调用

上面我们已经了解到了如何调用闭包,最简单的方式就是直接调用闭包加上一个括号就可以调用了,括号里面可以传递闭包参数

def hello = {println "hello world"}
hello();
1
2

# 4.3.2 参数传递

闭包是可以传递参数的,如果有参数是可以通过括号进行传递的,如果多个参数可以在后面加上逗号

def hello = {x -> println "参数值:${x}"}
hello(100)
def info = { String name,int age -> name + "," + age}
println info("matthew",30)
1
2
3
4

# 4.3.3 闭包返回结果

因为闭包是一个函数,所以可以直接调用就可以获取返回结果

def sqrt = { x -> return x * x }
def result = sqrt(10)
println result
//也可以直接输出
def sqrt = { x -> return x * x }
println sqrt(10)
1
2
3
4
5
6

# 4.3.4 通过call调用

上面都是通过直接函数调用来进行调用,还可以通过call来进行调用

def sqrt = { x -> return x * x }
println sqrt.call(10)
1
2

# 4.3.5 闭包的参数

  • 参数是可以有类型也可以不定义类型
  • 有一个隐含参数 it
  • 接受可变参数
def greeting =  { String ...name ->  "hey ${name.join(" ")}" } 
println greeting("matthew","jerry")
1
2

# 5. Groovy 文件I/O操作

Groovy在使用I / O时提供了许多辅助方法,Groovy提供了更简单的类来为文件提供以下功能。

  • 读取文件
  • 写入文件
  • 遍历文件树
  • 读取和写入数据对象到文件

除此之外,您始终可以使用下面列出的用于文件I / O操作的标准Java类。

  • java.io.File
  • java.io.InputStream
  • java.io.OutputStream
  • java.io.Reader
  • java.io.Writer

使用java代码的基本写法

FileInputStream fin = null;
try {
    fin = new FileInputStream("E:/tmp/xxx.log");
    BufferedReader br = new BufferedReader(new InputStreamReader(fin));
    String line = null;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}  catch (FileNotFoundException e) {
    // TODO: handle exception
}  catch (IOException e) {
    // TODO: handle exception
}  finally {
    try {
        if (fin != null) {
            fin.close();
        }
    }
    catch (IOException e2) {
        // TODO: handle exception
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

对一个文件进行读取的时候,基本上都会用到上面的代码,重复的写这些代码让人感觉很枯燥,同时在阅读这样的代码的时候也极易干扰视线。真正要干的事情也就是把文件内容输出而已。

# 5.1 读取文件

以下示例将输出Groovy中的文本文件的所有行。方法eachLine内置在Groovy中的File类中,目的是确保文本文件的每一行都被读取。

import java.io.File 
class Example { 
   static void main(String[] args) { 
      new File("E:/Example.txt").eachLine {  
         line -> println "line : $line"; 
      } 
   } 
}
1
2
3
4
5
6
7
8

File类用于实例化以文件名作为参数的新对象。 然后它接受eachLine的函数,将它放到一个line的变量并相应地打印它。

如果文件包含以下行,它们将被打印。

line : Example1
line : Example2
1
2

# 5.2 读取文件的内容到字符串

如果要将文件的整个内容作为字符串获取,可以使用文件类的text属性。以下示例显示如何完成此操作。

class Example { 
   static void main(String[] args) { 
      File file = new File("E:/Example.txt") 
      println file.text 
   } 
}
1
2
3
4
5
6

如果该文件包含以下行,它们将被打印出来。

line : Example1 
line : Example2
1
2

# 5.3 写入文件

如果你想写入文件,你需要使用作家类输出文本到一个文件中。下面的例子说明了如何可以做到这一点。

import java.io.File 
class Example { 
   static void main(String[] args) { 
      new File('E:/','Example.txt').withWriter('utf-8') { 
         writer -> writer.writeLine 'Hello World' 
      }  
   } 
}
1
2
3
4
5
6
7
8

如果你打开文件example.txt文件,您将看到文本中打印了“Hello World”这个词。

# 5.4 测试文件是否是目录

如果要查看路径是文件还是目录,可以使用File类的isFile和isDirectory选项。以下示例显示如何完成此操作。

class Example { 
   static void main(String[] args) { 
      def file = new File('E:/') 
      println "File? ${file.isFile()}" 
      println "Directory? ${file.isDirectory()}" 
   } 
}
1
2
3
4
5
6
7

上面的代码将显示以下输出 -

File? false 
Directory? True
1
2

# 5.5 创建目录

如果要创建一个新目录,可以使用File类的mkdir函数。以下示例显示如何完成此操作。

class Example {
   static void main(String[] args) {
      def file = new File('E:/Directory')
      file.mkdir()
   } 
}
1
2
3
4
5
6

如果目录E:\ Directory不存在,将创建它。

# 5.6 删除文件

如果要删除文件,可以使用File类的delete功能。以下示例显示如何完成此操作。

class Example {
   static void main(String[] args) {
      def file = new File('E:/Example.txt')
      file.delete()
   } 
}
1
2
3
4
5
6

如果存在该文件将被删除。

# 5.7 复制文件

Groovy还提供将内容从一个文件复制到另一个文件的功能。以下示例显示如何完成此操作。

class Example {
   static void main(String[] args) {
      def src = new File("E:/Example.txt")
      def dst = new File("E:/Example1.txt")
      dst << src.text
   } 
}
1
2
3
4
5
6
7

将创建文件Example1.txt,并将文件Example.txt的所有内容复制到此文件。

# 5.8 获取目录内容

以下示例显示如何使用File类的eachFile函数列出特定目录中的文件。

class Example {
   static void main(String[] args) {
      new File("E:/Temp").eachFile() {  
         file->println file.getAbsolutePath()
      }
   } 
}
1
2
3
4
5
6
7

输出将显示目录E:\ Temp中的所有文件

如果要递归显示目录及其子目录中的所有文件,则可以使用File类的eachFileRecurse函数。以下示例显示如何完成此操作。

class Example { 
   static void main(String[] args) {
      new File("E:/temp").eachFileRecurse() {
         file -> println file.getAbsolutePath()
      }
   }
} 
1
2
3
4
5
6
7

输出将显示目录E:\ Temp中的所有文件及其子目录(如果存在)。

# 6. Java运行Groovy脚本

Groovy是用于Java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言中的其他特性。

可将java代码在Groovy脚本动态编码、代码被修改达到不重启服务的目的(类似于热部署)

# 6.1 核心涉及

  • ClassLoader:就是类的装载器,它使JVM可以动态的载入Java类,JVM并不需要知道从什么地方(本地文件、网络等)载入Java类,这些都由ClassLoader完成。
  • GroovyClassLoader:动态地加载一个脚本并执行它的行为。GroovyClassLoader是一个定制的类装载器,负责解释加载Java类中用到的Groovy类。

# 6.2 环境搭建

# 6.2.1 添加maven环境

<!--Groovy脚本依赖-->
<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy</artifactId>
    <version>2.5.14</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11

# 6.2.2 创建Groovy脚本装载类

public class GroovyUtils {


    private final static ClassLoader classLoader = GroovyUtils.class.getClassLoader();//获取当前类装载器
    //ClassLoader:就是类的装载器,它使JVM可以动态的载入Java类,JVM并不需要知道从什么地方(本地文件、网络等)载入Java类,这些都由ClassLoader完成。


    public final static GroovyClassLoader groovyClassLoader = new GroovyClassLoader(classLoader);
    //GroovyClassLoader:负责在运行时编译groovy源代码为Class的工作,从而使Groovy实现了将groovy源代码动态加载为Class的功能。


    /**
     * .
     * 获取实例化对象
     *
     * @param script groovy脚本内容
     * @param <T>
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static <T> T instanceTaskGroovyScript(String script) throws IllegalAccessException, InstantiationException {
        Class taskClz = groovyClassLoader.parseClass(script);
        T instance = (T) taskClz.newInstance();
        return instance;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 6.3 定义代码

# 6.3.1 定义java接口

/**
 * 文件扫描接口
 */
public interface FileScanFilter {


    public List<String> scan(String path);
}


1
2
3
4
5
6
7
8
9
10

# 6.3.2 定义Groovy实现

在resources目录下添加groovy的是实现类,并实现java的FileScanFilter接口

package groovy


import com.heima.buzz.FileScanFilter


/**
 * Groovy目录扫描是是实现
 */
class FileScanFilterImpl implements FileScanFilter {


    @Override
    List<String> scan(String path) {
        File file = new File(path)
        List<String> fileList = new ArrayList<>();
        file.eachFileRecurse {
            fileList << "${it.name}".toString()
        }
        return fileList
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 6.3.3 Java进行调用

public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException {
    //加载groovy文件
    String script = IOUtils.toString(GroovyUtils.class.getResourceAsStream("/groovy/FileScanFilterImpl.groovy"), "UTF-8");
    //读取文件并使用groovy的类加载器加载groovy文件
    FileScanFilter filter = GroovyUtils.instanceTaskGroovyScript(script);
    //扫描路径下的文件
    List<String> list = filter.scan("e:/tmp");
    //并将结果返回
    list.forEach(str-> System.out.println(str));
}
1
2
3
4
5
6
7
8
9
10

# 6.4 测试

# 6.4.1 测试代码

运行代码后可以得到以下结果

config.xml
data.json
dsl.txt
out.txt
xxx.log
xxx.txt
yiming_oa_back.zip
1
2
3
4
5
6
7

# 6.5 动态修改

我们可以在不改动java代码的情况下改动groovy代码的情况下改变业务

package groovy


import com.heima.buzz.FileScanFilter
import groovy.io.FileType


/**
 * Groovy目录扫描是是实现
 */
class FileScanFilterImpl implements FileScanFilter {


    @Override
    List<String> scan(String path) {
        File file = new File(path)
        List<String> fileList = new ArrayList<>();
        file.eachFileRecurse(FileType.FILES) {
            fileList << "文件路径:${it.path}".toString()
        }
        return fileList
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 6.5.1 再次测试

文件路径:e:\tmp\config.xml
文件路径:e:\tmp\data.json
文件路径:e:\tmp\dsl.txt
文件路径:e:\tmp\out.txt
文件路径:e:\tmp\xxx.log
文件路径:e:\tmp\xxx.txt
1
2
3
4
5
6
上次更新: 2025/04/03, 11:07:08
常用第三方类库
设计模式概述

← 常用第三方类库 设计模式概述→

最近更新
01
tailwindcss
03-26
02
PaddleSpeech
02-18
03
whisper
02-18
更多文章>
Theme by Vdoing | Copyright © 2019-2025 跨境互联网 | 豫ICP备14016603号-5 | 豫公网安备41090002410995号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式