爪哇2博客
爪哇2博客

爪哇 8流 API深入指南

在这篇文章中,我们将看到Java 8流的深入概述,其中包含许多示例和练习。

介绍

You may think that 流 must be similar to InputStream or OutputStream, but that’s not the case.

A represents a sequence 的 elements supporting sequential and 平行 aggregate operations. 流 does not store data, it operates on source data structures such as 清单, 采集,数组等

大多数流操作接受 功能接口 这使其成为lambda表达式的理想选择。

如果您不熟悉功能接口,lambda表达式和方法引用,则可能需要先阅读以下教程,然后再继续。

流操作的类型

流操作有两种类型。

  1. Intermediate operations: 返回一个流,该流可以与其他通过dot进行的中间操作链接在一起。
  2. Terminal operations: 返回void或非流输出。

让’通过简单的示例了解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
组织.Arpit.爪哇2blog.;
 
进口 爪哇.实用程序.数组;
进口 爪哇.实用程序.清单;
 
上市 流操作 {
 
    上市 静态的 虚空 主要([] args) {
        清单<> stringList = 数组.asList(“约翰”, “马丁”, “玛丽”, “史蒂夫”);
 
        stringList.()
                   .地图((s) -> s.至大写())
                   .每次(系统.:: 打印);
    }
}
 

输出:

约翰
马丁
玛丽
史蒂夫

这里,
To perform a computation, 流 operations are built 整型o a 流 pipeline. 流 pipeline consists 的:

  1. source
  2. zero or more 整型ermediate operations
  3. terminal operation.

在我们的示例中,Stream管道包括:
Source:字符串列表
1 Intermediate operation:地图
1 terminal operation:forEach

下图将使其更加清晰。
地图 is 整型ermediate operation and 前言 is terminal opertion.
流基础

大多数流操作接受描述用户定义的行为的参数,例如 lambda表达式 地图((s)-&gt;s.toUpperCase()) 传递给地图操作。

为了获得正确的行为,流参数应为:
non-interfering: 在执行Stream管线时,不应修改流源。您可以了解更多有关 干扰.
Stateless: 在大多数情况下,lambda表达式应该是无状态的。其输出不应取决于在Stream流水线执行期间可能改变的状态。我已经讲过 有状态lambda表达式 在并行流教程中。

另请阅读: 爪哇 8并行流

流创建

有多种创建流的方法。

空流

空的() 方法可用于创建空流。

1
2
3
 
s = .空的()
 

通常用于返回零元素而不是null的Stream。

收集流

流 can be created from 采集 by calling .stream() or .parallelStream()

1
2
3
4
5
6
7
 
清单 stringList=数组.asList(“安迪”,“彼得”,“艾米”,“玛丽”);
 
stringList.()
.地图((s)->s.至大写())
.每次(系统.:: 打印);
 

stringList.stream() 将返回常规对象流。

你不’无需创建集合即可获取Stream。您也可以使用 。的()

1
2
3
 
流Array =.(“X”,“ Y”,“ Z”);
 

流.generate()

生成()方法接受 供应商 用于元素生成。它创建无限的Stream,您可以通过调用limit()函数对其进行限制。

1
2
3
4
5
6
7
8
9
10
 
<整数> 整型Stream=.生成(() -> 1).限制(5);
整型Stream.每次(系统.:: 打印);
//输出
// 1
// 1
// 1
// 1
// 1
 

这将创建具有10个值为1的Integer流。

流.iterate()

流.iterate()也可用于生成无限流。

1
2
3
4
5
6
7
8
9
10
 
<整数> 整型Stream = .重复(100 , n -> n+1).限制(5);
整型Stream.每次(系统.:: 打印);
//输出
// 100
// 101
// 102
// 103
// 104
 

First parameter 的 重复 method represents first element 的 the 流. All the following elements will be 生成d by lambda表达式 n-&gt;n+1 and 限制() is used to convert infinite 流 to finite 流 with 5 elements.

懒惰评估

流是懒惰的;在遇到终端操作之前,不执行中间操作。

每个中间操作都会生成一个新流,并存储提供的操作或功能。当调用终端操作时,流管线执行开始,并且所有中间操作都一一执行。

让’借助示例可以理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
<> 名称Stream = .(“莫汉”,“约翰”,“ vaibhav”,“阿米特”);
<> 名称StartJ = 名称Stream.地图(:: 至大写)
                                    .窥视( e -> 系统..打印(e))
                                  .过滤(s -> s.以。。开始(“ J”));
 
系统..打印(“呼叫终端操作:计数”);
计数 = 名称StartJ.计数();
系统..打印(“数:”+ 计数);
//输出
//调用终端操作:计数
// MOHAN
// 约翰
// VAIBHAV
// AMIT
//计数:1
 

在前面的输出中,您可以看到,除非且直到调用了终端操作计数,否则控制台上什么都没有打印。

In the preceding example, 窥视() method is used to print the element 的 流. 窥视() method is generally used for logging and debugging purpose only.

操作顺序

让’看流如何处理操作顺序。
您能猜出程序的输出吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
<> 名称Stream = .(“莫汉”,“约翰”,“ vaibhav”,“阿米特”);
<> 名称StartJ = 名称Stream.地图(
        (s) ->
        {
            系统..打印(“地图:”+s);
            返回 s.至大写();
 
        })
        .过滤(
        (s) ->
        {
             系统..打印(“过滤器:”+s);
             返回 s.以。。开始(“ J”);
        }
    );
 
可选的<> findAny = 名称StartJ.findAny();
系统..打印(“最终输出:”+findAny.得到());
 

输出将是:

地图:莫汉
筛选条件:MOHAN
地图:约翰
筛选器:约翰
约翰

这里的操作顺序可能令人惊讶。一种常见的方法是对所有元素执行中间操作,然后执行下一个操作,但每个元素都垂直移动。

这种行为可以减少实际的操作次数。
例如:
In preceding example, 串s vaibhav and amit did not go through 地图 and 过滤 operation as we already got result(findAny()) with 串 john.

一些中间操作,例如 已排序 are executed on the entire 收藏ion. As succeding operations might depend on the result 的 已排序 operation.

原始流

除了常规流之外, 爪哇 8 还为int,long和double提供原始Stream。
原始流是:

  1. Int的IntStream
  2. 长流长
  3. DoubleStream双

所有原始流都与常规流类似,但有以下区别。

  • It supports few terminal aggregate functions such 和(), 平均(), etc.
  • 它接受专用功能接口,例如IntPredicate代替Predicate,IntConsumer代替Consumer。

这是IntStream的示例。

1
2
3
4
5
6
7
8
 
整型 = 数组.( 整型[] {1,2,3})
                .();
系统..打印();
 
//输出
// 6
 

将Stream转换为IntStream

You may need to convert 流 to 串流 to perform terminal aggregate operations such as 和 or 平均. 您可以使用 地图ToInt(), 地图ToLong() or 地图ToDouble() method to convert 流 to primitive 流s.
这是一个例子:

1
2
3
4
5
6
7
8
 
.("10","20","30")
      .地图ToInt(整数:: parseInt)
      .平均()
      .如果存在(系统.:: 打印);
//输出
// 20.0
 

将IntStream转换为流

You may need to convert 串流 to 流 to use it as any other datatype. 您可以使用 地图ToObj() convert primitive 流s to regular 流.
这是一个例子:

1
2
3
4
5
6
7
8
 
收藏 = 串流.(10,20,30)
                          .地图ToObj((i)->+i)
                          .收藏(收藏家.加盟(“-”));
系统..打印(收藏);
//输出
// 10-20-30
 

员工阶层

Consider a 雇员 类 which has two fields 名称, 年龄, listOfCities.

Here listOfCities denotes cities in which 雇员 has lived so far.

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
 
组织.Arpit.爪哇2blog.;
 
进口 爪哇.实用程序.清单;
 
上市 雇员 实施 可比<雇员>{
 
    私人的 名称;
    私人的 整型 年龄;
    私人的 清单<> listOfCities;
 
    上市 雇员( 名称, 整型 年龄,清单<> listOfCities) {
        ();
        这个.名称 = 名称;
        这个.年龄 = 年龄;
        这个.listOfCities=listOfCities;
    }
 
    上市 得到Name() {
        返回 名称;
    }
 
    上市 虚空 setName( 名称) {
        这个.名称 = 名称;
    }
 
    上市 整型 得到Age() {
        返回 年龄;
    }
 
    上市 虚空 setAge(整型 年龄) {
        这个.年龄 = 年龄;
    }
 
    上市 清单<> 得到ListOfCities() {
        返回 listOfCities;
    }
 
    上市 虚空 setListOfCities(清单<> listOfCities) {
        这个.listOfCities = listOfCities;
    }
 
    @覆写
    上市 toString() {
        返回 “员工[name =“ + 名称 + “,age =” + 年龄 + “]”;
    }
 
    @覆写
    上市 整型 相比于(雇员 o) {
        返回 这个.得到Name().相比于(o.得到Name());
    }
}
 

This 雇员 类 will be used in all succeeding examples.
让’s create 员工名单 on which we are going to perform 整型ermediate and terminal operations.

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
33
 
组织.Arpit.爪哇2blog.;
 
进口 爪哇.实用程序.数组列表;
进口 爪哇.实用程序.数组;
进口 爪哇.实用程序.清单;
 
上市 流GetListOfEmployees {
 
    上市 静态的 虚空 主要([] args) {
        清单<雇员> 员工名单=得到ListOfEmployees();
 
        //在此处编写流代码
    }
 
    上市 静态的 清单<雇员> 得到ListOfEmployees() {
 
        清单<雇员> 员工名单 = 数组列表<>();
 
        雇员 e1 = 雇员(“莫汉”, 24,数组.asList(“纽约”,孟加拉国));
        雇员 e2 = 雇员(“约翰”, 27,数组.asList(“巴黎”,“伦敦”));
        雇员 e3 = 雇员(“ Vaibhav”, 32,数组.asList(“浦那”,“西雅图”));
        雇员 e4 = 雇员(“授予”, 22,数组.asList(“金奈”,“海得拉巴”));
 
        员工名单.(e1);
        员工名单.(e2);
        员工名单.(e3);
        员工名单.(e4);
 
        返回 员工名单;
    }
}
 

常见的中间操作

地图()

地图()操作 用于转换流<T> to 流<R>. It produces one 出put result 的 type 'R' for each input value 的 type 'T'. It takes 功能 接口作为参数。
例如:
You have 流 的 list 的 雇员 and you need a list 的 employee 名称s, you simply need to convert 流<Employee> to 流<String>.

1
2
3
4
5
6
7
8
9
 
清单<> 员工姓名 = 员工名单.()
                                .地图(e -> e.得到Name())
                               .收藏(收藏家.到清单());
系统..打印(员工姓名);
 
//输出
// [Mohan,John,Vaibhav,Amit]
 

流图
地图操作的逻辑表示

您也可以使用map,即使它产生相同类型的结果。
In case, you want employee 名称 in uppercase, you can use another 地图() function to convert string to uppercase.

1
2
3
4
5
6
7
8
9
10
 
清单<> 员工姓名 = 员工名单.()
                                .地图(e -> e.得到Name())
                                .地图(s -> s.至大写())
                                .收藏(收藏家.到清单());
系统..打印(员工姓名);
 
//输出
// [MOHAN,JOHN,VAIBHAV,AMIT]
 

过滤()

Filter()操作 用于根据条件过滤流。 Filter方法采用Predicate()接口,该接口返回布尔值。
让’例如,您要向以名字开头的员工‘A’.
您可以编写以下功能代码来实现相同的目的。

1
2
3
4
5
6
7
8
9
10
 
清单<> 员工姓名 = 员工名单.()
                                .地图(e -> e.得到Name())
                                .过滤(s -> s.以。。开始(“一个”))
                               .收藏(收藏家.到清单());
系统..打印(员工姓名);
 
//输出
// [AMIT]
 

流过滤器
过滤器操作的逻辑表示

[![StreamFilter]

已排序()

您可以使用 已排序() 对对象列表进行排序的方法。不带参数的sorted方法以自然顺序对列表进行排序。 已排序()方法还接受比较器作为参数来支持自定义排序。

💡 你知道吗?

自然顺序意味着根据列表元素类型实现的可比接口对列表进行排序。
例如:
清单<Integer> 将根据Integer类实现的可比较接口进行排序。

这是sorted()方法的示例

1
2
3
4
5
6
7
8
9
 
清单<雇员> 雇员 = 员工名单.()
                                            .已排序()
                                            .收藏(收藏家.到清单());
系统..打印(雇员);
 
//输出
// [员工[name = Amit,年龄= 22],员工[name = John,年龄= 27],员工[name = Mohan,年龄= 24],员工[name = Vaibhav,年龄= 32]]
 

Here is the 已排序() method example with 比较器 作为参数。

1
2
3
4
5
6
7
8
9
 
清单<雇员> 雇员 = 员工名单.()
                              .已排序((e1,e2)->e1.得到Age() - e2.得到Age())
                               .收藏(收藏家.到清单());
系统..打印(雇员);
 
//输出
// [雇员[name = Amit,年龄= 22],雇员[name = Mohan,年龄= 24],雇员[name = John,年龄= 27],雇员[name = Vaibhav,年龄= 32]]
 

您也可以使用 方法参考 如下:

1
2
3
4
5
6
7
8
9
 
清单<雇员> 雇员 = 员工名单.()
                                                .已排序(比较器.比较(雇员:: 得到Age))
                                                .收藏(收藏家.到清单());
系统..打印(雇员);
 
//输出
// [雇员[name = Amit,年龄= 22],雇员[name = Mohan,年龄= 24],雇员[name = John,年龄= 27],雇员[name = Vaibhav,年龄= 32]]
 

限制()

您可以使用limit()限制流中元素的数量。
例如:
限制(3) 返回列表中的前3个元素。

让’借助示例查看:

1
2
3
4
5
6
7
8
9
 
清单<雇员> 雇员 = 员工名单.()
                                     .限制(3)
                                  .收藏(收藏家.到清单());
系统..打印(雇员);
 
//输出
// [员工[name = Mohan,年龄= 24],员工[name = John,年龄= 27],员工[name = Vaibhav,年龄= 32]]
 

跳跃()

跳跃(int n) 方法用于丢弃流中的前n个元素。
例如:
跳跃(3) 从流中丢弃前3个元素。

让’借助示例查看:

1
2
3
4
5
6
7
8
9
 
清单<雇员> 雇员 = 员工名单.()
                                     .跳跃(3)
                                  .收藏(收藏家.到清单());
系统..打印(雇员);
 
//输出
// [Employee [name = Amit,age = 22]]
 

flatmap()

地图() 运算为每个输入元素生成一个输出。

如果每个输入都需要多个输出怎么办?
flatmap()操作 正是用于此目的。它用于为每个输入映射多个输出。
例如:
我们要累积所有员工居住的城市清单。一名员工可能居住在多个城市,因此每个员工可能拥有一个以上的城市。

让’借助示例查看:

1
2
3
4
5
6
7
8
9
10
 
清单<> listOfCities = 员工名单.()
                                           .flatMap(e -> e.得到ListOfCities().())
                                           .收藏(收藏家.到清单());
 
系统..打印(“ listOfCities:” +listOfCities);
 
//输出
// listOfCities:[纽约,孟加拉,巴黎,伦敦,浦那,西雅图,金奈,海得拉巴]
 

常用终端操作

前言

前言() 是终端操作,用于迭代对象的收集/流。它需要 消费者 作为参数。

让’s说您要打印流中的元素。

1
2
3
4
5
6
7
8
9
10
 
员工名单.()
             .每次(系统.:: 打印);
 
//输出
//员工[姓名= Mohan,年龄= 24]
//员工[姓名=约翰,年龄= 27]
//员工[姓名= Vaibhav,年龄= 32]
//员工[name = Amit,年龄= 22]
 

收藏

收藏() 是终端操作,使用收集器对Stream的元素执行可变还原。 收藏家 是提供内置收集器的实用程序类。
例如:
收藏家.toList() 提供了一个收集器,该收集器将Stream转换为列表对象。
以下代码将员工姓名累积到 数组列表

1
2
3
4
5
6
7
8
9
 
清单<> 员工姓名 = 员工名单.()
                                          .地图(雇员:: 得到Name)
                                          .收藏(收藏家.到清单());
系统..打印(员工姓名);
 
//输出
// [Mohan,John,Vaibhav,Amit]
 

减少

归约运算结合了Stream的所有元素并产生单个结果。
爪哇 8有3个重载版本的reduce方法。

  1. 可选的&lt;T&gt; 减少(BinaryOperator&lt;T&gt; accumulator):
    This method takes BinaryOperator accumulator function. BinaryOperator is BiFunction where both the operands are 的 same type. First parameter is result till current execution, and second parameter is the current element 的 the 流.

    让’查找年龄最小的人的名字。

    1
    2
    3
    4
    5
    6
    7
     
    员工名单.()
    .减少( (e1,e2)-> (e1.得到Age() < e2.得到Age()? e1:e2))
    .如果存在(系统.:: 打印);
    //输出
    //员工[name = Amit,年龄= 22]
     

  2. T 减少(T identity, BinaryOperator<T> accumulator):
    此方法具有标识值和累加器功能。同一性值是减少量的初始值。如果Stream为空,则结果为identity。
    让’查找所有年龄段的员工的总和

    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    整型 总年龄 = 员工名单.()
    .地图ToInt(雇员:: 得到Age)
    .减少(0, (年龄1,年龄2)-> (年龄1 + 年龄2));
     
    系统..打印(“所有员工的年龄总和:”+总年龄);
    //输出
    //所有员工的年龄总和:105
     

  3. &lt;U&gt; U 减少(U identity, BiFunction&lt;U,? 超 T,U&gt; accumulator, BinaryOperator&lt;U&gt; combiner):
    该方法采用标识值和累加器功能以及组合器。合并器主要用于以下情况 并行流。合并器合并并行运行的子流的结果。

计数

计数()用于计算流中元素的数量。

1
2
3
4
5
6
7
8
9
10
 
empCountStartJ = 员工名单.()
                                   .地图(雇员:: 得到Name)
                                   .过滤(s -> s.以。。开始(“ J”))
                                   .计数();
系统..打印(empCountStartJ);
 
//输出
// 1
 

allMatch()

allMatch() 当流中的所有元素都满足提供的条件时,返回true。

这是一种短路端子操作,因为一旦遇到任何不匹配的元素,操作就会立即停止。

1
2
3
4
5
6
7
8
9
 
布尔值 allMatch = 员工名单.()
                                .allMatch(e ->e.得到Age()>18);
 
系统..打印(“所有受雇的成年人都是:” +allMatch);
 
//输出
//所有雇员都是成年人吗?
 

nonMatch()

nonMatch() 当流中的所有元素都不满足提供的条件时,返回true。

这是一种短路端子操作,因为一旦遇到任何匹配的元素,操作就会停止。

1
2
3
4
5
6
7
8
9
 
布尔值 noneMatch = 员工名单.()
                                 .noneMatch(e ->e.得到Age()>60);
 
系统..打印(“所有的雇员年龄都在60岁以下:” +noneMatch);
 
//输出
//所有员工都在60岁以下:是
 

anyMatch()

anyMatch() 当流中的任何元素满足提供的条件时,返回true。

这是一种短路端子操作,因为一旦遇到任何匹配的元素,操作就会停止。

1
2
3
4
5
6
7
8
9
 
布尔值 anyMatch = 员工名单.()
                                 .anyMatch(e ->e.得到Age()>30);
 
系统..打印(“任何员工的年龄大于30岁:” +anyMatch);
 
//输出
//任何员工的年龄大于30:true
 

分()

分(Comparator) 根据提供的比较器返回流中的最小元素。它返回一个包含实际值的对象。

1
2
3
4
5
6
7
8
9
10
 
可选的<雇员> 分EmpOpt = 员工名单.()
                                            .(比较器.比较(雇员:: 得到Age));
 
雇员 分AgeEmp = 分EmpOpt.得到();
系统..打印(“最低年龄的雇员是:” +分AgeEmp);
 
//输出
//年龄最小的员工是:Employee [name = Amit,age = 22]
 

最高()

最高(Comparator) 根据提供的比较器返回流中的最大元素。它返回一个包含实际值的对象。

1
2
3
4
5
6
7
8
9
10
 
可选的<雇员> 最高EmpOpt = 员工名单.()
                                            .最高(比较器.比较(雇员:: 得到Age));
 
雇员 最高AgeEmp = 最高EmpOpt.得到();
系统..打印(“年龄最大的雇员是:” +最高AgeEmp);
 
//输出
//年龄上限为的员工为:员工[name = Vaibhav,年龄= 32]
 

并行流

You can create Parallel 流 using .parallel() method on object in 爪哇.
这是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
 
整型[] 数组= {1,2,3,4,5};
 
系统..打印(“ ================================”);
系统..打印(“使用并行流”);
系统..打印(“ ================================”);
串流 整型ParallelStream=数组.(数组).平行();
整型ParallelStream.每次((s)->
{
    系统..打印(s++线.currentThread().得到Name());
}
);
 

这是一篇关于 爪哇 8并行流.

练习题

让’在Stream上练习一些练习。

练习1

练习2

给定雇员列表,找到年龄大于25岁的雇员人数?
回答:

练习3

Given the list 的 雇员, find the employee whose 名称 is John.

练习4

给定雇员列表,您需要找到雇员的最高年龄吗?
回答:

练习5

给定雇员列表,您需要按年龄对雇员列表进行排序?仅使用Java 8 API
回答:

练习6

给定雇员列表,您需要将所有雇员姓名与","?
回答:

练习7

根据员工列表,您需要按姓名分组


导入联系人

您可能还喜欢:

分享这个

作者

关注作者

相关文章

  • 在Java中将日期转换为LocalDate
    1月12日

    爪哇日期到LocalDate

    在这篇文章中,我们将看到如何在Java中将Date转换为LocalDate。有时,我们可能需要将Date转换为新的Java 8 API,反之亦然。在Java中,有多种将Date转换为LocalDate的方法。另请参见:使用Date类的[InInstant()方法将Java中的LocalDate转换为Date…]

  • 在Java中将LocalDate转换为Date
    1月11日

    迄今为止的Java LocalDate

    在这篇文章中,我们将看到如何将LocalDate转换为Date。 爪哇 8引入了许多有关日期和时间的新API。有多种方法可以将Java LocalDateTime转换为日期。使用Instant对象您可以使用来自Zone的Instant对象将LocalDate转换为Date。这是 […]

  • 将流转换为Java中的列表
    12月31日

    爪哇流列表

    在本文中,我们将看到如何在Java中将Stream转换为List。在Java中,有多种方法可以将Stream转换为List。使用Collectors.toList()可以将Collectors.toList()传递给Stream.collect()方法,以在Java中将Stream转换为List。流’的collect方法对Stream和Collectors的元素执行可变的约简操作。toList()提供[…]

  • 在Java中将LocalDateTime转换为时间戳
    11月18日

    在Java中将LocalDateTime转换为时间戳

    在本文中,我们将如何将LocalDateTime转换为Timestamp。在学习如何将localdatetime转换为时间戳之前,让我们了解LocalDateTime和Timestamp,并了解这种转换的重要性。 LocalDateTime LocalDateTime在Java 8中已引入。LocalDateTime可以导入时间包:import 爪哇.time.LocalDateTime; LocalDateTime是使用的不可变对象[…]

  • 要映射的Java流列表
    4月26日

    要映射的Java流列表

    在本文中,我们将看到如何在Java 8中使用Stream将List转换为Map。’s toMap()可与Stream一起使用,以在Java中将List转换为Map。考虑一个名为Movie的类,它具有3个字段–ID,名称和流派[crayon-601967a33dc21997509764 /]创建电影列表并将其转换为[…]

  • 爪哇流排序
    4月26日

    爪哇 流排序示例

    在本文中,我们将看到如何使用Stream.sorted()方法对列表进行排序。 爪哇 8引入了Stream.sort()方法来方便地对元素列表进行排序。它有助于我们编写简短的功能样式代码,而不是样板代码。 爪哇.util.Stream具有sorted()方法的两个重载版本。 已排序():返回包含元素[…]

发表评论

您的电子邮件地址不会被公开。 必需的地方已做标记 *

订阅我们的新闻

获取质量教程到您的收件箱。现在订阅。


让’s be Friends

©2020 爪哇2博客