软件结构- C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件。
- B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等。
两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机的通信的程序。
网络通信协议- **网络通信协议:**通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
- TCP/IP协议: 传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
上图中,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能。 链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。 网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。 运输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。 应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。
协议分类通信的协议还是比较复杂的,java.net 包中包含的类和接口,它们提供低层次的通信细节。我们可以直接使用这些类和接口,来专注于网络程序开发,而不用考虑通信的细节。
java.net 包中提供了两种常见的网络协议的支持:
UDP:用户数据报协议(User Datagram Protocol)。UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。UDP的交换过程如下图所示。
- TCP:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。
- 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
- 第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
- 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
- 第三次握手,客户端再次向服务器端发送确认信息,确认连接。
网络编程三要素协议- **协议:**计算机网络通信必须遵守的规则,已经介绍过了,不再赘述。
IP地址- IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”。
IP地址分类
IPv4:是一个32位的二进制数,通常被分为4个字节,表示成a.b.c.d 的形式,例如192.168.65.100 。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。
IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。
为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。
常用命令
ipconfig
ping
空格 IP
地址ping 220.181.57.216
特殊的IP地址
- 本机IP地址:127.0.0.1、localhost 。
端口号网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?
如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。
- **端口号:用两个字节表示的整数,它的取值范围是065535**。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。
利用协议+IP地址+端口号 三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。
TCP通信TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。
两端通信时步骤:
- 服务端程序,需要事先启动,等待客户端的连接。
- 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
在Java中,提供了两个类用于实现TCP通信程序:
- 客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
- 服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。
Socket类[Java] 纯文本查看 复制代码
java.net.ServerSocket类: TCP服务端
// 构造方法
ServerSocket(int port): 创建一个TCP服务端, 并监听指定端口
// 成员方法
Socket accept(): 监听数据, 会阻塞. 收到数据后返回Socket对象
void close(): 关闭服务端ServerSocket
java.net.Socket类: TCP客户端
// 构造方法
Socket(String ip, int port): 创建TCP客户端对象
// 成员方法
OutputStream getOutputStream(): 获取输出流对象, 用于发送数据
InputStream getInputStream(): 获取输入流, 用于接收数据
void shutdownOutput(): 关闭输出流, 告知服务端数据发送完毕
void close(): 关闭客户端Socket
TCP通信
服务端代码实现
[Java] 纯文本查看 复制代码
需求:
编写TCP服务端代码, 监听8888端口, 接收客户端发来的消息打印到控制台, 然后向客户端回写"收到谢谢"
补全TCP客户端接收数据的代码, 将服务器回写的数据打印到控制台
public class Demo2 {
public static void main(String[] args) throws IOException {
//创建服务端对象向操作系统要指定的端口号
ServerSocket serverSocket1=new ServerSocket(8888);
//使用服务端的成员方法accept()获取客户端对象
Socket accept = serverSocket1.accept();
//获取客户端的网络流对象
InputStream is = accept.getInputStream();
OutputStream os = accept.getOutputStream();
//接受客户端传输的数据
byte[]bytes =new byte[1024];
int len;
len=is.read(bytes);
System.out.println(new String(bytes,0,len));
//回写数据
os.write("收到了".getBytes());
//释放资源
serverSocket1.close();
}
}
客户端代码实现
[Java] 纯文本查看 复制代码
需求:
编写TCP客户端, 连接服务端的IP是"127.0.0.1", 端口是8888, 发送"你好服务器"
public class Demo1Socket {
public static void main(String[] args) throws IOException {
//创建一个套接字Socket对象
Socket socket1=new Socket("127.0.0.1",8888);
//使用套接字的成员方法获取返回的网络流对象
OutputStream outputStream = socket1.getOutputStream();
//使用网络流对象的成员方法write()传送数据
outputStream.write("你好服务器".getBytes());
//接收服务器返回的数据
InputStream inputStream = socket1.getInputStream();
byte[]bytes =new byte[1024];
int len;
len=inputStream.read(bytes);
System.out.println(new String(bytes,0,len));
//关闭客户端套接字
socket1.close();
}
}
综合案例
文件上传案例的客户端
[Java] 纯文本查看 复制代码
1.创建一个本地字节输入流FileInputStream对象, 构造方法中绑定要读取的数据源
2.创建一个客户端Socket对象, 构造方法中绑定服务器的IP地址和端口号
3.使用Socket中的方法getOutputStream, 获取网络字节输出流OutputStream对象
4.使用本地字节输入流FileInputStream对象中的方法read, 读取本地文件
5.使用网络字节输出流OutputStream对象中的方法write, 把读取到的文件上传到服务器
6.使用Socket中的方法getInputStream, 获取网络字节输入流InputStream对象
7.使用网络字节输入流InputStream对象中的方法read读取服务回写的数据
8.释放资源(FileInputStream, Socket)
lic class Demo1Socket {
public static void main(String[] args) throws IOException {
//1.创建一个本地字节输入流FileInputStream对象, 构造方法中绑定要读取的数据源
FileInputStream fis=new FileInputStream("E:\\develop69992173_p0.jpg");
//2.创建一个客户端Socket对象, 构造方法中绑定服务器的IP地址和端口号
Socket mySc=new Socket("127.0.0.1",8888);
//3.使用Socket中的方法getOutputStream, 获取网络字节输出流OutputStream对象
OutputStream upos = mySc.getOutputStream();
//4.使用本地字节输入流FileInputStream对象中的方法read, 读取本地文件
byte[]bytes=new byte[1024];
int len;
while((len=fis.read(bytes))!=-1){
//5.使用网络字节输出流OutputStream对象中的方法write, 把读取到的文件上传到服务器
upos.write(len);
}
//6.使用Socket中的方法getInputStream, 获取网络字节输入流InputStream对象
InputStream is=mySc.getInputStream();
byte[]bytes2=new byte[1024];
int len2;
//7.使用网络字节输入流InputStream对象中的方法read读取服务回写的数据
while((len2=is.read(bytes2))!=-1){
System.out.println(new String(bytes2,0,len2));
}
//8.释放资源(FileInputStream, Socket)
fis.close();
mySc.close();
}
}
文件上传案例的服务端
[Java] 纯文本查看 复制代码
需求:
编写文件上传服务器代码, 将上传的文件保存到当前模块下的upload目录中
实现步骤:
1.创建一个服务器ServerSocket对象,和系统要指定的端口号
2.使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
3.使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
4.判断"模块名\\upload"文件夹是否存在,不存在则创建
5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
6.使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
7.使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
8.使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
9.使用网络字节输出流OutputStream对象中的方法write,给客户端回写"上传成功"
10.释放资源(FileOutputStream,Socket,ServerSocket)
public class Demo2ServerSocket {
public static void main(String[] args) throws IOException {
//1.创建一个服务器ServerSocket对象,和系统要指定的端口号
ServerSocket serverSocket=new ServerSocket(8888);
//2.使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
Socket acceptsc = serverSocket.accept();
//3.使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
InputStream acceptscInputStream = acceptsc.getInputStream();
//4.判断"模块名\\upload"文件夹是否存在,不存在则创建
File serverfolder=new File("E:\\develop\\sever\\myserver\\accept");
if (!serverfolder.exists()){
serverfolder.mkdirs();
}
//5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
FileOutputStream fos=new FileOutputStream(serverfolder+"\\1.jpg");
int len;
byte[]bytes=new byte[1024];
//6.使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
while((len=acceptscInputStream.read(bytes))!=-1){
//7.使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
fos.write(len);
}
System.out.println("成功保存图片");
//8.使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
OutputStream acceptscOutputStream = acceptsc.getOutputStream();
//9.使用网络字节输出流OutputStream对象中的方法write,给客户端回写"上传成功"
acceptscOutputStream.write("成功上传图片!".getBytes());
//10.释放资源(FileOutputStream,Socket,ServerSocket)
fos.close();
acceptscOutputStream.close();
}
}
优化版客户端
[Java] 纯文本查看 复制代码
public class Demo1Socket {
public static void main(String[] args) throws IOException {
//2.创建一个客户端Socket对象, 构造方法中绑定服务器的IP地址和端口号
Socket mySc=new Socket("192.168.12.36",8888);
while(true){
//1.创建一个本地字节输入流FileInputStream对象, 构造方法中绑定要读取的数据源
FileInputStream fis=new FileInputStream("E:\\develop\\benzi\\"+"0"+ new Random().nextInt(99)+".jpg");
//3.使用Socket中的方法getOutputStream, 获取网络字节输出流OutputStream对象
OutputStream upos = mySc.getOutputStream();
//4.使用本地字节输入流FileInputStream对象中的方法read, 读取本地文件
byte[]bytes=new byte[1024];
int len;
while((len=fis.read(bytes))!=-1){
//5.使用网络字节输出流OutputStream对象中的方法write, 把读取到的文件上传到服务器
upos.write(bytes,0,len);
}
mySc.shutdownOutput();
//6.使用Socket中的方法getInputStream, 获取网络字节输入流InputStream对象
InputStream is=mySc.getInputStream();
//7.使用网络字节输入流InputStream对象中的方法read读取服务回写的数据
byte[]bytes2=new byte[1024];
int len2;
while((len2=is.read(bytes2))!=-1){
System.out.println(new String(bytes2,0,len2));
}
}
/*//8.释放资源(FileInputStream, Socket)
fis.close();
mySc.close();*/
}
}
优化版服务端
[Java] 纯文本查看 复制代码
public class Demo2ServerSocket {
public static void main(String[] args) throws IOException {
//1.创建一个服务器ServerSocket对象,和系统要指定的端口号
ServerSocket serverSocket=new ServerSocket(8888);
// 从serverSocket.accept()开始, 直到最后释放资源的操作代码, 全都放入while(true)死循环中
while (true) {
//2.使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
Socket acceptsc = serverSocket.accept();
//将serverSocket.accept()之后, 直到最后释放资源的操作代码, 全都放到子线程中执行
new Thread(new Runnable() {
@Override
public void run() {
try {//3.使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
InputStream acceptscInputStream = acceptsc.getInputStream();
//4.判断"模块名\\upload"文件夹是否存在,不存在则创建
File serverfolder=new File("E:\\develop\\sever\\myserver\\accept");
if (!serverfolder.exists()){
serverfolder.mkdirs();
}
// 拼接的文件名, 改为: 域名+毫秒值+随机数 的方式
String fileName="img"+System.currentTimeMillis()+ new Random().nextInt(999999);
//5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
FileOutputStream fos=new FileOutputStream(serverfolder+"\\"+fileName+".jpg");
int len;
byte[]bytes=new byte[1024];
//6.使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
while((len=acceptscInputStream.read(bytes))!=-1){
//7.使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
fos.write(bytes,0,len);
}
System.out.println("成功保存图片");
//8.使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
OutputStream acceptscOutputStream = acceptsc.getOutputStream();
//9.使用网络字节输出流OutputStream对象中的方法write,给客户端回写"上传成功"
acceptscOutputStream.write("成功上传图片!".getBytes());
//10.释放资源(FileOutputStream,Socket,ServerSocket)
fos.close();
acceptscOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
/*//*/
}
}
day12【函数式接口】
概念格式[Java] 纯文本查看 复制代码
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
由于接口当中抽象方法的`public abstract`是可以省略的,所以定义一个函数式接口很简单:
public interface MyFunctionalInterface {
void myMethod();
}
函数式编程
使用Lambda优化日志案例
[Java] 纯文本查看 复制代码
public class Demo1FunctionUse {
public static void main(String[] args) {
String s1="Hello";
String s2="New";
String s3="Hello";
showLog(1, new MessageBuilder() {
@Override
public String builderMessage() {
return s1+s2+s3;
}
});
showLog(2, () -> s1+s2+s3);
}
public static void showLog(int level,MessageBuilder builder) {
if (level==1){
String s = builder.builderMessage();
System.out.println(s);
}
}
}
[Java] 纯文本查看 复制代码
public interface MessageBuilder {
// 用于拼接字符串的方法, 怎么拼, 需要我们传递Lambda来实现
String buildMessage();
}
public class Test {
// 模拟拼接大量字符串的方法:
public static String getString() {
// 模拟拼接字符串的耗时
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String s1 = "hello";
String s2 = "world";
String s3 = "java";
return s1 + s2 + s3;
}
// 普通方式
public static void showLog(int level, String message) {
if (level == 1) {
System.out.println(message);
}
}
// 使用Lambda的延迟执行特性
public static void showLogLambda(int level, MessageBuilder builder) {
if (level == 1) {
String s = builder.buildMessage();
System.out.println(s);
}
}
public static void main(String[] args) {
// 调用普通方法
long start1 = System.currentTimeMillis();
showLog(2, getString());
long end1 = System.currentTimeMillis();
System.out.println("普通方式耗时:" + (end1-start1));
// 调用Lambda延迟执行的方法
long start2 = System.currentTimeMillis();
showLogLambda(2, ()->getString());
long end2 = System.currentTimeMillis();
System.out.println("延迟执行方式耗时:" + (end2-start2));
}
}
函数式接口作为方法的参数案例
[Java] 纯文本查看 复制代码
public class Demo2 {
public static void main(String[] args) {
openNewThread(new Runnable() {
@Override
public void run() {
System.out.println("开启了一个新线程"+Thread.currentThread().getName()+"了!");
}
});
openNewThread(()-> System.out.println("新线程"+Thread.currentThread().getName()+"+已开启"));
}
private static void openNewThread(Runnable r) {
new Thread(r).start();
}
}
函数式接口作为方法的参数案例
[Java] 纯文本查看 复制代码
需求:
定义方法, 用于获取一个比较器对象: public static Comparator<String> getComparator()
方法内部使用Lambda表达式作为Comparator接口的返回值, 比较规则是:
第二个字符串参数的长度 - 第一个字符串参数的长度
在main方法中
定义字符串数组: {"aaa", "b", "cccccc", "ddddddddddd"}
调用 getComparator() 方法获取比较器对象
调用 Arrays.sort(数组, 比较器) 方法, 将字符串数组排序, 然后打印出来
public class Demo3 {
public static void main(String[] args) {
String[]str={"嘤嘤嘤","啊大大大","大都是多群无多群多","大大说的现在的餐点"};
System.out.println("排序前:");
for (String s : str) {
System.out.print(s+" ");
}
System.out.println();
System.out.println("排序后:");
Arrays.sort(str,getComparator());
for (String s : str) {
System.out.println(s+" ");
}
}
private static Comparator<String> getComparator() {
return (o1, o2) -> o2.length()-o1.length();
}
}
day13【Stream流,方法引用】
Stream流
流式思想概述
注意:请暂时忘记对传统IO流的固有印象!
整体来看,流式思想类似于工厂车间的“生产流水线”。
当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型”步骤方案,然后再按照方案去执行它。
这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字3是最终结果。
这里的filter、map、skip都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法count执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。
备注:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。
Stream(流)是一个来自数据源的元素队列
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组 等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。
当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
两种获取Stream流的方式
[Java] 纯文本查看 复制代码
获取Stream流对象的2种方式:
1. 利用"Collection接口"中的默认方法 default Stream<E> stream() 方法: 集合转Stream对象
[Java] 纯文本查看 复制代码
2. 利用"Stream接口"中的静态方法 static <T> Stream<T> of(T... values): 数组转Stream对象
[Java] 纯文本查看 复制代码
public class Demo2Stream {
public static void main(String[] args) {
String[]strings={"张三","李四","王五","左老六","何老七"};
Stream.of(strings)
.filter(name->name.length()==3)
.filter(name->name.contains("老"))
.forEach(name-> System.out.println(name));
}
}
Stream几种常用方法
[Java] 纯文本查看 复制代码
延迟方法: (具有延迟执行的特性)
返回值类型"是Stream"类型的方法, 支持链式调用
Stream filter(): 过滤
Stream map(): 映射/转换
Stream limit(): 截取
Stream skip(): 跳过
终结方法:
返回值类型"不是Stream"类型的方法, 不支持链式调用
void forEach(): 遍历
long count(): 统计
foreach遍历
[Java] 纯文本查看 复制代码
void forEach(): 遍历
.forEach()->{};
blic class Demo2Stream {
public static void main(String[] args) {
String[]strings={"张三","李四","王五","左老六","何老七"};
Stream.of(strings)
.forEach(name-> System.out.println(name));
}
}
filter过滤
[Java] 纯文本查看 复制代码
Stream<T> filter(Predicate<? super T> predicate)
filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数组进行过滤
Predicate中的抽象方法
boolean test(T t)
[Java] 纯文本查看 复制代码
需求:
定义字符串数组: {"张三","李四","王五","左老六","何老七"}
将数组转换为Stream对象, 并过滤出名字中有"老字"的, 并将元素打印出来
public static void main(String[] args) {
String[] strings={"张三","李四","王五","左老六","何老七"};
Stream.of(strings)
.filter(name->name.contains("老"))
.forEach(name-> System.out.println(name));
}
}
终结方法count()统计流中元素个数
[Java] 纯文本查看 复制代码
long count() 返回此流中的元素数 是一个终结方法
[Java] 纯文本查看 复制代码
需求:
创建一个ArrayList集合, 存储整数: 1,2,3,4,5,6,7
将集合转换为Stream流对象, 调用count()获取流中元素的个数, 打印到控制台
public class Demo5 {
public static void main(String[] args) {
List<Integer> list=new ArrayList<>();
for (int i = 1; i < 8; i++) {
list.add(i);
}
long count = list.stream()
.count();
System.out.println(count);
}
}
map()映射转换
[Java] 纯文本查看 复制代码
<R>?Stream<R>?map(Function<??super?T,???extends?R>?mapper): 将T元素转换为R元素, 返回新的流
[Java] 纯文本查看 复制代码
需求:
定义字符串数组: {"1", "2", "3", "4"}
将数组转换为Stream流, 使用 map() 方法将流中的字符串都转换为int, 遍历输出转换后的结果
public class Demo4Map {
public static void main(String[] args) {
String[]str={"1", "2", "3", "4"};
Stream.of(str)
.map(s-> Integer.parseInt(s))
.forEach(i -> System.out.println(i) );
}
}
limit()获取数组前n个元素
[Java] 纯文本查看 复制代码
Stream<T> limit(long maxSize): 从流中获取前maxSize个元素 是一个延迟方法
[Java] 纯文本查看 复制代码
需求:
创建字符串数组: {"史莱姆", "暴风龙","哥布林"...}
将数组转换为Stream流, 从流中获取前6个并且长度为3的元素,将截取后的流遍历打印输出到控制台
public class Demo6LimitMethod {
public static void main(String[] args) {
ArrayList<String>list =new ArrayList<>();
list.add("史莱姆");
list.add("暴风龙");
list.add("哥布林");
list.add("觉醒者");
list.add("魔王");
list.add("勇者");
list.add("精灵");
list.add("骑士");
list.stream()
.limit(6)
.filter(s -> s.length()==3)
.forEach(s -> System.out.println(s));
}
}
skip()跳过前n个元素
[Java] 纯文本查看 复制代码
Stream<T>?skip(long?n): 从流中跳过n个元素, 获取后面的元素
[Java] 纯文本查看 复制代码
需求:
创建字符串数组: {"史莱姆", "暴风龙","哥布林"...}
将数组转换为Stream流, 获取流中跳过前4个元素并不包含"精"字的元素,将截取后的流遍历打印输出到控制台
public class Demo7SkipMethod {
public static void main(String[] args) {
ArrayList<String> list =new ArrayList<>();
list.add("史莱姆");
list.add("暴风龙");
list.add("哥布林");
list.add("觉醒者");
list.add("魔王");
list.add("勇者");
list.add("精灵");
list.add("骑士");
try{list.stream()
.skip(4)
.filter(s -> !s.contains("精"))
.forEach(s -> System.out.println(s));
}
catch (Exception e){
e.printStackTrace();
}
}
}
//报错:
java.lang.IllegalArgumentException: -5
at java.base/java.util.stream.ReferencePipeline.skip(ReferencePipeline.java:409)
at lesson.am.Demo7SkipMethod.main(Demo7SkipMethod.java:17)
try{list.stream()
.skip(-5)
.forEach(s -> System.out.println(s));
}
catch (Exception e){
e.printStackTrace();
}
}
}
//程序挂掉,控制台无内容
try{list.stream()
.skip(-5)
.forEach(s -> System.out.println(s));
}
catch (Exception e){
e.printStackTrace();
}
}
}
limit()合并成为一个流
[Java] 纯文本查看 复制代码
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b): 合并两个流为新流
[Java] 纯文本查看 复制代码
将2个集合分别转换为Stream流对象, 并使用Stream的静态方法concat()将2个流拼接为新的流,
遍历打印新流的元素
public class Demo8ConcatMethod {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("李四");
list.add("黄继光");
list.add("董存瑞");
list.add("张三三");
list.add("张德江");
ArrayList<String> list2 =new ArrayList<>();
list.add("史莱姆");
list.add("暴风龙");
list.add("哥布林");
list.add("觉醒者");
list.add("魔王");
list.add("勇者");
list.add("精灵");
list.add("骑士");
//使用List接口的默认方法stream获取集合的流对象
Stream<String> stream1 = list.stream();
Stream<String> stream2 = list2.stream();
//使用stream的静态方法concat拼接两个集合的流对象
Stream.concat(stream1,stream2).
//遍历拼接好的流对象
forEach(s -> System.out.println(s));
}
}
练习_使用Steam流处理集合元素
[Java] 纯文本查看 复制代码
需求:
现在有两个ArrayList集合存储队伍当中的多个成员姓名,要求使用"Stream流方式"依次进行以下若干操作步骤:
1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。 filter
2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。 limit
3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。 filter
4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。 skip
5. 将两个队伍合并为一个队伍;存储到一个新集合中。 concat
6. 根据String姓名创建Person对象;存储到一个新集合中。 String -> Person 转换 map(Function)
7. 打印整个队伍的Person对象信息 forEach
public class Demo1StreamTest {
public static void main(String[] args) {
ArrayList<String> list1=new ArrayList<>();
list1.add("迪内热巴");
list1.add("宋远桥");
list1.add("苏星河");
list1.add("石破天");
list1.add("石中玉");
list1.add("老子");
list1.add("庄子");
list1.add("洪七公");
ArrayList<String>list2=new ArrayList<>();
list2.add("古力娜扎");
list2.add("张无忌");
list2.add("赵丽颖");
list2.add("张三丰");
list2.add("尼古拉斯赵四");
list2.add("张天爱");
list2.add("张二狗");
Stream<String> stream1 = list1.stream()
.filter(name -> name.length() == 3)
.limit(3);
Stream<String> stream2 = list2.stream()
.filter(name -> name.startsWith("张"))
.skip(2);
Stream.concat(stream1,stream2)
.map(name->new Person(name))
.forEach(name-> System.out.println(name));
}
}
方法引用
[Java] 纯文本查看 复制代码
方法引用能简化以下场景: (方法名后不要写小括号)
场景 格式 简化之前的Lambda 方法引用简化后
1. 通过对象名引用成员方法 对象名::成员方法名 ()->person.eat() person::eat
2. 通过类名引用静态方法 类名::静态方法名 i -> Math.abs(i) Math::abs
3. 通过super引用父类成员方法 super::父类方法名 ()->super.eat(); super::eat
4. 通过this引用本类成员方法 this::本类方法名 ()->this.eat(); this::eat
5. 引用某个类的构造方法 类名::new name->new Person(name) Person::new
6. 引用创建数组的方法 数据类型[]::new length->new int[length]; int[]::new
通过对象名引用成员方法
[Java] 纯文本查看 复制代码
需求:
已知如下代码, 请在Test类中补全main()方法, 分别使用Lambda和方法引用调用printString方法
// 定义函数式接口: 模拟Java中已经提供的函数式接口
@FunctionalInterface
public interface Printable {
// 定义功能: 打印s. 如何打印需要我们传递Lambda实现
void print(String s);
}
// 定义一个类: 模拟Java中已经定义好的类和方法
public class MethodRefObject {
// 将字符串变大写再打印出来
public void printUpperCaseString(String str){
System.out.println(str.toUpperCase());
}
}
public class Test {
// 定义方法用于打印Hello, 参数为 打印字符串的方式
public static void printString(Printable lambda){
lambda.print("Hello");
}
public static void main(String[] args) {
// TODO 补全代码. 普通Lambda方式调用printString方法: 创建MethodRefObject对象, 调用已经存在方法printUpperCase
// TODO 补全代码. 方法引用方式调用printString方法: 创建MethodRefObject对象, 直接引用obj的printUpperCaseString方法
}
}
public class Test {
// 定义方法用于打印Hello, 参数为 打印字符串的方式
static void printString(Printable lambda){
lambda.print("Hello");
}
public static void main(String[] args) {
MethodRefObject mrf=new MethodRefObject();
//匿名内部类
printString(new Printable() {
@Override
public void print(String s) {
mrf.printUpperCaseString(s);
}
});
//lamda表达式
printString(s -> mrf.printUpperCaseString(s));
//方法引用
printString(mrf::printUpperCaseString);
}
}
// 定义一个类: 模拟Java中已经定义好的类和方法
public class MethodRefObject {
// 将字符串变大写再打印出来
void printUpperCaseString(String str){
System.out.println(str.toUpperCase());
}
}
// 定义函数式接口: 模拟Java中已经提供的函数式接口
@FunctionalInterface
public interface Printable {
// 定义功能: 打印s. 如何打印需要我们传递Lambda实现
void print(String s);
}
通过类名引用静态成员方法
[Java] 纯文本查看 复制代码
public class Test {
static int method(int number,Calcable c){
return c.calcAbs(number);
}
public static void main(String[] args) {
//匿名内部类
method(-10, new Calcable() {
@Override
public int calcAbs(int number) {
return Math.abs(number);
}
});
//lambda表达式
method(-10,number->Math.abs(number));
//方法引用
int method = method(-10, Math::abs);
System.out.println(method);
}
}
// 定义函数式接口: 模拟Java中已经提供的函数式接口
@FunctionalInterface
public interface Calcable {
// 计算整数的绝对值并返回. 怎么计算需要我们传递Lambda来实现
int calcAbs(int number);
}
通过super引用父类的成员方法
[Java] 纯文本查看 复制代码
需求:
在Man类中的show()方法中, 补全三种方式调用method方法的代码:
// 定义见面打招呼的函数式接口: 模拟Java中已经提供的函数式接口
@FunctionalInterface
public interface Greetable {
// 见面打招呼的方法. 怎么打招呼需要传递Lambda实现
void greet();
}
// 定义父类: 人类
public class Human {
//定义一个sayHello的方法
public void sayHello(){
System.out.println("Hello 我是Human!");
}
}
// 定义子类: 男人类, 继承人类
public class Man extends Human {
// 子类重写了父类sayHello的方法
@Override
public void sayHello() {
System.out.println("Hello 我是Man!");
}
//定义方法用来见面打招呼. 参数是见面打招呼的方式
public void method(Greetable g){
g.greet();
}
// 因为要使用super, 所以定义这个非静态方法
public void show(){
// TODO 补全代码. Lambda方式调用method方法: 直接super调用父类方法, 省去创建父类对象的麻烦
// TODO 补全代码. 方法引用方式调用method方法: 直接引用父类的sayHello方法
}
public static void main(String[] args) {
new Man().show();
}
}
## 2.7 通过super引用成员方法
如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代。首先是函数式接口:
@FunctionalInterface
public interface Greetable {
void greet();
}
```
然后是父类`Human`的内容:
```java
public class Human {
public void sayHello() {
System.out.println("Hello!");
}
}
```
最后是子类`Man`的内容,其中使用了Lambda的写法:
```java
public class Man extends Human {
@Override
public void sayHello() {
System.out.println("大家好,我是Man!");
}
//定义方法method,参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
//调用method方法,使用Lambda表达式
method(()->{
//创建Human对象,调用sayHello方法
new Human().sayHello();
});
//简化Lambda
method(()->new Human().sayHello());
//使用super关键字代替父类对象
method(()->super.sayHello());
}
}
```
但是如果使用方法引用来调用父类中的`sayHello`方法会更好,例如另一个子类`Woman`:
```java
public class Man extends Human {
@Override
public void sayHello() {
System.out.println("大家好,我是Man!");
}
//定义方法method,参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
method(super::sayHello);
}
}
```
在这个例子中,下面两种写法是等效的:
- Lambda表达式:`() -> super.sayHello()`
- 方法引用:`super::sayHello`
字数到上限了,先写到这