Java学习笔记(十二)
网络编程
软件结构
- C/S结构:全称Client/Server结构,是指客户端和服务器结构
- B/S结构:全程Browser/Server结构,是指浏览器和服务器结构
网络通信协议
- 网络通信协议:通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信的时候要遵守一定的规则。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一的规定,通信双方必须同时遵守才能完成数据交换
- TCP/IP协议:传输控制协议/英特网互联协议(Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。它定义了计算机如何连接英特网,以及数据如何在它们之间传输的标准,它内部包含一系列的用于处理数据通信的协议,并采用4层的分成模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求
- 链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议
- 网络层:网络层是整个TCP/IP的核心,它主要用于将创数的数据进行分组,将分组数据发送到目标计算机或网络
- 传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议或者UDP协议
- 应用层:主要负责应用程序的协议,例如HTTP、FTP等
网络通信协议的分类
java.net
包中包含的类和接口,它们提供低层次的通信细节。我们可以直接使用这些类和接口,来进行网络程序的开发,而不考虑通信的细节
java.net
提供了两种常见的网络协议的支持:
- UDP:用户数据报协议(User Datagram Protocol)。UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是
否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用丁音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失两个 数据包,也不会对按收结果产生太大影响。
但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。
特点:数据被限制在64kb以内,超过这个范围就不能发送了
数据报(Datagram):网络传输的基本单位 - TCP:传输控制协议(Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过”三次握手"。
- 三次握手: TCP协议中,在发送数据的准备阶段,户端与服务器之间的三次交互,以保证连接的可靠
- 第一次握手,客户端向服务器端发出连接请求,待服务器确认。
- 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
- 第三次握手,客户端再次向服务器端发送确认信息.确认连接。
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性, TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。
网络编程的三要素
协议
- 协议:计算机网络通信必须遵守的规则
IP地址
- IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给网络中的计算机设备做唯一的编号
- IP地址的分类
- IPv4:是一个32位的二进制,通常被分为4个字节,表示成
a.b.c.d
的形式,例如192.168.65.100
。其中a、b、c、d都是0-255之间的十进制整数,那么最多可以表示42亿个 - IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,打死你hi网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址空间,每16个字节一组,分成8组十六进制数,表示成
ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
端口号
- 端口号:用两个字节表示的整数,它的取值范围是0-65535,其中,0-1023之间的端口用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号,端口号可以唯一表示设备中的进程(应用程序),如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
UDP通信程序
概述:UDP协议是一种不可靠的网络协议,它在通信的两端个建立一个Socket对象答案是这两个Socket只是发送、接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务端的概念
Java提供了DatagramSocket
类作为基于UDP协议的Socket
UDP发送数据
发送数据的步骤
- 创建发送端的Socket对象(DatagramSocket)
- 创建数据,并把数据打包
- 调用DatagramSocket对象的方法发送数据
- 关闭发送端
public class SendDemo{
public static void main(String[] args) throws SocketException{
//DatagramSocket()构造数据报套接字并将其绑定到本地主机上的任何可用端口
DatagramSocket ds = new DatagramSocket();
/*
DatagramPacket(byte[] buf,int length,InetAddress address,int port)
构造一个数据包,发送长度为length的数据包到指定主机上的指定端口号
*/
byte[] bytes = "Hello udp".getBytes();
int len = bytes.length;
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 8888;
DatagramPacket dp =new DatagramPacket(bytes,len,address,port);
ds.send(dp);
ds.close();
}
}
UDP接收数据
接收数据的步骤
- 创建接收端的Socket对象(DatagramSocket)
- 创建一个数据包,用于接收数据
- 调用DatagramSocket对象的方法接收数据
- 解析数据包,并把数据在控制台输出
- 关闭接收端
public class ReceiveDemo{
public static void main(String[] args) thorws IOException{
//DatagramSocket(int port)构造数据报套接字并将其绑定到本地主机地址的指定端口
DatagramSocket ds = new DatagramSocket(8888);
byte[] bytes = new byte[1024];
//DatagramPacket(byte[] buf,int length)构造一个DatagramPacket用于接收长度为length的数据包
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
ds.receive(dp);
byte[] datas = dp.getDate();
int len = dp.getLength();
String data = new String(datas,0,len);
System.out.println(data);
ds.close();
}
}
TCP通信程序
概述:TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)
两端通信时的不步骤
- 服务端程序,需要实现启动,等待客户端的连接
- 客户端主动连接服务端,连接成功才能通信,服务端不可以主动连接客户端
在Java类中,提供了两个类用于实现TCP通信程序
- 客户端:
java.net.Socket
类表示,创建Socket
对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信 - 服务端:
java.net.ServerSocket
类表示,创建ServerSocket
对象,相当于开启一个服务,并等待客户端的连接
Socket类
- Socket类:该类是实现客户端套接字,套接字指的是两台设备之间的通讯的端点(包含了IP地址和端口号的网络单位)
- 构造方法
pubic Socket(String host,int port)
:创建一个流套接字并将其连接到指定主机上的指定端口号
- 参数:
-
String host
:服务器主机的名称、服务器的IP地址 -
int port
:服务器的端口号
- 成员方法
-
OutputStream getOutputStream()
:返回此套接字的输出流 -
InputStream getInputStream()
:返回此套接字的输入流 -
void close()
:关闭此套接字
- 客户端的实现步骤
- 创建一个客户端对象
Socket
,构造方法绑定服务器的IP地址和端口号 - 使用
Socket
对象中的方法getOutputStream()
获取网络字节输出流OutputStream
对象 - 使用网络字节输出流
OutputStream
对象中的方法write
给服务器发送数据 - 使用Socket对象中方法
getInputStream()
获取网络字节输入流InputStream
对象 - 使用网络字节输入流
InputStream
对象中的方法read
,读取服务器回写的数据 - 释放资源(
Socket
)
- 注意:
- 客户端和服务端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象
- 当我们创建客户端对象Socket的时候,就会去请求服务端和服务端进行3次握手建立连接通道,如果服务端没有启动,那么就会抛出异常
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
OutputStream os = socket.getOutputStream();
os.write("你好".getBytes());
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes, 0, len));
socket.close();
}
}
ServerSocket类
- ServerSocket类:该类是实现服户端套接字
- 构造方法:
-
public ServerSocket(int port)
:创建绑定到指定端口号的服务器套接字
- 成员方法
Socket accept()
:侦听并接受此套接字的连接
- 服务端必须明确一件事情,必须知道是哪个客户端请求的服务端,缩影可以使用accept方法获取到请求的客户端对象Socket
- 服务端的实现步骤
- 创建一个客户端对象
ServerSocket
,构造方法绑定指定的端口号 - 使用
ServerSocket
对象中的accept
方法,获取到请求的客户端对象Socket
- 使用
Socket
对象中的方法getInputStream()
获取网络字节输入流InputStream
对象 - 使用网络字节输入流
InputStream
对象中的方法read
,读取客户端发送的数据 - 使用
Socket
对象中的方法getOutputStream()
获取网络字节输出流OutputStream
对象 - 使用网络字节输出流
OutputStream
对象中的方法write
,给客户端回写数据 - 释放资源(Socket,ServerSocket)
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes, 0, len));
OutputStream os = socket.getOutputStream();
os.write("收到".getBytes());
socket.close();
serverSocket.close();
}
}
函数式接口
- 概念:函数式接口在Java中是指:有且仅有一个抽象方法的接口
- 函数式接口,即适用于函数式编程场景的接口,而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以使用于Lambda使用的接口,只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利进行推导
- Lambda表达式和匿名内部类的区别
- 所需类型不同
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式:只能是接口
- 使用限制不同
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
- 实现原理不同
- 匿名内部类:编译之后,产生一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
格式
- 只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称{
public abstract 返回值类型 方法名称(可选参数信息)
//其他非抽象方法内容
}
- 由于接口中抽象方法的
public abstract
是可以省略的,所以定义一个函数式接口很简单
public interface MyFunctionalInterface{
void myMethod();
}
@FunctionalInterface注解
- 与
@Override
注解的作用类似,Java8中专门为函数式接口引入了一个新的注解:@FunctionalInterface
,该注解可用于一个接口的定义上
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
函数式接口作为方法的参数
- 需求描述
定义一个类(RunnableDemo),在类中提供两个方法
一个方法是:startThread(Runnable r) 方法参数Runnable是一个函数式接口
一个方法是主方法,在主方法中调用startThread方法
public class RunnableDemo {
public static void main(String[] args) {
//在主方法中调用startThread方法
//匿名内部类的方式
startThread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程启动了");
}
});
//Lambda方式
startThread(() -> System.out.println(Thread.currentThread().getName() + "线程启动了"));
}
private static void startThread(Runnable r) {
new Thread(r).start();
}
}
函数式接口作为方法的返回值
- 需求描述
定义一个类(ComparatorDemo),在类中提供两个方法
一个方法是:Comparator getComparator() 方法返回值Comparator是一个函数式接口
一个方法是主方法,在主方法中调用getComparator方法
public class ComparatorDemo {
public static void main(String[] args) {
//定义集合,存储字符串元素
ArrayList<String> array = new ArrayList<String>();
array.add("cccc");
array.add("aa");
array.add("b");
array.add("ddd");
System.out.println("排序前:" + array);
Collections.sort(array, getComparator());
System.out.println("排序后:" + array);
}
private static Comparator<String> getComparator() {
//匿名内部类的方式实现
// return new Comparator<String>() {
// @Override
// public int compare(String s1, String s2) {
// return s1.length()-s2.length();
// }
// };
//Lambda方式实现
return (s1, s2) -> s1.length() - s2.length();
}
}
函数式编程
Lambda的延迟执行
- 有些场景的代码执行后,结果不一定会被使用,从而造成性能的浪费,而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能
常用的函数式接口
- JDK提供了大量的常用函数式接口以丰富Lambda的典型使用场景,它们主要在
java.util.function
包中被提供
常用函数式接口之Supplier
- Supplier接口
Supplier接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用。 - 常用方法
只有一个无参的方法T get()
:按照某种实现逻辑(由Lambda表达式实现)返回一个数据
public class SupplierDemo {
public static void main(String[] args) {
String s = getString(() -> "林青霞");
System.out.println(s);
Integer i = getInteger(() -> 30);
System.out.println(i);
}
//定义一个方法,返回一个整数数据
private static Integer getInteger(Supplier<Integer> sup) {
return sup.get();
}
//定义一个方法,返回一个字符串数据
private static String getString(Supplier<String> sup) {
return sup.get();
}
}
Supplier接口练习之获取最大值
- 案例需求
定义一个类(SupplierTest),在类中提供两个方法
一个方法是:int getMax(Supplier sup) 用于返回一个int数组中的最大值
一个方法是主方法,在主方法中调用getMax方法
public class SupplierTest {
public static void main(String[] args) {
//定义一个int数组
int[] arr = {19, 50, 28, 37, 46};
int maxValue = getMax(()-> {
int max = arr[0];
for(int i=1; i<arr.length; i++) {
if(arr[i] > max) {
max = arr[i];
}
}
return max;
});
System.out.println(maxValue);
}
//返回一个int数组中的最大值
private static int getMax(Supplier<Integer> sup) {
return sup.get();
}
}
常用函数式接口之Consumer
- Consumer接口
Consumer接口也被称为消费型接口,它消费的数据的数据类型由泛型指定 - 常用方法
Consumer:包含两个方法
-
void accept(T t)
:对给定的参数执行此操作 -
default Consumer<T> andThen(Consumer after)
:返回一个组合的Consumer,依次执行此操作,然后执行 after操作
public class ConsumerDemo {
public static void main(String[] args) {
//操作一
operatorString("林青霞", s -> System.out.println(s));
//操作二
operatorString("林青霞", s -> System.out.println(new StringBuilder(s).reverse().toString()));
System.out.println("--------");
//传入两个操作使用andThen完成
operatorString("林青霞", s -> System.out.println(s), s -> System.out.println(new StringBuilder(s).reverse().toString()));
}
//定义一个方法,用不同的方式消费同一个字符串数据两次
private static void operatorString(String name, Consumer<String> con1, Consumer<String> con2) {
// con1.accept(name);
// con2.accept(name);
con1.andThen(con2).accept(name);
}
//定义一个方法,消费一个字符串数据
private static void operatorString(String name, Consumer<String> con) {
con.accept(name);
}
}
常用函数式接口之Predicate
- Predicate接口
Predicate接口通常用于判断参数是否满足指定的条件 - 常用方法
-
boolean test(T t)
:对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值 -
default Predicate<T> negate()
:返回一个逻辑的否定,对应逻辑非 -
default Predicate<T> and(Predicate other)
:返回一个组合判断,对应短路与 -
default Predicate<T> or(Predicate other)
:返回一个组合判断,对应短路或
public class PredicateDemo01 {
public static void main(String[] args) {
boolean b1 = checkString("hello", s -> s.length() > 8);
System.out.println(b1);
boolean b2 = checkString("helloworld",s -> s.length() > 8);
System.out.println(b2);
}
//判断给定的字符串是否满足要求
private static boolean checkString(String s, Predicate<String> pre) {
// return !pre.test(s);
return pre.negate().test(s);
}
}
public class PredicateDemo02 {
public static void main(String[] args) {
boolean b1 = checkString("hello", s -> s.length() > 8);
System.out.println(b1);
boolean b2 = checkString("helloworld", s -> s.length() > 8);
System.out.println(b2);
boolean b3 = checkString("hello",s -> s.length() > 8, s -> s.length() < 15);
System.out.println(b3);
boolean b4 = checkString("helloworld",s -> s.length() > 8, s -> s.length() < 15);
System.out.println(b4);
}
//同一个字符串给出两个不同的判断条件,最后把这两个判断的结果做逻辑与运算的结果作为最终的结果
private static boolean checkString(String s, Predicate<String> pre1, Predicate<String> pre2) {
return pre1.or(pre2).test(s);
}
//判断给定的字符串是否满足要求
private static boolean checkString(String s, Predicate<String> pre) {
return pre.test(s);
}
}
常用函数式接口之Function
- Function接口
Function<T,R>接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新的值 - 常用方法
-
R apply(T t)
:将此函数应用于给定的参数 -
default <V> Function andThen(Function after)
:返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
public class FunctionDemo {
public static void main(String[] args) {
//操作一
convert("100",s -> Integer.parseInt(s));
//操作二
convert(100,i -> String.valueOf(i + 566));
//使用andThen的方式连续执行两个操作
convert("100", s -> Integer.parseInt(s), i -> String.valueOf(i + 566));
}
//定义一个方法,把一个字符串转换int类型,在控制台输出
private static void convert(String s, Function<String,Integer> fun) {
// Integer i = fun.apply(s);
int i = fun.apply(s);
System.out.println(i);
}
//定义一个方法,把一个int类型的数据加上一个整数之后,转为字符串在控制台输出
private static void convert(int i, Function<Integer,String> fun) {
String s = fun.apply(i);
System.out.println(s);
}
//定义一个方法,把一个字符串转换int类型,把int类型的数据加上一个整数之后,转为字符串在控制台输出
private static void convert(String s, Function<String,Integer> fun1, Function<Integer,String> fun2) {
String ss = fun1.andThen(fun2).apply(s);
System.out.println(ss);
}
}
方法的引用
方法引用符
- 方法引用符
:: 该符号为引用运算符,而它所在的表达式被称为方法引用 - 推导与省略
- 如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导
- 如果使用方法引用,也是同样可以根据上下文进行推导
- 方法引用是Lambda的孪生兄弟
引用类方法
引用类方法,其实就是引用类的静态方法
- 格式
类名::静态方法 - 范例
Integer::parseInt
Integer类的方法:public static int parseInt(String s) 将此String转换为int类型数据 - 练习描述
- 定义一个接口(Converter),里面定义一个抽象方法 int convert(String s);
- 定义一个测试类(ConverterDemo),在测试类中提供两个方法
- 一个方法是:useConverter(Converter c)
- 一个方法是主方法,在主方法中调用useConverter方法
public interface Converter {
int convert(String s);
}
public class ConverterDemo {
public static void main(String[] args) {
//Lambda写法
useConverter(s -> Integer.parseInt(s));
//引用类方法
useConverter(Integer::parseInt);
}
private static void useConverter(Converter c) {
int number = c.convert("666");
System.out.println(number);
}
}
- 使用说明
Lambda表达式被类方法替代的时候,它的形式参数全部传递给静态方法作为参数
引用对象的实例方法
引用对象的实例方法,其实就引用类中的成员方法
- 格式
对象::成员方法 - 范例
“HelloWorld”::toUpperCase
String类中的方法:public String toUpperCase() 将此String所有字符转换为大写 - 练习描述
- 定义一个类(PrintString),里面定义一个方法
public void printUpper(String s):把字符串参数变成大写的数据,然后在控制台输出 - 定义一个接口(Printer),里面定义一个抽象方法
void printUpperCase(String s) - 定义一个测试类(PrinterDemo),在测试类中提供两个方法
- 一个方法是:usePrinter(Printer p)
- 一个方法是主方法,在主方法中调用usePrinter方法
public class PrintString {
//把字符串参数变成大写的数据,然后在控制台输出
public void printUpper(String s) {
String result = s.toUpperCase();
System.out.println(result);
}
}
public interface Printer {
void printUpperCase(String s);
}
public class PrinterDemo {
public static void main(String[] args) {
//Lambda简化写法
usePrinter(s -> System.out.println(s.toUpperCase()));
//引用对象的实例方法
PrintString ps = new PrintString();
usePrinter(ps::printUpper);
}
private static void usePrinter(Printer p) {
p.printUpperCase("HelloWorld");
}
}
- 使用说明
Lambda表达式被对象的实例方法替代的时候,它的形式参数全部传递给该方法作为参数
引用类的实例方法
引用类的实例方法,其实就是引用类中的成员方法
- 格式
类名::成员方法 - 范例
String::substring
public String substring(int beginIndex,int endIndex)
从beginIndex开始到endIndex结束,截取字符串。返回一个子串,子串的长度为endIndex-beginIndex - 练习描述
- 定义一个接口(MyString),里面定义一个抽象方法:
String mySubString(String s,int x,int y); - 定义一个测试类(MyStringDemo),在测试类中提供两个方法
- 一个方法是:useMyString(MyString my)
- 一个方法是主方法,在主方法中调用useMyString方法
public interface MyString {
String mySubString(String s,int x,int y);
}
public class MyStringDemo {
public static void main(String[] args) {
//Lambda简化写法
useMyString((s,x,y) -> s.substring(x,y));
//引用类的实例方法
useMyString(String::substring);
}
private static void useMyString(MyString my) {
String s = my.mySubString("HelloWorld", 2, 5);
System.out.println(s);
}
}
- 使用说明
Lambda表达式被类的实例方法替代的时候
第一个参数作为调用者
后面的参数全部传递给该方法作为参数
引用构造器
引用构造器,其实就是引用构造方法
- l格式
类名::new - 范例
Student::new - 练习描述
- 定义一个类(Student),里面有两个成员变量(name,age)
并提供无参构造方法和带参构造方法,以及成员变量对应的get和set方法 - 定义一个接口(StudentBuilder),里面定义一个抽象方法
Student build(String name,int age); - 定义一个测试类(StudentDemo),在测试类中提供两个方法
- 一个方法是:useStudentBuilder(StudentBuilder s)
- 一个方法是主方法,在主方法中调用useStudentBuilder方法
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public interface StudentBuilder {
Student build(String name,int age);
}
public class StudentDemo {
public static void main(String[] args) {
//Lambda简化写法
useStudentBuilder((name,age) -> new Student(name,age));
//引用构造器
useStudentBuilder(Student::new);
}
private static void useStudentBuilder(StudentBuilder sb) {
Student s = sb.build("林青霞", 30);
System.out.println(s.getName() + "," + s.getAge());
}
}
- 使用说明
Lambda表达式被构造器替代的时候,它的形式参数全部传递给构造器作为参数
Stream流
体验Stream流
- 案例需求
按照下面的要求完成集合的创建和遍历
- 创建一个集合,存储多个字符串元素
- 把集合中所有以"张"开头的元素存储到一个新的集合
- 把"张"开头的集合中的长度为3的元素存储到一个新的集合
- 遍历上一步得到的集合
- 原始方式示例代码
public class StreamDemo {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
//把集合中所有以"张"开头的元素存储到一个新的集合
ArrayList<String> zhangList = new ArrayList<String>();
for(String s : list) {
if(s.startsWith("张")) {
zhangList.add(s);
}
}
// System.out.println(zhangList);
//把"张"开头的集合中的长度为3的元素存储到一个新的集合
ArrayList<String> threeList = new ArrayList<String>();
for(String s : zhangList) {
if(s.length() == 3) {
threeList.add(s);
}
}
// System.out.println(threeList);
//遍历上一步得到的集合
for(String s : threeList) {
System.out.println(s);
}
System.out.println("--------");
//Stream流来改进
// list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);
}
}
- Stream流的好处
- 直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印
- Stream流把真正的函数式编程风格引入到Java中
Stream流的常见生成方式
- Stream流的思想
- 生成Stream流的方式
- Collection体系集合
使用默认方法stream()生成流, default Stream stream() - Map体系集合
把Map转成Set集合,间接的生成流 - 数组
通过Stream接口的静态方法of(T… values)生成流
public class StreamDemo {
public static void main(String[] args) {
//Collection体系的集合可以使用默认方法stream()生成流
List<String> list = new ArrayList<String>();
Stream<String> listStream = list.stream();
Set<String> set = new HashSet<String>();
Stream<String> setStream = set.stream();
//Map体系的集合间接的生成流
Map<String,Integer> map = new HashMap<String, Integer>();
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valueStream = map.values().stream();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
//数组可以通过Stream接口的静态方法of(T... values)生成流
String[] strArray = {"hello","world","java"};
Stream<String> strArrayStream = Stream.of(strArray);
Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
Stream<Integer> intStream = Stream.of(10, 20, 30);
}
}
Stream流中间操作方法
- 概念
中间操作的意思是,执行完此方法之后,Stream流依然可以继续执行其他操作。 - 常见方法
-
Stream<T> filter(Predicate predicate)
: 用于对流中的数据进行过滤 -
Stream<T> limit(long maxSize)
: 返回此流中的元素组成的流,截取前指定参数个数的数据 -
Stream<T> skip(long n)
: 跳过指定参数个数的数据,返回由该流的剩余元素组成的流 -
static <T> Stream<T> concat(Stream a, Stream b)
: 合并a和b两个流为一个流 -
Stream<T> distinct()
: 返回由该流的不同元素(根据Object.equals(Object) )组成的流 -
Stream<T> sorted()
: 返回由此流的元素组成的流,根据自然顺序排序 -
Stream<T> sorted(Comparator comparator)
:返回由该流的元素组成的流,根据提供的Comparator进行排序 -
<R> Stream<R> map(Function mapper)
:返回由给定函数应用于此流的元素的结果组成的流 -
IntStream mapToInt(ToIntFunction mapper)
:返回一个IntStream其中包含将给定函数应用于此流的元素的结果
- filter代码演示
public class StreamDemo01 {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
//需求1:把list集合中以张开头的元素在控制台输出
list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);
System.out.println("--------");
//需求2:把list集合中长度为3的元素在控制台输出
list.stream().filter(s -> s.length() == 3).forEach(System.out::println);
System.out.println("--------");
//需求3:把list集合中以张开头的,长度为3的元素在控制台输出
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);
}
}
- limit&skip代码演示
public class StreamDemo02 {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
//需求1:取前3个数据在控制台输出
list.stream().limit(3).forEach(System.out::println);
System.out.println("--------");
//需求2:跳过3个元素,把剩下的元素在控制台输出
list.stream().skip(3).forEach(System.out::println);
System.out.println("--------");
//需求3:跳过2个元素,把剩下的元素中前2个在控制台输出
list.stream().skip(2).limit(2).forEach(System.out::println);
}
}
- concat&distinct代码演示
public class StreamDemo03 {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
//需求1:取前4个数据组成一个流
Stream<String> s1 = list.stream().limit(4);
//需求2:跳过2个数据组成一个流
Stream<String> s2 = list.stream().skip(2);
//需求3:合并需求1和需求2得到的流,并把结果在控制台输出
// Stream.concat(s1,s2).forEach(System.out::println);
//需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
Stream.concat(s1,s2).distinct().forEach(System.out::println);
}
}
- sorted代码演示
public class StreamDemo04 {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("linqingxia");
list.add("zhangmanyu");
list.add("wangzuxian");
list.add("liuyan");
list.add("zhangmin");
list.add("zhangwuji");
//需求1:按照字母顺序把数据在控制台输出
// list.stream().sorted().forEach(System.out::println);
//需求2:按照字符串长度把数据在控制台输出
list.stream().sorted((s1,s2) -> {
int num = s1.length()-s2.length();
int num2 = num==0?s1.compareTo(s2):num;
return num2;
}).forEach(System.out::println);
}
}
- map&mapToInt代码演示
public class StreamDemo05 {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("10");
list.add("20");
list.add("30");
list.add("40");
list.add("50");
//需求:将集合中的字符串数据转换为整数之后在控制台输出
// list.stream().map(s -> Integer.parseInt(s)).forEach(System.out::println);
// list.stream().map(Integer::parseInt).forEach(System.out::println);
// list.stream().mapToInt(Integer::parseInt).forEach(System.out::println);
//int sum() 返回此流中元素的总和
int result = list.stream().mapToInt(Integer::parseInt).sum();
System.out.println(result);
}
}
Stream流终结操作方法
- 概念
终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作。 - 常见方法
-
void forEach(Consumer action)
:对此流的每个元素执行操作 -
long count()
:返回此流中的元素数
public class StreamDemo {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
//需求1:把集合中的元素在控制台输出
// list.stream().forEach(System.out::println);
//需求2:统计集合中有几个以张开头的元素,并把统计结果在控制台输出
long count = list.stream().filter(s -> s.startsWith("张")).count();
System.out.println(count);
}
}
Stream流的收集操作
- 概念
对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中。 - 常用方法
-
R collect(Collector collector)
: 把结果收集到集合中
- 工具类Collectors提供了具体的收集方式
-
public static <T> Collector toList()
:把元素收集到List集合中 -
public static <T> Collector toSet()
:把元素收集到Set集合中 -
public static Collector toMap(Function keyMapper,Function valueMapper)
:把元素收集到Map集合中
public class CollectDemo {
public static void main(String[] args) {
//创建List集合对象
List<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
/*
//需求1:得到名字为3个字的流
Stream<String> listStream = list.stream().filter(s -> s.length() == 3);
//需求2:把使用Stream流操作完毕的数据收集到List集合中并遍历
List<String> names = listStream.collect(Collectors.toList());
for(String name : names) {
System.out.println(name);
}
*/
//创建Set集合对象
Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(20);
set.add(30);
set.add(33);
set.add(35);
/*
//需求3:得到年龄大于25的流
Stream<Integer> setStream = set.stream().filter(age -> age > 25);
//需求4:把使用Stream流操作完毕的数据收集到Set集合中并遍历
Set<Integer> ages = setStream.collect(Collectors.toSet());
for(Integer age : ages) {
System.out.println(age);
}
*/
//定义一个字符串数组,每一个字符串数据由姓名数据和年龄数据组合而成
String[] strArray = {"林青霞,30", "张曼玉,35", "王祖贤,33", "柳岩,25"};
//需求5:得到字符串中年龄数据大于28的流
Stream<String> arrayStream = Stream.of(strArray).filter(s -> Integer.parseInt(s.split(",")[1]) > 28);
//需求6:把使用Stream流操作完毕的数据收集到Map集合中并遍历,字符串中的姓名作键,年龄作值
Map<String, Integer> map = arrayStream.collect(Collectors.toMap(s -> s.split(",")[0], s -> Integer.parseInt(s.split(",")[1])));
Set<String> keySet = map.keySet();
for (String key : keySet) {
Integer value = map.get(key);
System.out.println(key + "," + value);
}
}
}