Java学习笔记(二)
Lambda表达式的简单使用
lambda 表达式三要素:形式参数,箭头,代码块
Lambda 表达式的标准格式
格式:
( 形式参数 ) -> { 代码块 }
1 | new Thread(() -> { |
- 形式参数:如果有多个参数,参数之间用逗号隔开。如果没有参数,留空即可
- ->:固定写法
- 代码块:具体要做的内容
Lambda 表达式注意事项
- 有一个接口,并且接口中有且仅有一个抽象方法
- 必须要有上下文环境,才能推导出 Lambda 对应的接口(也就是说Lambda表达式不能单独使用)
Lambda 表达式实例
既没有参数也没有返回值的情况
1 | public class lambda01 { |
有参数,无返回值
1 | public class lambda02 { |
有参数,有返回值
1 | public class Lambda03 { |
Lambda 表达式的省略模式
省略规则:
- 参数类型可以省略,但是又多个参数的情况下,不能只省略一个
- 如果参数有且仅有一个,那么小括号可以省略
- 如果代码块的语句只有一条,那么可以省略大括号和分号
- 如果代码块的语句只有一条,并且这条语句是 return 语句,那么可以省略大括号和分号和return
1 | MyOperate((int a, int b) -> { |
Lambda 表达式和匿名内部类的区别
- 所需类型不同
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式:只能是接口
- 使用限制不同
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中有多于一个的抽象方法,则只能使用匿名内部类
- 实现原理不同
- 匿名内部类:编译之后,产生一个单独的.class 字节码文件
- Lambda 表达式:编译之后,没有一个单独的.class 字节码文件,对应的字节码会在运行的时候动态生成
Java中的IO流
概述:
IO流原理及流的分类
流的分类
- 按操作数据单位不同分为:字节流(8bit)(二进制文件),字符流(按字符)(文本文件)
- 按数据流的流向不同分为:输入流,输出流
- 按流的角色的不同分为:节点流,处理流/包装流
Java操作文件方式
创建文件
1 | 1. new File(String pathName); //根据路径创建一个File对象 |
在创建文件对象后,对象只是存储在内存中,并没有实际在硬盘中创建,需要调用文件对象的 createNewFile() 方法才可真正创建。
1 | public static void main(String[] args) { |
获取文件信息方法
- getName //获取文件名
- getAbsolutePath //获取绝对路径
- getParent //获取文件父级目录
- length //获取文件字节数
- exists //判断文件是否存在,返回bool类型
- isFile //判断是否是文件,bool类型
- isDirectory //判断是否是目录,bool类型
创建目录
用mkdir方法,如果要创建多级目录,用mkdirs方法。
删除文件或者目录
用Delete方法,当用delete方法删除目录时,只能用来删除空目录。
流
字节流(二进制文件)
- InputStream 字节输入流
- OutputStream 字节输出流
如:拷贝图片文件
1 | //将 1.jpg 文件中的内容读取到 2.jpg 中 |
字符流(文本文件)
- Reader 字符输入流
- Writer 字符输出流
对应方法有:
处理流
字节流和处理流
注意:字节是最基本的单位,字节流既可以处理二进制文件,也可以处理字符文件
处理流 BufferedReader 和 BufferedWriter
- BufferedReader 和 BufferedWriter 属于字符流,是按照字符来读取数据的,不能操作二进制文件
- 关闭处理流时,只需要关闭外层流即可(外层流指高级的那层流)
1 | BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath)); |
关闭时,只需要关闭最外层即可
1 | bufferedReader.close(); |
常用方法:
- BufferedReader
- readLine 读取一行,但并不会读取换行符
- BufferedWriter
- newLine 插入一个与系统相关的换行
- write 多种重载,写入文件
处理流 BufferedInputStream 和 BufferedOutputStream
- BufferedInputStream 和 BufferedOutputStream 是字节流,处理二进制文件
对象流 ObjectInputStream 和 ObjectOutputStream
- 顾名思义,对象流就是可以把一个对象保存到文件(序列化)或者把对象从文件中读取出来(反序列化)
- 序列化和反序列化
- 序列化就是在保存数据时,保存数据的值和数据类型
- 反序列化就是在恢复数据时,恢复数据的值和数据类型
- 需要让某个对象支持序列化机制,则必须让其类是可序列化的,而为了让某个类是可序列化的,该类必须实现如下两个接口之一
- Serializable //这时一个标记接口
- Externalizable
1 | //序列化示例 |
注意事项:
读写顺序要一致
序列化和反序列化所使用的类一定要是同一个类,因为在序列化的过程中,会把包信息也加入在内
serialVersionUID 是序列化的版本号,可以提高兼容性(在类中新增属性时,只要版本号相同,他也会认为是同一个类。例如:private static final long serialVersionUID = 1L;
序列化对象时,默认将里面所有的属性都进行序列化,但除了static 和 transient 修饰的成员
序列化对象时,要求里面属性的类型也需要实现序列化接口
序列化具备可继承性,也就是说如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
要求序列化或反序列化对象,需要实现 Serializable 接口
标准输入输出流
System.in 和 System.out
System 类的 public final static InputStream in = null
System.in 的编译类型 InputStream, 运行类型 BufferedInputStream(对应键盘)
System.out 的编译类型 PrintStream,运行类型 PrintStream(对应屏幕/显示器)
转换流
InputStreamReader 和 OutputStreamWriter
作用:字节流转成字符流,可以解决字符乱码问题
使用:
- InputStreamReader 是 Reader 的子类,可以将 InputStream(字节流)包装/转换成 Reader(字符流)
- OutputStreamWriter 是 Writer 的子类,可以将 OutputStream(字节流)包装/转换成 Writer(字符流)
- 当处理纯文本的数据时,使用字符流效率往往会更高,而且可以解决中文乱码的问题
- 可以在使用时指定文件编码格式(比如 utf-8,gbk 等)
1 | //将a.txt用字节流方式打开并转成utf-8字符流形式,然后再包装成处理流 |
打印流
PrintStream 和 PrintWriter
打印流只有输出流,没有输入流,打印并不一定打印到屏幕上,也可以打印到文件里,网页上等等( 用System.setOut(); 方法 )。
- PrintStream 是字节流,PrintWriter 是字符流
Properties类
专门用于读写配置文件的集合类
配置文件格式:
键1=值1
键2=值2
注意:键值对不需要有空格,值不需要用引号引起来,默认是 String 类型
Properties常用方法
- load 加载配置文件的键值对到 Properties 对象
- list 将数据显示到指定设备
- getProperty(key) 根据键获取值
- setProperty(key, value) 设置键值对到 Properties 对象(底层还是哈希表)
- store 将 Properties 中的键值对存储到配置文件,在 idea 中,保存信息到配置文件,如果含有中文,会存储为 unicode 码
Java使用Stream流快速编程
概述
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用 StreamAPI 对集合数据进行操作,就类似于使用 SQL 执行的数据路查询
官方文档对它的介绍如下:
1 | A sequence of elements supporting sequential and parallel aggregate operations. |
大概意思为:
1 | 支持顺序和并行聚合操作的元素序列。 |
可以看出 Stream
- 是一个元素序列
- 支持对元素的串行和并行操作
- 所有对流的操作并不会改变原集合的值
那么串行操作和并行操作有什么区别呢?
我的理解是:
- 串行操作类似于单线程,处理数据时是一条一条处理的,所以处理的数据自然也是有序的
- 并行操作类似于多线程,多条数据一起处理,虽然处理时效率较高,那么处理时数据自然也是无序的
总体来说,使用 Stream 的基本步骤为:
- 生成 Stream 操作(创建操作)
- 中间操作(转换操作)
- 终结操作(聚合操作)
这里我以 Student 类来举例具体操作(getter、setter、constructor、toString方法略)
1 | public class Student { |
定义返回数据的工具类
1 | public class StudentUtils { |
Stream 的创建
通过集合创建 Stream
函数原型:
1 | default Stream<E> stream() { |
第一个为返回一个串行的 Stream 流,第二个返回一个并行的 Stream 流
使用举例:
1 | public static void main(String[] args) { |
通过数组创建 Stream
通过 Arrays 类的 Stream 方法,函数原型为:
1 | public static <T> Stream<T> stream(T[] array) { |
使用举例:
1 | public static void main(String[] args) { |
通过 Stream 类的 of 方法来创建
函数原型:
1 | public static<T> Stream<T> of(T... values) { |
使用举例:
1 | public static void main(String[] args) { |
此处 Stream 自动把 1,2,3 这些数字封装成了对应的包装类 Integer
创建 Stream 无限流
通过 Stream 类的 iterate 方法和 generate 方法来创建无限流
iterate函数定义(它包含一个重载的方法,两参数的和三参数的):
1 | public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) |
iterate使用举例:
- 两参数的
1 | public static void main(String[] args) { |
第一个参数为seed(种子值),根据种子生成第一个x的值并放到流中。第二个参数是一个lambda表达式,本式的意思是每次让x加1并把结果放入流中
- 三参数的
1 | public static void main(String[] args) { |
第一个参数为seed(种子值),根据种子生成第一个x的值并放到流中。第二个参数为一个lambda表达式,返回boolean类型值,若值为真,则根据第三个参数继续生成流;若值为假,则从此刻停止生成流
generate函数定义:
1 | public static<T> Stream<T> generate(Supplier<? extends T> s) |
generate使用举例:
1 | public static void main(String[] args) { |
创建 Stream 并行流
创建并行流有两种方式,一是直接在集合的基础上创建并行流,二是在串行流的基础上创建并行流
1 | public static void main(String[] args) { |
Stream 的中间操作
Stream常见的中间操作
方法名 | 参数类型 | 方法说明 |
---|---|---|
filter | Predicate | 用于对流中的数据进行过滤 |
concat | Stream, Stream | 合并两个流为一个流 |
distinct | 通过流中元素的 hashcode 去除重复元素 | |
sorted | 将流中元素自然排序 | |
sorted | Comparator | 将流中元素根据排序器进行排序 |
map | Function | 可以得到流中每一个元素,将流中元素进行映射,返回映射结果组成的流 |
peek | Consumer | 可以得到流中每一个元素,然后对元素进行操作,无返回值 |
limit | long | 截取前n个元素 |
skip | long | 与limit相反,跳过前n个元素,返回后面的元素 |
这几种常见的中间操作大致也可归为几类:
过滤操作
- filter:用于对流中的数据进行过滤
- distinct:通过流中元素的 hashcode 去除重复元素
1 | public static void main(String[] args) { |
截取操作
- limit:获取前n个元素
- skip:跳过前n个元素,获取后面的元素
1 | public static void main(String[] args) { |
排序操作
- sorted:进行自然排序
- sorted(Comparator):按照比较器进行排序
1 | public static void main(String[] args) { |
映射操作
- map:可以得到流中每一个元素,将流中元素进行映射,返回映射结果组成的流
1 | public static void main(String[] args) { |
消费操作
- peek:可以得到流中每一个元素,然后对元素进行操作,无返回值
1 | public static void main(String[] args) { |
Stream 终结操作
匹配、聚合操作
- allMatch:对流中所有元素进行判断,如果都满足条件返回true,否则返回false
1 | public static void main(String[] args) { |
- noneMatch:对流中所有元素进行判断,如果都不满足条件返回true,否则返回false
1 | public static void main(String[] args) { |
- anyMatch:对流中所有元素进行判断,只要有一个满足条件的就返回true,否则返回false
1 | public static void main(String[] args) { |
- findFirst:函数返回值为 Optional 类型。如果流为空,返回一个空 Optional,否则返回第一个元素
1 | public static void main(String[] args) { |
- findAny:如果数据较少,并且串行的情况下一般还是会返回第一个元素,但是当并行的情况下一般会返回任何一个元素
1 | public static void main(String[] args) { |
规约操作
- count:返回流中元素个数
1 | public static void main(String[] args) { |
- max:参数为 Comparator 类型的比较器,返回值为 Optional 类型。返回比较器比较结果的最后一个元素的 Optional 类型
1 | public static void main(String[] args) { |
- min:参数为 Comparator 类型的比较器,返回值为 Optional 类型。返回比较器比较结果的第一个元素的 Optional 类型
1 | public static void main(String[] args) { |
收集操作
这个就比较简单了,主要借助 Collectors
Collectors.toList:把 Stream 流转化为 List 集合
Collectors.toSet:把 Stream 流转化为 Set 集合
Collectors.toMap:把 Stream 流转化为 Map 集合
JDBC编程
JDBC 编程六部
- 注册驱动
- 获取数据库连接
- 获取数据库操作对象
- 执行 SQL 语句
- 处理查询结果集
- 释放资源
注意:连接数据库时的URL:
示例:
1 | public static void main(String[] args) { |
功能类详解
DriverManager
DriverManager 驱动管理对象
注册驱动
方法一:注册给定地驱动程序
1 | Driver driver = new com.mysql.jdbc.Driver(); |
方法二:直接使用com.mysql.cj.jdbc.Driver类中的静态代码块
1 | Class.forName("com.mysql.cj.jdbc.Driver"); |
源代码:
1 | package com.mysql.cj.jdbc; |
注意:
- 一般情况下不需要调用DriverManager的静态方法registerDriver(),因为只要静态类被使用,就会自动调用其中的静态代码块完成注册驱动
- mysql5 之后可以省略注册驱动的步骤
获取数据库连接
获取数据库连接对象:
1 | static Connection getConnection(String url, String username, String password); |
Connection
Connection 数据库连接对象
获取执行者对象
- 获取普通执行者对象:
1 | Statement createStatement(); |
- 获取预编译执行者对象
1 | PreparedStatement prepareStatement(String sql); |
管理事务
手动管理事务:
1 | setAutoCommit(boolean autoCommit); |
jdbc 默认是自动提交事务,通过将参数设置为 false 可以开启手动提交事务
提交事务:
1 | commit(); |
回滚事务:
1 | rollback(); |
释放资源
立即将数据库连接对象释放:
1 | void close(); |
Statement
Statement 执行 sql 语句
执行 DML 语句(增、删、改 操作)
1 | int executeUpdate(String sql); |
返回值:int 类型,指影响的行数
参数:insert、update、delete 语句
执行 DQL 语句(查询操作)
1 | ResultSet executeQuery(String sql); |
返回值:ResultSet 类型,封装查询结果
参数:select 语句
释放资源
1 | void close(); |
ResultSet
ResultSet 结果集对象
判断结果集中是否还有数据
1 | boolean next(); |
返回值:有数据则返回 true,并将索引向下移动一行。没有数据则返回 false
获取结果集中的数据
1 | XXX getXxx("数据库列名"); |
XXX:java 数据类型
如:
1 | String getString("name"); |
释放资源
1 | void close(); |
PrepareStatement
PrepareStatement 预编译执行者对象
原理:
- 在执行 sql 语句之前,将 sql 语句进行提前编译。明确 sql 语句的格式后,就不会改变了,剩下的内容都只会被认为是参数
- sql 语句中的参数使用 ?做占位符
为 ?占位符赋值的方法
1 | setXxx(参数1, 参数2); |
Xxx:数据类型
参数1:?的位置编号(编号从1开始)
参数2:?的实际参数
1 | String sql = "select * from book where id=?"; |
执行 sql 语句
- 执行 insert、update、delete 语句
1 | int executeUpdate(); |
- 执行 select 语句
1 | ResultSet executeQuery(); |
释放资源
1 | void close(); |
数据库连接池
自定义数据库连接池
实现步骤
- 定义一个类,实现 DataSource 接口
- 定义一个容器,用于保存多个 Connection 连接对象
- 定义静态代码块,通过 JDBC 工具类获取 10 个连接对象并保存到容器中
- 重写 getConnection 方法,从容器中获取一个连接并返回
- 定义 getSize 方法,用于获取容器的大小并返回
归还连接
归还数据库连接的方式:
- 继承方式
思想:
- 通过打印连接对象,发现 DriverManager 获取的连接实现类是 JDBC4Connection
- 那么就可以自己定义一个类,继承 JDBC4Connection这个类,重写close() 方法,完成连接对象的归还
问题:
- 我们虽然定义了一个子类完成了连接归还的操作,但是DriverManager获取的还是 JDBC4Connection这个对象,并不是我们自己定义的子类对象。多态允许把一个子类对象指向父类引用,但是这里强转相当于把一个父类对象指向子类引用,会报语法错误。所以此方法行不通
- 装饰设计模式
思想:
- 可以自己定义一个类,实现 Connection 接口。这样就具备了和 JDBC4Connection相同的行为了
- 重写close() 方法,完成连接的归还,其余的功能还调用 mysql 驱动包实现类原有的方法即可
问题:
- 实现 Connection 接口后,有大量的方法需要在自定义类中进行重写,非常麻烦
- 适配器设计模式
思想:
- 我们可以自己定义一个适配器类,实现 Connection 接口,将除 close 外的所有方法进行实现
- 自定义连接类只需要继承这个适配器类,重写需要改进的 close 方法即可
问题:
- 虽然自定义连接类比较简洁,但是适配器类和上个方法一样麻烦
- 动态代理方式
思想:
- 通过 Proxy 来完成对 Connection 实现类对象的代理
- 代理过程中判断如果执行的是 close 方法,就将连接归还到连接池中,如果是其他的方法就继续调用原来的功能即可
第三方连接池
c3p0 连接池
1 | public static void main(String[] args) throws Exception { |
Druid 连接池
1 | public static void main(String[] args) throws Exception { |