文章目录
Java基础上,请看这里
不可变集合(jdk9)
集合一旦设置,无法再次修改
- List 集合:List.of
List<Integer> list = List.of(15, 26, 66, 32, 5, 1);
System.out.println(list);
// [15, 26, 66, 32, 5, 1]
- Set 集合:Set.of
Set 集合在存入的时候,不可以写入重复的数值
Set<Integer> set = Set.of(5, 6, 3);
System.out.println(set);
//[6, 5, 3]
- Map 集合:Map.of
细节:of方法,参数有上限,最多只能传递20个参数
Map<Integer, Integer> map = Map.of(1,1,2,2,3,3,4,4);
System.out.println(map);
//{4=4, 3=3, 2=2, 1=1}
第二中方式,解决只能存入20个参数
//1.创建一个普通的Map集合
HashMap<String, String> hm = new HashMap<>();
hm.put("张三", "南京");
hm.put("李四", "北京");
hm.put("王五", "上海");
hm.put("赵六", "北京");
hm.put("孙七", "深圳");
hm.put("周八", "杭州");
hm.put("吴九", "宁波");
hm.put("郑十", "苏州");
hm.put("刘一", "无锡");
hm.put("陈二", "嘉兴");
hm.put("aaa", "111");
// 2. 转为不可变集合, 先转为set集合
Set<Map.Entry<String, String>> entries = hm.entrySet();
// 3. 变成数组,map.Entry
Map.Entry[] arr = new Map.Entry[0];
// 重点:toArray方法在底层会比较集合和数组的长度, 如果集合的长度 > 数组的长度,此时就会重新创建数组。
entries.toArray(arr);
// JDK9
Map map = Map.ofEntries(arr);
简化(JDK9)
Map.Entry[] entries1 = hm.entrySet().toArray(new Map.Entry[0]);
Map map = Map.ofEntries(entries1);
再次简化(copyOf)(JDK10)
如果传递的是不可变集合,返回原数据,如果不是变成不可变集合返回
- 优点:如果是传入的集合,原集合改变,不会影响新的不可变集合,之前的of会影响
Map<String, String> ss = Map.copyOf(hm);
数组转集合(浅拷贝)
// 数组转集合也是不可变集合,且浅拷贝
String[] arr = {"1", "2", "3", "4", "5", "6"};
List<String> list = Arrays.asList(arr);
修饰符
transient 修饰
表示这个属性不被序列化
stream流
用于简化集合和数组的API,结合lambda表达式
stream流的三个方法
-
获取stream流
创建一条流水线,并把数据放到流水线上准备进行操作 -
中间方法
流水线的操作,操作完之后还可以继续操作 -
终结方法
就是流水线的最后操作(count,forEach,收集方法collect)
单列集合获取stram 流
// 集合 获取 steam流
ArrayList<String> list = new ArrayList<>();
// 获取 stram
Stream<String> s = list.stream();
双列集合获取stram 流
// Map 集合获取stram流
TreeMap<String, Integer> map = new TreeMap<>();
// 键流
Stream<String> keyStream = map.keySet().stream();
// 值流
Stream<Integer> valuesStream = map.values().stream();
// 键值对流
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
数组获取stram 流
// 数组获取stream流
int[] arr = {1,2,4,5,6,34};
IntStream stream = Arrays.stream(arr);
第二种方式,不支持基本数据类型
Stream<int[]> arr1 = Stream.of(arr);
filter(过滤条件)
ArrayList<String> lis = new ArrayList<>();
lis.add("张无忌");
lis.add("周族若");
lis.add("赵敏");
lis.add("张强");
lis.add("张三丰");
lis.add("张三丰");
// 直接链式条件过滤,最后forEach打印
lis.stream().filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));
无法使用 Collections.addAll 直接添加所有元素
count(返回长整型)
ArrayList<String> lis = new ArrayList<>();
lis.add("张无忌");
lis.add("周族若");
lis.add("赵敏");
lis.add("张强");
lis.add("张三丰");
lis.add("张三丰");
// 过滤条件,长度等于3,用count返回
long count = lis.stream().filter(s -> s.length() == 3).count();
System.out.println(count);
limit(只取前 N 位)
ArrayList<String> lis = new ArrayList<>();
lis.add("张无忌");
lis.add("周族若");
lis.add("赵敏");
lis.add("张强");
lis.add("张三丰");
lis.add("张三丰");
// 过滤 姓张的,并且只要前两位 打印
lis.stream().filter(s -> s.startsWith("张")).limit(2).forEach(s -> System.out.println(s));
skip(跳过前 N 位)
ArrayList<String> lis = new ArrayList<>();
lis.add("张无忌");
lis.add("周族若");
lis.add("赵敏");
lis.add("张强");
lis.add("张三丰");
lis.add("张三丰");
lis.stream().filter(s -> s.startsWith("张")).skip(2).forEach(System.out::println);
map(数据加工)
ArrayList<String> lis = new ArrayList<>();
lis.add("张无忌");
lis.add("周族若");
lis.add("赵敏");
lis.add("张强");
lis.add("张三丰");
lis.add("张三丰");
// 利于map把 每个名字,加工成 对象
lis.stream().map(s -> new student(s)).forEach(s -> System.out.println(s));
// 使用方法引用
lis.stream().map(student::new).forEach(System.out::println);
distinct(去重复)
底层是:HashSet,利用equals 和 HashCode方法去重
ArrayList<String> lis = new ArrayList<>();
lis.add("张无忌");
lis.add("周族若");
lis.add("赵敏");
lis.add("张强");
lis.add("张三丰");
lis.add("张三丰");
lis.stream().filter(s -> s.startsWith("张")).distinct().forEach(System.out::println);
of(返回一个包含元素的流)
注意:Stream接口中静态方法of的细节
- 方法的形参是一个可变参数,可以传递一堆零散的数据,也可以传递数组
- 但是数组必须是引用数据类型的,如果传递基本数据类型,是会把整个数组当做一个元素,放到stream当中。
Stream<Integer> s2 = Stream.of(1, 3, 5);
concat(合并两个流)
如果两个流数据不一致,那么将合并,并且丢失子类特有属性
ArrayList<String> lis = new ArrayList<>();
lis.add("张无忌");
lis.add("周族若");
lis.add("赵敏");
lis.add("张强");
lis.add("张三丰");
lis.add("张三丰");
Stream<String> s1 = lis.stream().filter(s -> s.startsWith("张")).distinct();
Stream<Integer> s2 = Stream.of(1, 3, 5);
// 将 s1 和 s2 合并到一起
Stream.concat(s1,s2).forEach(System.out::println);
实用方法
max | 获取最大数 |
---|---|
get | 返回当前结果 |
sorted | 排序 |
获取数字
素材
List<Score> list = new ArrayList<>();
Score score = new Score(11.2);
Score score2 = new Score(11.2);
Score score3 = new Score(11.2);
Score score4 = new Score(11.2);
Score score5 = new Score(11.2);
Collections.addAll(list, score,score2,score3,score4,score5);
实现获取小数
// 指定字段,继续调用sum获取小数类型结果
double sum = list.stream().mapToDouble(Score::getPrice).sum();
System.out.println(sum); // 56.0
实现获取整数
double sum = list.stream().mapToInt(Score::getPrice).sum();
System.out.println(sum);
stream流的收集操作
就是把Stream流操作后的结果数据转回到集合或者数组中去。
转为 list 和 set(Collectors.toList)
流是不可逆操作,只能使用一次,不能一个流转两次
ArrayList<String> lis = new ArrayList<>();
lis.add("张无忌");
lis.add("周族若");
lis.add("赵敏");
lis.add("张强");
lis.add("张三丰");
lis.add("张三丰");
Stream<String> s1 = lis.stream().filter(s -> s.startsWith("张")).distinct();
// 转list
List<String> list = s1.collect(Collectors.toList());
// 转set
Set<String> set = s1.collect(Collectors.toSet());
转为数组(toArray)
ArrayList<String> lis = new ArrayList<>();
lis.add("张无忌");
lis.add("周族若");
lis.add("赵敏");
lis.add("张强");
lis.add("张三丰");
lis.add("张三丰");
Stream<String> s1 = lis.stream().filter(s -> s.startsWith("张")).distinct();
// 返回 Object 数组
Object[] string = s1.toArray();
// 指定返回值
String[] string1 = s1.toArray(new IntFunction<String[]>() {
@Override
public String[] apply(int value) {
return new String[value];
}
});
// 简化代码
String[] string2 = s1.toArray(value -> new String[0]);
// 方法引用简化
String[] string3 = s1.toArray(String[]::new);
转map(toMap)
- 参数1 : 键的生成规则
- 参数2 : 值的是生成规则
参数1:泛型一:表示流中的每一个数据。
泛型二:表示map中键的数据类型
方法体:apply表示生成键的代码,返回值就是生成的键
注意:map集合不能重复
Map<String, Integer> collect =
// 过滤男,取出姓名和年龄
list.stream().filter(s -> "男".equals(s.split("-")[1]))
// 键的方法
.collect(Collectors.toMap(new Function<String, String>() {
@Override
public String apply(String s) {
return s.split("-")[0];
}
// 值的方法
}, new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s.split("-")[2]);
}
}));
System.out.println(collect);
简化
Map<String, Integer> collect1 = list.stream().filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toMap(s -> s.split("-")[0], s -> Integer.parseInt(s.split("-")[2])));
System.out.println(collect1);
转map,并且key是List集合分组后
// 将列表转换为流
Map<String, List<String>> map = list.stream()
// 过滤满足条件的元素
.filter(s -> s.getAbnormal().split("-")[1].equals("2"))
// 根据指定的分类器进行分组
.collect(Collectors.groupingBy(
// // 根据条件返回分类键
optical -> {
return optical.getAbnormal().split("-")[2].equals("1") ? "board" : "slot";
},
// 对分组后的元素进行映射和收集
Collectors.mapping(
// 映射函数,将元素映射为指定类型
new Function<Optical, String>() {
@Override
public String apply(Optical optical) {
// 将Optical对象映射为topologicalLinkid字符串
return optical.getTopologicalLinkid();
}
},
// 将映射后的结果收集到列表中
Collectors.toList()
)
));
转为不可变集合(扩展 Jdk 16)
ArrayList<String> lis = new ArrayList<>();
lis.add("张无忌");
lis.add("周族若");
lis.add("赵敏");
lis.add("张强");
lis.add("张三丰");
lis.add("张三丰");
Stream<String> s1 = lis.stream().filter(s -> s.startsWith("张")).distinct();
List<String> s = s1.toList();
实用
异常处理
throws (捕获和抛出异常,但是不能处理异常)
- 传递性
- 能抛出更大的异常
- 多个抛出用逗号分隔,不区分大小写
- 重写里,子类不能比父类抛出更加宽泛的异常
try catch 捕获和处理异常
- 如果try中的代码报错,其下方代码不再执行,执行catch中的
- 如果try中的代码不报错,不再执行catch中的
- 可以有多个catch,顺序要从小到大
- 直接写他们的父类,或者直接写Exception
- try(打开的资源流)可以关闭
- finally,和try…catch连用,必会执行的代码
try{
高危代码
}catch(异常类类名 形参名){
处理方法
}
JDK 7 的时候,可以在catch中写 | 异常,写多个异常
如果try中出现的问题没有被捕获,就会交给虚拟机处理
异常是程序在“编译”或者“执行”的过程中可能出现的问题
- Error:系统级别问题、JVM退出等,代码无法控制
- Exception:java.lang包下,称为异常类,程序本身可以处理的问题
- RuntimeException及其子类:运行时异常,编译阶段不会报错。 (空指针异常,数组索引越界异常)
- 除RuntimeException之外所有的异常:编译时异常,编译期必须处理的,否则程序不能通过编译。 (日期格式化异常)。
自定义编译时 / 运行时 异常类
- 继承 Exception 类
- 重写无参和有参构造
- 编译时 Exception
- 运行时 RuntimeException
类名后缀要加上Exception,只是规范
// 继承 Exception 类
public class 类名 extends Exception{
}
// Exception ,这里可以更换异常
public class kk extends Exception{
// 重写构造器
public kk() {
}
// 有参
public kk(String message) {
super(message);
}
}
两种调用方式
直接抛出
throw new kk("出错了");.
....
异常常用方法
getMessage(错误提示信息)
int[] arr = {1, 2, 3, 4, 5};
try {
System.out.println(arr[10]);
} catch (ArrayIndexOutOfBoundsException e) {
// 获取报错提示信息
String message = e.getMessage();
System.out.println(message); // 10
}
toString(获取报错信息, 不带红色提示)
int[] arr = {1, 2, 3, 4, 5};
try {
System.out.println(arr[10]);
} catch (ArrayIndexOutOfBoundsException e) {
// 获取报错信息, 不带红色提示
String s = e.toString();
System.out.println(s); // java.lang.ArrayIndexOutOfBoundsException: 10
}
printStackTrace(获取红色报错信息)
不会结束虚拟机
int[] arr = {1, 2, 3, 4, 5};
try {
System.out.println(arr[10]);
} catch (ArrayIndexOutOfBoundsException e) {
// 获取红色报错信息
e.printStackTrace();
//java.lang.ArrayIndexOutOfBoundsException: 10
// at com.test.main(test.java:15)
}
抛出异常(throws,throw)
throws
写在方法声明处,告诉调用者使用本方法会有哪些异常
编译时异常:必须写
运行时异常:可以不写
throw
写在方法内,结束本方法,抛出异常,交给调用者
日志框架
Logback
步骤:
- 把jar包导入到项目去
- logback-classic-1.4.4 完整实现了slf4j API的模块。(必须有)
- logback-core-1.4.4 该模块为其他两个模块提供基础代码。 (必须有)
- slf4j-api-2.0.3 日志接口
- 将 logback.xml核心配置文件,直接拷贝到src目录下
Logback核心配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--输出流对象 默认 System.out 改为 System.err-->
<target>System.out</target>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern>
</encoder>
</appender>
<!-- File是输出的方向通向文件的 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--日志输出路径-->
<file>C:/code/itheima-data.log</file>
<!--指定日志文件拆分和压缩规则-->
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定压缩文件名称,来确定分割文件方式-->
<fileNamePattern>C:/code/itheima-data2-%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>
<!--
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
, 默认debug
<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
-->
<!-- 这里是输出配置,console表示在控制台输出,删除就没有了-->
<root level="ALL">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE" />
</root>
</configuration>
输出到文件
// 获取对象 参数写 监控的类名 加 class
public static final Logger LOGGER = LoggerFactory.getLogger("pp.class");
public static void main(String[] args) {
LOGGER.error("报错啦");
}
lookback 输出方式
- error 错误型
- debug
- info
- trace
LOGGER.error("报错啦");
日志打印级别
用来控制哪些信息要打印出来
ALL 和 OFF分别是打开、及关闭全部日志信息。
日志级别还有: trace< debug < info < warn < error ; 默认级别是debug
对应其方法
在 level 处设置级别
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE" />
</root>
线程
什么是线程? | 线程就是一个程序内部的一条执行路径 |
---|---|
main方法就是一个执行路径 | |
如果只有一条执行路径叫单线程 | |
为什么不调用 run,而是 strat? | 直接调用run方法,会当成普通方法执行 |
只有调用start方法才是启动一个新的线程执行 |
Thread 类
构造方法 | |
---|---|
构造方法传递,字符串 | 为当前线程指定名称 |
构造方法传递,Runnable | 设置线程 |
创建方式(继承Thread)
优缺点: | |
---|---|
优点: | 编码简单 |
缺点: | 线程类已经继承Thread类,不利于扩展 |
先创建子线程类
需要继承:Thread类
启动start方法后,还是执行run方法
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行输出");
}
}
}
创建这个线程对象
public static void main(String[] args) {
// 创建这个对象
MyThread my = new MyThread();
// 调用这个方法
my.start();
}
创建方式(实现Runnable接口)
优缺点: | |
---|---|
优点: | 线程只是实现接口,可以继续继承和实现,括住性强 |
缺点: | 编程多一层包装,线程有返回结果,无法返回 |
先创建子线程类
需要实现:Runnable接口
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行输出" + i);
}
}
}
创建这个线程对象
public static void main(String[] args) {
// 创建任务对象
MyThread my = new MyThread();
// 创建线程对象。把任务对象传入进去
Thread t = new Thread(my);
// 调用这个方法
t.start();
}
优化:可以用匿名内部类,而且还可以Lambda表达式简化
匿名内部类
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行输出" + i);
}
}
});
简化,并直接调用start,链式编程。
new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行输出" + i);
}
}).start();
创建方式(实现Callable接口)
前两种存在问题 | 他们重写的run方法均不能直接返回结果 |
---|---|
不适合需要返回线程执行结果的业务 | |
Runnable 优点: | 可以继承类和实现接口,扩展性强 |
可以获得线程返回的结果 | |
Runnable 缺点: | 编码复杂一点 |
FutureTask | 是 Runnable 的对象(实现 Runnable) |
---|---|
所以可以交给Thread | |
可以在线程执行结束的时候 | |
通过调用get方法得到线程执行完毕的结果 |
FutureTask的get方法,如果线程没有执行结束,会等待
get方法:得到线程返回结果
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建 Callable 任务对象
MyThread myThread = new MyThread(100);
FutureTask<String> ft = new FutureTask<>(myThread);
Thread thread = new Thread(ft);
thread.start();
String s = ft.get();
System.out.println(s);
}
接口多了一个泛型,指定返回类型
// 定义一个任务类 实现 Callable接口
class MyThread implements Callable<String> {
private int n;
public MyThread(int n) {
this.n = n;
}
int sum = 0;
// 重写 call 方法,不再是run,比run多一个返回值
@Override
public String call() {
for (int i = 1; i < n; i++) {
sum += i;
}
return "子线程执行的结果是 " + sum;
}
}
获取当前线程(静态方法)
那个线程执行它,就会得到那个线程
Thread thread = Thread.currentThread();
获取线程名字(getName)
// 创建线程对象
Thread t = new Thread(new Thread1());
// 启动线程
t.start();
// 打印当前线程的名字
System.out.println(t.getName());
// Thread-1
设置线程名字(getName)
// 创建线程对象
Thread t = new Thread(new Thread1());
// 启动线程
t.start();
// 设置线程名字
t.setName("线程1");
// 打印当前线程的名字
System.out.println(t.getName());
//线程1
等待(sleep)
传递一个毫秒值
Thread.sleep(5000);
线程安全
线程安全:存在线程并发,同时方法资源,存在修改
示例:
public class TestThead {
public static void main(String[] args) {
// 设置取钱对象
Money_to money = new Money_to(10000);
// 创建线程1
new thread(money, "小红").start();
new thread(money, "小明").start();
}
}
class Money_to {
private double money;
public Money_to() {
}
public void getmoney(double money_1) {
// 获取当前取钱对象
String name = Thread.currentThread().getName();
// 判断当前钱够不够
if (this.money >= money_1) {
System.out.println(name + "取钱成功,吐出" + money_1);
this.money -= money_1;
System.out.println(name + "取钱后剩余:" + this.money);
}else{
System.out.println("不够");
}
}
public Money_to(double money) {
this.money = money;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
class thread extends Thread {
private Money_to money;
public thread(Money_to money, String name) {
super(name);
this.money = money;
}
public thread() {
}
@Override
public void run() {
money.getmoney(10000);
}
}
线程同步
如何才能保证线程安全?
对出现问题的核心代码使用synchronized进行加锁
核心思想
加锁,把共享资源上锁,每次只能进一个线程。访问完毕后解锁。
同步代码块(synchronized)
作用: | 把出现线程安全问题的核心代码块上锁 |
---|---|
原理: | 每次只能进入一个线程,完毕后自动解锁 |
锁对象的规范要求 | 使用共享资源作为锁对象 |
如果是实例化的可以直接用this | |
静态方法直接使用 类名.Class |
synchronized(对象 / 类名.Class){
核心代码
}
同步方法
作用:把出现线程安全问题的核心方法给上锁
public synchronized 返回值类型 方法名(参数) {
}
同步方法的底层原理:
- 底层也是隐式锁对象,只是锁的是整个方法,默认是this
- 如果是静态方法,默认直接class . 类名
是同步方法好 | 还是同步方法块好用 |
---|---|
同步代码块的范围更小,同步方法块更大 | |
一般都使用同步方法,因为一目了然,易读 |
Look锁(接口)
建议,带上final ,创建实例化对象,保证是一个锁
final Lock look = new ReentrantLock();
建议在try finally 里,无论如何也会解锁
try {
look.lock();
} finally {
look.unlock();
}
线程池(重点)
有工具类
什么是线程池?
线程池就是一个复用线程的技术
不使用线程池的问题?
如果用户每发一个请求,后台都要创建一个新线程来处理,严重影响系统性能,创建新线程的开销很大
临时线程什么时候创建啊?
新任务提交的时候发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建。
什么时候会开始拒绝任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。
ThreadPoolExecutor参数说明
参数说明: | |
---|---|
参数1:核心线程 | 长久不死亡线程,必须大于0 |
参数2:最大线程 | 最多创建几个临时线程,必须大于等于核心线程数量 |
参数3:存活时间 | 不能小于0 |
参数4:时间单位 | 指定存活时间的单位(秒分时天) |
参数5:任务队列 | 线程都在忙的时候,可以等待多少任务,不能为空 |
参数6:设置工厂 | 指定那个线程工厂创建线程,不能为空 |
参数7:异常处理 | 达到最多线程的时候,怎么报异常,不能为空 |
获得线程池对象,方式一
使用 ExecutorService 的实现类ThreadPoolExecutor自创建一个线程池对象
ThreadPoolExecutor executor = new ThreadPoolExecutor();
需要学习的方法和对象
任务队列(ArrayBlockingQueue)
参数就是,可以等待的任务数量
ArrayBlockingQueue pool= new ArrayBlockingQueue<>(5);
默认线程工厂
Executors.defaultThreadFactory();
异常处理方案
- 默认方案,丢弃任务并抛出异常,是默认方法
ThreadPoolExecutor.AbortPolicy p = new ThreadPoolExecutor.AbortPolicy();
- 丢弃任务,但是不抛出异常,不推荐
ThreadPoolExecutor.DiscardPolicy p = new ThreadPoolExecutor.DiscardPolicy();
- 抛出队列中等待最久的任务,然后把当前任务加入到队列
ThreadPoolExecutor.DiscardOldestPolicy p = new ThreadPoolExecutor.DiscardOldestPolicy();
- 由主线程调用任务的run方法,绕过线程池直接执行
ThreadPoolExecutor.CallerRunsPolicy p = new ThreadPoolExecutor.CallerRunsPolicy();
线程池添加方法(execute)
// 创建线程工厂,3个核心线程,5个最大线程,一共5个线程。临时线程存活 6 秒。
// 任务等待队列 5个,创建默认工厂,创建默认异常
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
// 创建任务对象
MyRunnable m = new MyRunnable();
// 添加一个线程
pool.execute(m);
线程池强制关闭(shutdownNow)
// 创建线程工厂,3个核心线程,5个最大线程,一共5个线程。临时线程存活 6 秒。
// 任务等待队列 5个,创建默认工厂,创建默认异常
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
// 创建任务对象
MyRunnable m = new MyRunnable();
// 添加一个线程
pool.execute(m);
// 强制关闭线程池
pool.shutdownNow();
立即关闭,不管有没有线程在运行,会丢失任务
线程池关闭(shutdown)
// 创建线程工厂,3个核心线程,5个最大线程,一共5个线程。临时线程存活 6 秒。
// 任务等待队列 5个,创建默认工厂,创建默认异常
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
// 创建任务对象
MyRunnable m = new MyRunnable();
// 添加一个线程
pool.execute(m);
// 关闭线程池
pool.shutdown();
等所有的线程都执行完毕,才会关闭线程池
一般不会关闭,因为是长期执行的
线程池的创建
- 创建线程池对象
// 创建线程工厂,3个核心线程,5个最大线程,一共5个线程。临时线程存活 6 秒。
// 任务等待队列 5个,创建默认工厂,创建默认异常
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
- 创建任务对象
// 创建任务对象
MyRunnable m = new MyRunnable();
- 把任务给线程池处理
// 线程池添加任务
pool.execute(m);
pool.execute(m);
pool.execute(m);
//pool-1-thread-1输出了HelloWord0
//pool-1-thread-3输出了HelloWord0
//pool-1-thread-3输出了HelloWord1
//pool-1-thread-2输出了HelloWord0.....
重点: | |
---|---|
上述,最大五个线程,队列5个 | 也就是说只有任务队列超过5个才会创建临时线程 |
线程池执行Callable任务,返回结果
传入一个Callable(submit)
Future<String> s1 = pool.submit(new MyRunnable(100));
传入一个Callable任务,返回一个Future对象
Future的get
System.out.println(s2.get());
获得结果,如果没有跑完,会等待结果
案例:
// 创建线程工厂,3个核心线程,5个最大线程,一共5个线程。临时线程存活 6 秒。
// 任务等待队列 5个,创建默认工厂,创建默认异常
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
// 线程池添加任务
Future<String> s1 = pool.submit(new MyRunnable(100));
// 打印数据
System.out.println(s1.get());
并发与并行
正在运行的程序(软件)就是一个独立的进程,线程是属于进程的,多个线程其实是并发与并行同时进行的。
并行: | 同时有多个线程被CPU执行。同一个时刻同时执行 |
---|---|
并发: | 就是线程轮询执行 |
线程的生命周期
线程的状态:也就是线程从生到死的过程,以及中间经历的各种状态及状态转换。
运行流程 | |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。 |
Runnable(可运行) | 线程已经调用了start()等待CPU调度 |
Blocked(锁阻塞) | 在执行的时候未竞争到锁对象,进入Blocked状态 |
Waiting(无限等待) | 一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。带有超时参数的常用方法有Thread.sleep 、Object.wait |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
六种状态:
- 新建状态( NEW )
- 就绪状态( RUNNABLE )
- 阻塞状态( BLOCKED )
- 等待状态( WAITING )
- 计时等待( TIMED_WAITING )
- 结束状态( TERMINATED )
定时器
创建对象(Timer )
// 创建定时器对象
Timer timer = new Timer();
创建定时器任务(schedule)
参数1:匿名内部类 TimerTask
参数2:第一次执行等待时间
参数3:每隔几秒执行一次
// 调用方法处理定时器任务
// 创建定时器对象
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行一次~~");
}
}, 4000, 2000);
Timer 定时器的特点和缺点 | |
---|---|
Timer 是单线程,处理多个任务按照顺序时 | |
存在延时,会等上一个定时器执行完,造成时间误差 | |
如果其中有定时器异常,直接死掉 |
创建对象(ScheduledExecutorService)
// 创建多线程定时器对象
ScheduledExecutorService s = Executors.newScheduledThreadPool(3);
调用定时器方法(scheduleAtFixedRate)
- newScheduledThreadPool 参数为线程数量
- run 方法参数:
-
- 是首次执行等待时间
-
- 周期执行时间
-
- 单位
-
// 创建多线程定时器对象
ScheduledExecutorService s = Executors.newScheduledThreadPool(3);
// 调用方法
s.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行输出AAA");
}
},0,2, TimeUnit.SECONDS);
好处:基于线程池,某个任务的执行情况,不会影响其他定任务的执行
多线程下的单例模式
锁住的时候,要锁两次
// volatile防止指令重排
private static volatile Singleton_01 singleton_01=null;
public static Singleton_01 getInstance(){
if(singleton_01==null) {
synchronized (Singleton_01.class) {
if (singleton_01 == null) {
singleton_01 = new Singleton_01();
}
}
}
return singleton_01;
}
volatile,防止指令重排
网络编程
IP 和 常识
网络通信的两种形式:Client-Server(CS) 、 Browser/Server(BS)
网络编程的三要素 | |
---|---|
IP地址 | 设备在网络种的地址,唯一标识 |
端口 | 应用程序在设备中的唯一标识 |
协议 | 数据在网络中传输的规则,常见协议 TCP 和 UDP |
IPv6 是 128bit 16字节
IPv6分成8个整数,每个整数用四个十六进制位表示, 数之间用冒号:分开
常用IP命令和形式
IP地址和形式:
公网地址、和私有地址(局域网使用)。
192.168. 开头的就是常见的局域网地址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用。
IP常用命令:
ipconfig:查看本机IP地址
ping IP地址:检查网络是否连通
本机IP: 127.0.0.1或者localhost:称为回送地址也可称本地回环地址,表示本机。
IP地址操作类(InetAddress)
获取本机对象
InetAddress localHost = InetAddress.getLocalHost();
获取本机IP地址
// 获取本机对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);
// Kun/192.168.43.1
获取本机主机名
// 获取本机对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost.getHostName());
// Kun
获取本机地址
// 获取本机对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost.getHostAddress());
// 192.168.43.1
根据域名获取 ip 地址
// 获取本机对象
InetAddress byName = InetAddress.getByName("www.baidu.com");
System.out.println(byName.getHostName());
System.out.println(byName.getHostAddress());
//www.baidu.com
//220.181.38.149
判断是否能和ip地址互通
// 获取公网对象
InetAddress byName = InetAddress.getByName("127.0.0.1");
// 是否能在5秒互通
System.out.println(byName.isReachable(5000));
指定时间内互通的返回 true
端口号
端口号
标识计算机上正在运行的进程,被规定为一个十六位的二进制,0~65535
周知端口 0~1023 | 被预先定义的知名应用占用 |
---|---|
注册端口 1024~49151 | 分配给用户进程,或者是某些应用 |
动态端口 49152~65535 | 不固定分配某种进程,而是动态分配 |
HTTP | 80 |
FTP | 21 |
Tomcat | 8080 |
Mysql | 3306 |
Redis | 6379 |
注意:我们自己开发程序,选择注册端口,一个设备不能出现两个程序端口号一样,否则出错
端口号的作用:唯一标识计算机上运行的程序
IP+端口号=Scoket(网络套接字)
域名=Ip+端口号的一个映射
协议
连接和通信数据的规则称为网络通信协议
- OSI参考模型:世界互联协议标准,全球通信规范,由于此模型过于理想化,未能在因特网上进行广泛推广。
- TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。
传输层的两个常见协议:UDP、TCP
TCP协议
TCP特点:
- 使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。
- 传输前,采用“三次握手”方式建立连接,所以是可靠的 。
- 在连接中可进行大数据量的传输 。
- 连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低。
TCP协议通信场景
对信息安全要求较高的场景,例如:文件下载、金融等数据通信。
三次握手确认连接
- 客户端向服务端发起连接请求,等待服务器确认
- 服务器向客户端返回一个响应,告诉客户端收到了请求
- 客户端向服务端再次发出确认信息,连接建立
四次挥手断开连接
- 客户端向服务端发起取消连接请求
- 服务器向客户端返回一个相应,表示收到取消请求
- 服务器再次向客户端发出确认取消信息
- 客户端再次发送确认信息,连接取消
TCP 实现发送与接收
方法
创建 Socket 通信请求,有服务器的连接
参数1:服务端的 IP 地址
参数2:服务端的端口
// 创建Socket 对象,通信请求,有服务器的连接
Socket socket = new Socket("192.168.1.76",7777);
从Socket通信得到一个字节输出流,负责发送数据
// 从Socket通信得到一个字节输出流,负责发送数据
OutputStream out = socket.getOutputStream();
注册端口
// 注册端口
ServerSocket serverSocket = new ServerSocket(7777);
必须调用 accept 方法,等待接收客户端的 Socket请求,建立 Socket 通信
// 必须调用 accept 方法,等待接收客户端的 Socket请求,建立 Socket 通信
Socket socket = serverSocket.accept();
从 Socket 通信中得到一个字节输入流
// 从 Socket 通信中得到一个字节输入流
InputStream input = socket.getInputStream();
获取发送端的IP地址
// 获取发送端的IP地址
Socket socket = serverSocket.accept();
socket.getRemoteSocketAddress()
创建发送端
public static void main(String[] args) throws IOException {
// 创建Socket 对象,通信请求,有服务器的连接
Socket socket = new Socket("192.168.1.76",7777);
// 从Socket通信得到一个字节输出流,负责发送数据
OutputStream out = socket.getOutputStream();
// 把低级流,转换成高级流
PrintStream ps = new PrintStream(out);
//发送数据
ps.println("hello, 10.13日啦");
// 刷新
ps.flush();
}
创捷接收端
public static void main(String[] args) throws IOException {
// System.out.println(InetAddress.getLocalHost());
// 注册接口
ServerSocket serverSocket = new ServerSocket(7777);
// 必须调用 accept 方法,等待接收客户端的 Socket请求,建立 Socket 通信
Socket socket = serverSocket.accept();
// 从 Socket 通信中得到一个字节输入流
InputStream input = socket.getInputStream();
// 把字节输入流包装成缓冲字符输入流,进行消息的接收
BufferedReader buff = new BufferedReader(new InputStreamReader(input));
// 按照行读取
String msg;
if((msg = buff.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
}
}
即使通信技术
即时通信是什么含义,要实现怎么样的设计?
- 即时通信,是指一个客户端的消息发出去,其他客户端可以接收到
- 即时通信需要进行端口转发的设计思想。
- 服务端需要把在线的Socket管道存储起来一旦收到一个消息要推送给其他管道
1、之前的客户端都是什么样的
其实就是CS架构,客户端实需要我们自己开发实现的。
2、BS结构是什么样的,需要开发客户端吗?
浏览器访问服务端,不需要开发客户端。
发送端
public static void main(String[] args) throws IOException {
// 创建客户端
Socket socket = new Socket("192.168.43.1", 7771);
// 创建独立线程专门负责接收信息(服务器随时可能发信息过来)
new ClientReade(socket).start();
// 创建打印流
PrintStream ps = new PrintStream(socket.getOutputStream());
Scanner sc = new Scanner(System.in);
while (true){
String s = sc.nextLine();
if(s.equals("exit")){
System.exit(0);
}
ps.println(s);
}
}
创建接收端
// 收集所有的管道
public static ArrayList<Socket> arrayList = new ArrayList<>();
public static void main(String[] args) throws IOException {
// 创建服务端
ServerSocket server = new ServerSocket(7771);
while (true) {
// 调用连接方法
Socket accept = server.accept();
// 上线提醒应该在这里
System.out.println(accept.getRemoteSocketAddress() + "上线啦!!!");
// 把管道添加到集合
arrayList.add(accept);
// 负责创建线程发送信息,并启动线程
new ClientReade(accept).start();
}
}
创建线程
public class ClientReade extends Thread{
private final Socket accept;
public ClientReade(Socket socket) {
this.accept = socket;
}
@Override
public void run() {
// 把低级流转换高级流
try {
BufferedReader buff = new BufferedReader(new InputStreamReader(accept.getInputStream()));
String msg;
while ((msg = buff.readLine()) != null) {
System.out.println("收到了消息:" + msg);
ClientReade.sendMsg(msg);
}
} catch (IOException e) {
System.out.println("下线咯~~");
// 这个时候应该移出去
Server.arrayList.remove(accept);
}
}
// 打印这个信息
public static void sendMsg(String msg) throws IOException {
for (Socket socket : Server.arrayList) {
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println(msg);
ps.flush();
}
}
}
UDP协议
UDP特点:
- UDP是一种无连接、不可靠传输的协议。
- 将数据源IP、目的地IP和端口封装成数据包,不需要建立连接 。
- 每个数据包的大小限制在64KB内。
- 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的。
- 可以广播发送 ,发送数据结束时无需释放资源,开销小,速度快。
UDP协议通信场景
语音通话,视频会话等。
UDP 实现数据的发送与接收
实现步骤 | |
---|---|
创建服务器 | |
创建数据包 | |
发送数据 |
方法
// 创建客户端
DatagramSocket data = new DatagramSocket();
// 创建数据包
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.1.76"), 8888);
// 发送数据
data.send(packet);
// 等待接收数据
data.receive(packet);
// 获取接收数据的长度
int len = packet.getLength();
// 获取发送端的ip
System.out.println(packet.getSocketAddress());
// 192.168.1.2:60320
// 获取发送端的端口号
System.out.println(packet.getPort());
//60320
创建发送端客户端DatagramSocket
自带端口号,无需设置
// 创建 服务器对象(自带默认端口号)
DatagramSocket data = new DatagramSocket();
创建数据,和配置发送的数据DatagramPacket
- 参数一:传递的数据
- 参数二:传递数据的长度
- 参数三:传递到那个主机
- 参数四:接收端(也是DatagramSocket创建的对象)的端口号
// 创建 服务器对象(自带默认端口号)
DatagramSocket data = new DatagramSocket();
// 创建数据,数据是字节
byte[] bytes = "我是一个快乐的韭菜,你愿意吃吗?".getBytes();
// 参数一:传递的数据,参数二:传递数据的长度,参数三:传递到那个主机,参数四:接收端(也是DatagramSocket创建的对象)的端口号
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 8888);
发送数据
// 发送数据
data.send(packet);
// 关闭资源
data.close();
创建接收端客户端DatagramSocket
创建服务端对象,这里对象要填写端口号
// 创建接收端的对象
DatagramSocket data = new DatagramSocket(8888);
为什么要1024 * 64 因为防止丢包,直接最大就可以
创建接收数据的包,和配置接收的数据 DatagramPacket
// 创建接收端的对象
DatagramSocket data = new DatagramSocket();
// 创建一个接收包,接收传过来的数据
byte[] bytes = new byte[1024 * 64];
// 配置接收的数据,因为只接收,传递两个参数就可以了
DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
接收数据,关闭资源
一定要先接收数据,再获取长度,要不然会出错的
// 等待接收数据
data.receive(packet);
// 获取接收数据的长度
int len = packet.getLength();
// 转换为字符串并打印
String s = new String(bytes, 0, len);
System.out.println(s);
// 获取发送端的ip
System.out.println(packet.getSocketAddress());
// 192.168.1.2:60320
// 获取发送端的端口号
System.out.println(packet.getPort());
//60320
// 关闭资源
data.close();
广播,组播
- 广播:当前主机与所在网络中的所有主机通信。
- 组播:当前主机与选定的一组主机的通信。
使用广播地址:255.255.255.255
具体操作:
- 发送端发送的数据包的目的地写的是广播地址、且指定端口。 (255.255.255.255 , 9999)
- 本机所在网段的其他主机的程序只要注册对应端口就可以收到消息了。(9999)
使用组播地址:224.0.0.0 ~ 239.255.255.255
具体操作:
- 发送端的数据包的目的地是组播IP (例如:224.0.1.1, 端口:9999)
- 接收端必须绑定该组播IP(224.0.1.1),端口还要注册发送端的目的端口9999 ,这样即可接收该组播消息。
- DatagramSocket的子类MulticastSocket可以在接收端绑定组播IP。
TCP 协议 和 UDP 的区别
TCP | 需要先建立TCP连接,形成通道 |
---|---|
传输前,采用三次握手,点对点通信,可靠 | |
TCP两个应用进程:客户端,服务端 | |
可进行大数据传输 | |
传输完毕,需释放已建立的链接,效率低 | |
UDP | 将数据、源、目的封装成数据包,不需要建立连接 |
每个数据报的大小限制在64K内 | |
发送不管对方是否准备好,接收方收到也不确认,故是不可靠的 | |
可以广播发送 | |
发送数据结束时无需释放资源,开销小,速度快 |
正则表达式
代表汉字
\\u4e00-\\u9fa5
规则 | |
---|---|
\ | 转义符 |
^ | 以…开头 |
$ | 以…结尾 |
* | 有0个或多个 |
+ | 有1个或多个 |
? | 有0个或1个 |
() | 分组 |
[0-9] | 范围数字0到9 |
[a-z] | 范围a-z |
[A-Z] | 范围A-Z |
[^0-9] | 取反,非0-9 |
[abc] | 匹配a,b,c |
{n} | 匹配n次 |
{n,} | 匹配n次及n次以上 |
{n,m} | 匹配n到m次 |
\d | 匹配一个数字字符,等价于[0-9] |
\D | 匹配一个非数字字符,等价于[^0-9] |
\s | 匹配空白字符 |
\S | 匹配任意非空白字符 |
\w | 匹配包括下划线在内的任何单词字符 |
等价于[a-zA-Z0-9_] | |
\W | 匹配任何非单词字符 |
(?i)n | n不区分大小写 |
ab+? | 表示只获得ab,不贪婪爬取 |
ab+ | 表示贪婪爬取 |
略难 | ((.\\2*).+\\1) |
分组 | 每组的内容可以反复使用 |
正则内部 | \\组号 |
正则外部 | $组号 |
?: ?= ?! | 表示不占用组号 |
(? : ) 这个括号没有组号 |
利用正则去重
String content = "我我我....要要要...学学学...编编编...程程程";
// 首先去除点符号
content = content.replaceAll("[^\\u4e00-\\u9fa5]", "");
// $1 表示括号1,去重
content = content.replaceAll("(.)(\\1+)", "$1");
System.out.println(content);
//我要学编程
正则对象 Pattern
获取正则对象和文本匹配器对象
// 正则对象,表示搜索java后面的数字出现 0~2次
Pattern c = Pattern.compile("java\\d{0,2}");
// 表示在s变量的字符串中搜索以上文本
Matcher m = c.matcher(s);
寻找(find)
// 正则对象,表示搜索java后面的数字出现 0~2次
Pattern c = Pattern.compile("java\\d{0,2}");
// 表示在s变量的字符串中搜索以上文本
Matcher m = c.matcher(s);
// 找到返回 true
System.out.println(m.find());
文本对象只能往前走,再次调用表示第二个符合条件的字符
常规使用
String s = "java18斤斤计较经济界java17哦哦哦哦哦";
Pattern c = Pattern.compile("java\\d{0,2}");
Matcher m = c.matcher(s);
while (m.find()){
String group = m.group();
System.out.println(group);
}
替换所有(replaceAll)
参数1:正则表达式
参数2:替换的内容
String s = "小诗诗dgefqwfg小丹丹wfwg123112312小惠惠";
// 把所有的字母数字不包括下划线,替换成vs,至少替换一次
String vs = s.replaceAll("[\\w&&[^_]]+", "vs");
System.out.println(vs);
//小诗诗vs小丹丹vs小惠惠
切割返回数组(split)
String s = "小诗诗dgefqwfg小丹丹wfwg123112312小惠惠";
// 根据正则表达式切割
String[] split = s.split("[\\w&&[^_]]+");
for (String s1 : split) {
System.out.println(s1);
//小诗诗 小丹丹 小惠惠
}
以…开头(LookingAt)
单元测试(JUnit)
测试分类 | |
---|---|
黑盒测试 | 不需要写代码,输入值,看结果是否符合预期 |
白盒测试 | 需要写代码,关注程序具体的执行流程 |
就是针对方法的测试,JUnit是java语言实现的单元测试框架,开源的
Junit单元测试的优点是什么? | |
---|---|
JUnit可以选择执行哪些测试方法 | |
可以一键执行全部测试方法的测试。 | |
JUnit可以生测试报告 | |
测试良好则是绿色,测试失败,则是红色 | |
某个方法测试失败了,不会影响其他测试方法 |
JUnit使用
IDEA会自动下载
编写测试公共方法,无返回值,无参数,非静态,@Test注解
预期测试
- 参数1:是返回错误内容
- 参数2:预期正确结果
- 参数3:根据 s 的内容判断
Assert.assertEquals("您登录业有问题","登录成功",s);
完整代码
@Test
public void testName(){
p1 p1 = new p1();
String s = p1.logName("admin","123456");
Assert.assertEquals("您登录业有问题","登录成功",s);
}
红色失败,黄色结果有误,绿色通过
注解(Junit 4.xxxx版本)
注解 | 说明 |
---|---|
@Test | 测试方法 |
@Before | 修饰实例方法,该方法会在每一个测试方法执行之前执行一次。 |
@After | 修饰实例方法,该方法会在每一个测试方法执行之后执行一次。 |
@BeforeClass | 用来静态修饰方法,该方法会在所有测试方法之前只执行一次。 |
@AfterClass | 用来静态修饰方法,该方法会在所有测试方法之后只执行一次。 |
开始执行的方法:初始化资源。
执行完之后的方法:释放资源。
注解(Junit 5.xxxx版本)
注解 | 说明 |
---|---|
@Test | 测试方法 |
@BeforeEach | 修饰实例方法,该方法会在每一个测试方法执行之前执行一次。 |
@AfterEach | 修饰实例方法,该方法会在每一个测试方法执行之后执行一次。 |
@BeforeAll | 用来静态修饰方法,该方法会在所有测试方法之前只执行一次。 |
@AfterAll | 用来静态修饰方法,该方法会在所有测试方法之后只执行一次。 |
开始执行的方法:初始化资源。
执行完之后的方法:释放资源。
反射
- 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
- 反射:将类的各个组成部分封装为其他对象,这就是反射机制
- 好处:
- 可以在程序运行过程中,操作这些对象。
- 可以解耦,提高程序的可扩展性。
反射,就是对于任何一个类,在运行的时候可以直接拿到这个类的全部成分。
这种运行时动态获取类 的信息以及动态调用类中的成分,这种能力称之为反射机制。
反射的关键
- 反射第一步:先得到编译后的Class类对象,就得到了类的所有成分
得到Class对象(forName)
// 使用 forName 获取 ii 下的 Student
Class c = Class.forName("ii.Student");
// 打印这个对象
System.out.println(c);
// class ii.Student
得到Class对象(对象.class)
// 使用 对象.class 获取
Class c1 = Student.class;
// 打印这个对象
System.out.println(c1);
// class ii.Student
得到Class对象(对象.class)
// 先创建对象
Student student = new Student();
// getClass获取类
Class c2 = student.getClass();
// 打印这个对象
System.out.println(c2);
// class ii.Student
获取构造方法
方法
getSimpleName | 获取类名 |
取资源的安全方式(getResourceAsStream)
不以 / 开头,表示在当前项目的包下取
InputStream res = aaa.class.getResourceAsStream("Contacts.xml");
以 / 开头表示在项目的src中查找
InputStream res = aaa.class.getResourceAsStream("/src/ii/Contacts.xml");
获取构造方法(getConstructors)
返回的是一个数组,不包含私有化构造方法
Constructor[ ]
Constructor[ ]方法 | |
---|---|
getName | 获取构造方法名字 |
getParameterCount | 获取构造方法参数个数 |
// 1. 获取 类对象
Class stu = Student.class;
// 2. 获取当前类的所有构造方法
Constructor[] con = stu.getConstructors();
for (Constructor constructor : con) {
System.out.println(constructor.getName() + "-----" + constructor.getParameterCount());
}
获取带私有化构造方法(getConstructors)
返回的是一个数组,包含私有化构造方法
Class stu = Student.class;
Constructor[] con = stu.getDeclaredConstructors();
根据构造方法参数获取构造方法(不包含私有化)
无法获取私有化构造方法,获取不到抛异常
Class stu = Student.class;
Constructor con = stu.getConstructor();
根据构造方法参数获取构造方法(包含私有化)
获取不到抛异常
Class stu = Student.class;
Constructor con = stu.getDeclaredConstructor();
根据构造方法参数获取构造方法(包含私有化,带参)
// 1. 获取 类对象
Class stu = Student.class;
// 2. 根据参数类型获取构造方法
Constructor con = stu.getDeclaredConstructor(String.class, int.class);
把类解析成对象(getDeclaredConstructor)
构造方法 . setAccessible | 设置为公开 |
---|---|
newInstance | 解析 |
newInstance(参数…) | 解析 |
// 1. 获取 类对象
Class<Student> stu = Student.class;
// 2. 获取构造方法
Constructor<Student> con1 = stu.getDeclaredConstructor();
// 3. 把私有化的成员转换成 public
con1.setAccessible(true);
// 4。 把类解析成对象
Student s = con1.newInstance();
// 打印
System.out.println(s);
获取成员变量
获取所有的成员变量(getDeclaredFields)
Field方法 | |
---|---|
getName | 获取名字 |
getType | 获取类型 |
// 1. 获取 类对象
Class<Student> stu = Student.class;
// 2. 获取当前类的所有成员变量
Field[] fields = stu.getDeclaredFields();
// 遍历
for (Field field : fields) {
System.out.println(field.getName() + "---" + field.getType());
}
//name---class java.lang.String
//age---int
//schoolName---class java.lang.String
//COUNTTRY---class java.lang.String
成员变量赋值 / 取值操作(getDeclaredField)
方法 | |
---|---|
set | 赋值 |
get | 取值 |
// 1. 获取 类对象
Class<Student> stu = Student.class;
// 2. 获取当前类的成员变量
Field sdf = stu.getDeclaredField("name");
// 3. 将类公开
sdf.setAccessible(true);
// 4. 创建对象 赋值
Student s1 = new Student();
// 5. 暴力赋值
sdf.set(s1,"张三");
System.out.println(s1);
// 取值
String o = (String)sdf.get(s1);
System.out.println(o);
获取方法对象(getDeclaredMethods)
方法名称 | |
---|---|
getName | 获取方法名称 |
getReturnType | 获取返回值类型 |
// 1. 获取 类对象
Class<Dog> dog = Dog.class;
// 2. 获取当前类所有方法
Method[] d = dog.getDeclaredMethods();
// 遍历
for (Method method : d) {
System.out.println(method.getName() + " 返回值类型:" + method.getReturnType() + " 参数名称:" + method.getParameterCount());
}
执行方法(invoke)
- invoke
- 参数1:是对象
- 参数2:是改方法传递的参数
// 1. 获取 类对象
Class<Dog> dog = Dog.class;
// 2. 获取当前类所有方法
Method m = dog.getDeclaredMethod("eat");
// 3. 暴力改 public
m.setAccessible(true);
Dog d = new Dog();
// 5. 如果 result 返回null 表示没有返回结果
Object result = m.invoke(d);
System.out.println(result);
反射突破泛型的控制
思维,因为泛型是在编译时的错误,而编译好的class文件里已经不存在泛型,而反射是在运行时控制,所以可以突破
自定义注解
public @interface 注解名称{
public 属性类型 属性名() default 默认值;
}
注解的定义格式
@interface 注解名 {}
注解的成员格式
类型 变量名();
package ii;
@qqq(name = "《精通JAVASE》", authors = {"黑马", "diei"}, price = 95)
public class TestP1 {
public static void save(Object obj) {
}
}
@interface qqq {
String name();
String[] authors();
double price();
}
java支持的格式,基本都可以注解
注解的特殊写法
注解只有一个属性时且是value,使用的时候可以直接写值
其他属性有默认值时也可以
@qqq("《精通JAVASE》")
public class TestP1 {
public static void save(Object obj) {
}
}
@interface qqq {
String value();
}
文档注解
注解作用:编写文档、代码分析、编译检查
文档注解 | |
---|---|
@since | 在 jdk 几开始的 |
@author | 作者 |
@version | 版本 |
@param | 参数 |
@return | 返回 |
JDK 预定义注解
预定义注解 | |
---|---|
@Override | 注解标注的方法是否是继承自父类(接口)的 |
@Deprecated | 注解标注的内容,表示已过时 |
@SuppressWarnings | 压制警告 |
idea有很多警告提示等操作,标记该注解,(“all”),不再提示
一般传递参数all @SuppressWarnings("all")
注解的本质
java 编译
javac myInc.java
java 反编译
javap myInc.class
public interface 包名.类名 extends java.lang.annotation.Annotation {
}
本质:注解本质上就是一个接口,该接口默认继承Annotation接口
注解的特点
注解中的抽象方法就是注解的属性
- 属性的返回值类型:
- 基本数据类型
- String
- 枚举
- 注解
- 以上类型的数组
- 使用的时候必须赋值,有默认值除外
枚举定义
public enum Person {
p1("p1"), p2("p222222"), p3("p1"), p4("p1");
Person(String name) {
this.name = name;
}
public String name;
枚举测试
public static void main(String[] args) {
Person[] values = Person.values();
for (Person value : values) {
// 测试
System.out.println(value.name);
}
}
枚举在注解中赋值
per 是在注解中的属性名
@myInc(per = Person.p1)
注解在注解中赋值
// ms 注解
@myInc(per = Person.p1, ms = @dada)
数组在注解中赋值
@myInc(per = Person.p1, ms = @dada, arr = {4,1,3,5,6})
生成帮助文档
javadoc java文件路径
元注解(@Target)
就是来给注解进行注解的
public class TestP1 {
@qqq
public static void save(Object obj) {
}
}
@Target(ElementType.METHOD)
@interface qqq {
}
@Target 参数介绍
参数介绍 | |
---|---|
ElementType.METHOD | 修饰方法 |
ElementType.FIELD | 修饰变量 |
ElementType.FIELD | 修饰变量 |
ElementType.TYPE | 修饰类、接口 |
ElementType.PARAMETER | 修饰方法参数 |
ElementType.CONSTRUCTOR | 修饰构造器 |
ElementType.LOCAL_VARIABLE | 修饰局部变量 |
@Retention 注解声明周期
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface qqq {
}
生命周期 | |
---|---|
RetentionPolicy. RUNTIME | |
注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)虚拟机JVM读取的到 | |
RetentionPolicy. CLASS | |
注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值.JVM虚拟机读取不到 | |
RetentionPolicy. SOURCE | |
注解只作用在源码阶段,生成的字节码文件中不存在 |
@Documented 描述注解是否被抽取到api文档中
@Inherited 描述注解是否被子类继承
如果B继承了A,A上面有注解,那么B也继承过来了
注解解析
注解顶级接口 Annotation
定义了接口解析方法 AnnotatedElement
获取所有的注解
获取所有的注解,返回数组
// 先得到类对象
Class<BookStore> c = BookStore.class;
// 获取对象上所有的注解
java.lang.annotation.Annotation[] z = c.getDeclaredAnnotations();
for (java.lang.annotation.Annotation annotation : z) {
System.out.println(annotation);
}
根据注解类型返回注解对象
根据注解类型返回注解对象
// 先得到类对象
Class<BookStore> c = BookStore.class;
// 根据注解类型返回注解对象
qqq def = c.getDeclaredAnnotation(qqq.class);
System.out.println(def);
判断当前对象是否使用了指定的注解
判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false
// 先得到类对象
Class<BookStore> c = BookStore.class;
if (c.isAnnotationPresent(qqq.class)) {
// 直接获取注解对象
qqq de = c.getDeclaredAnnotation(qqq.class);
}
案例
public class Annotation {
@Test
public void parseClass() {
// 先得到类对象
Class<BookStore> c = BookStore.class;
// 判断这个类上面是否存在这个注解
if (c.isAnnotationPresent(qqq.class)) {
// 直接获取注解对象
qqq de = c.getDeclaredAnnotation(qqq.class);
System.out.println(de.value());
System.out.println(de.price());
System.out.println(Arrays.toString(de.author()));
}
}
}
@qqq(value = "情深深雨蒙蒙", price = 99.0, author = "琼瑶")
class BookStore {
@qqq(value = "情深深雨蒙蒙", price = 99.0, author = "琼瑶")
public static void save(Object obj) {
}
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface qqq {
String value();
double price() default 100;
String[] author();
}
获得注解方法
public static void main(String[] args) {
// 获取类对象
Class<test02> aClass = test02.class;
// 获取注解,单获取,获取注解对象
// 在内存中生成了该注解的接口的子类实现对象
// public class test02 implements myInc{
// public String per(){
// return Person.p1;
// }
// public String ms(){
// return @dada;
// }
// }
myInc inc = aClass.getAnnotation(myInc.class);
// 调用注解对象中定义的抽象方法
Person per = inc.per();
System.out.println(inc);
}
动态代理
介绍
一个方法不想实现其他的事情,需要找个东西代替自己完成,动态代理就是对业务功能(方法)进行代理的。
关键步骤 | |
---|---|
必须有接口 | |
创建实现类对象,为业务对象,为业务代理做代理对象 |
newProxyInstance 分析
- 参数1:test.getClass().getClassLoader() 由它获得类class文件,再获得类加载器,来实现代理
- 参数2: test.getClass().getInterfaces() 获取代理全部接口,为全部接口进行代理
- 参数3:new InvocationHandler() 代理的核心处理逻辑
Proxy.newProxyInstance();
实战案例
test 功能方法
package ii;
public class test implements UserService {
@Override
public String login(String loginName, String password) {
try {
Thread.sleep(1000);
if ("admin".equals(loginName) && "1234".equals(password)) {
return "登录成功";
}
return "登录密码可能有毛病";
} catch (Exception e) {
e.printStackTrace();
return "error";
}
}
@Override
public void selectUsers() {
System.out.println("查询了100个数据");
try {
Thread.sleep(2000);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean deleteUsers() {
try {
System.out.println("删除了100个数据");
Thread.sleep(500);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
UserService 接口
package ii;
public interface UserService {
String login(String loginName, String password);
void selectUsers();
boolean deleteUsers();
}
login 测试方法
package ii;
public class login {
public static void main(String[] args) {
// 把业务对象直接做成一个代理对象返回,代理对象的类型也是,本类型
UserService test = ProxyUtil.getProxy(new test());
System.out.println(test.login("admin", "1234"));
System.out.println(test.deleteUsers());
test.selectUsers();
}
}
ProxyUtil 业务对象
package ii;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtil {
/*
* 参数一:类加载器,负责加载代理类到内存中使用,
* 参数二:获取被代理对象实现的全部接口。代理要为全部接口的全部方法进行代理
* 参数三:代理的核心处理逻辑
* */
public static UserService getProxy(UserService test) {
return (UserService) Proxy.newProxyInstance(
test.getClass().getClassLoader(),
test.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
* 参数1:代理对象本身,一般不管
* 参数2:正在被代理的方法
* 参数3:被代理的方法应该传入的参数
* */
long start = System.currentTimeMillis();
Object invoke = method.invoke(test, args);
long end = System.currentTimeMillis();
System.out.println("deleteUsers耗时:" + (end - start) / 1000.0 + "s");
return invoke;
}
});
}
}
优点
动态代理的优点 | |
---|---|
非常的灵活,支持任意接口类型的实现类对象做代理,也可以直接为接口本身做代理。 | |
可以为被代理对象的所有方法做代理。 | |
可以在不改变方法源码的情况下,实现对方法功能的增强。 | |
不仅简化了编程工作、提高了软件系统的可扩展性,同时也提高了开发效率。 |
XML解析
XML的作用是什么?
- 用于进行存储数据和传输数据
- 作为软件的配置文件
<?xml version="1.0" encoding="UTF-8" SYSTEM = "约束" ?>
<--
version:XML默认的版本号码、该属性是必须存在的
encoding:本XML文件的编码
-->
XML文件的后缀名为:xml
文档声明必须是第一行
特殊字符
特殊字符 | |
---|---|
< | < 小于 |
> | > 大于 |
& | & 和号 |
' | ’ 单引号 |
" | " 引号 |
可以自定义内容
XML文件中可以存在CDATA区: <![CDATA[ …内容… ]]>
文档约束
强制约束程序员必须按照文档约束的规定来编写xml文件。
DTD
编写DTD约束文档,后缀必须是.dtd
可以约束XML文件的编写。
不能约束具体的数据类型。
文档约束-schema
可以约束XML文件的标签内容格式,以及具体的数据类型。
本身也是xml文件,格式更严谨。
DOM4J 解析
Dom解析的文档对象模型是怎么样的?都属于Node对象
- Document对象:整个xml文档
- Element对象:标签
- Attribute对象:属性
- Text对象:文本内容
创建dom4j解析器对象(SAXReader)
代表dom4j框架
// 创建一个dom4j的解析器对象
SAXReader saxReader = new SAXReader();
把xml文件加载到内存中(read)
// 创建一个dom4j的解析器对象
SAXReader saxReader = new SAXReader();
// 把xml文件加载到内存中成为一个Document对象
Document document = saxReader.read(res);
获取根元素对象(getRootElement)
// 创建一个dom4j的解析器对象
SAXReader saxReader = new SAXReader();
// 把xml文件加载到内存中成为一个Document对象
Document document = saxReader.read(res);
// 获取根元素对象
Element root = document.getRootElement();
获取根元素下的子元素(elements)
获取根元素下的所有子元素
// 创建一个dom4j的解析器对象
SAXReader saxReader = new SAXReader();
// 把xml文件加载到内存中成为一个Document对象
Document document = saxReader.read(res);
// 获取根元素对象
Element root = document.getRootElement();
// 获取根元素下的子元素
List<Element> elements = root.elements();
for (Element element : elements) {
System.out.println(element.getName());
}
获取根元素下的指定元素
// 创建一个dom4j的解析器对象
SAXReader saxReader = new SAXReader();
// 把xml文件加载到内存中成为一个Document对象
Document document = saxReader.read(res);
// 获取根元素对象
Element root = document.getRootElement();
// 获取根元素下的指定元素
List<Element> elements = root.elements("contact");
for (Element element : elements) {
System.out.println(element.getName());
}
获取指定的某一个元素(element)
// 创建一个dom4j的解析器对象
SAXReader saxReader = new SAXReader();
// 利用 getResourceAsStream 安全获取文件
InputStream res = aaa.class.getResourceAsStream("/ii/Contacts.xml");
// 把xml文件加载到内存中成为一个Document对象
Document document = saxReader.read(res);
// 获取根元素对象
Element root = document.getRootElement();
// 获取指定的某一个元素
Element user = root.element("user");
System.out.println(user.getName()); // user
获取子元素文本(elementText)
// 创建一个dom4j的解析器对象
SAXReader saxReader = new SAXReader();
// 利用 getResourceAsStream 安全获取文件
InputStream res = aaa.class.getResourceAsStream("/ii/Contacts.xml");
// 把xml文件加载到内存中成为一个Document对象
Document document = saxReader.read(res);
// 获取根元素对象
Element root = document.getRootElement();
// 获取指定的某一个元素
Element contact = root.element("contact");
// 获取第一个 contact 下的 name 标签的文本
String name = contact.elementText("name");
System.out.println(name); // 潘金莲
获取子元素文本去除左右侧空格(elementTextTrim)
// 获取第一个 contact 下的 name 标签的文本
String name = contact.elementTextTrim("name");
System.out.println(name);
根据元素获取元素属性对象(attribute)
// 创建一个dom4j的解析器对象
SAXReader saxReader = new SAXReader();
// 利用 getResourceAsStream 安全获取文件
InputStream res = aaa.class.getResourceAsStream("/ii/Contacts.xml");
// 把xml文件加载到内存中成为一个Document对象
Document document = saxReader.read(res);
// 获取根元素对象
Element root = document.getRootElement();
// 获取指定的某一个元素
Element contact = root.element("contact");
// 获取id属性对象
Attribute idAttr = contact.attribute("id");
// 获取元素属性名和属性值
System.out.println(idAttr.getName() + "---" + idAttr.getValue());
直接提取属性值(attributeValue)
// 获取id属性值
String id = contact.attributeValue("id");
System.out.println(id);
检索技术:Xpath
Dom4j需要进行文件的全部解析,然后再寻找数据。
Xpath技术更加适合做信息检索。
XPath使用路径表达式来定位XML文档中的元素节点或属性节点。
绝对路径获取所有的name对象(selectNodes)
// 解析器对象
SAXReader saxReader = new SAXReader();
// 安全性获取文件,并且加载到内存
Document document = saxReader.read(aaa.class.getResourceAsStream("Contacts2.xml"));
方法 | |
---|---|
selectNodes | 获取所有指定路径的对象 |
// 获取所有的名称
List<Node> nameNodes = document.selectNodes("contactList/contact/name");
for (Node nameNode : nameNodes) {
// 因为知道它是什么类型,可以直接强转
Element element = (Element) nameNode;
// 打印名字
System.out.println(element.getTextTrim());
}
检索语法说明
检索语法 | |
---|---|
//contact | 找contact元素,无论元素在哪里 |
//contact/name | 找contact,无论在哪一级,但name一定是contact的子节点 |
//contact//name | contact无论在哪,name只要是contact的子孙元素都可以找到 |
属性语法 | |
---|---|
//@id | 找出所有属性是id的 |
//name[@id] | 找出所有name标签属性是id的 |
//name[@id=888] | 找出所有name标签属性是id属性值是888的 |
设计模式
是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案
单例模式
介绍:希望创建多个对象都是一个对象
- 私有化构造方法
- 创建一个静态方法,让外界调用
- 创建一个私有化的变量存取唯一的对象
- 饿汉模式:类加载的时候创建一个
- 懒汉模式:第一次调用获取对象方法的时候
工厂模式
(培训教的)定义一个用于创建产品的接口,由子类决定生产什么产品。
对象通过工厂的方法返回,工厂的方法可以为该对象进行加工和数据注入
可以实现类于类之间的解耦操作(核心)
装饰模式
装饰模式指的是在不改变原类的基础上, 动态地扩展一个类的功能。
- 定义父类。
- 定义原始类,继承父类,定义功能。
- 定义装饰类,继承父类,包装原始类,增强功能!!
案例
抽象类,原始类
/* 共同父类 */
public abstract class inputstream {
public abstract int read();
public abstract int read(byte[] buffer);
}
测试类
/*
* 定义父类 inputstream
* 定义功能 fileinputstream 继承父类
* 定义装饰实现类 bufferedinputstream 包装原始类,定义功能,增强功能
* */
// 测试类
public static void main(String[] args) {
buffedinputstream buff = new buffedinputstream(new fileinputstream());
buff.read();
//提供了8KB的缓冲区
//低性能读取了一个字节a
byte[] arr = {1,2,3};
buff.read(arr);
//提供了8KB的缓冲区
//低性能读取了一个字节数组[97, 98, 99]
}
功能类
public class fileinputstream extends inputstream{
/*
* 原始类,
* */
@Override
public int read() {
System.out.println("低性能读取了一个字节a");
return 97;
}
@Override
public int read(byte[] buffer) {
buffer[0] = 97;
buffer[1] = 98;
buffer[2] = 99;
System.out.println("低性能读取了一个字节数组" + Arrays.toString(buffer));
return 3;
}
}
增强类
public class buffedinputstream extends inputstream {
/*
* 扩展原始类的功能
*
* */
private final inputstream is;
public buffedinputstream(inputstream is) {
this.is = is;
}
@Override
public int read() {
System.out.println("提供了8KB的缓冲区");
return is.read();
}
@Override
public int read(byte[] buffer) {
System.out.println("提供了8KB的缓冲区");
return is.read(buffer);
}
}
方法引用
方法引用的特点
什么是方法引用:
- 方法:就是以前学习的方法
- 引用:把已经有的方法拿过来用,当做函数式接口中抽象方法的方法体
使用条件:
- 引用处必须是函数式接口
- 被引用的方法必须存在
- 被引用的方法的形参和返回值必须跟抽象方法一致
- 被引用方法必须满足当前功能
如果 forEach 参数和方法里的参数名称一样,就可以使用引用
lis.forEach(s -> System.out.println(s));
使用引用
lis.forEach(System.out::println);
方法引用的分类
静态引用(Integer::parseInt)
类名 : : 静态方法
案例
ArrayList<String> inter = new ArrayList<>();
Collections.addAll(inter, "1", "2", "3", "4", "5", "6");
/*方法引用转换数字*/
List<Integer> collect = inter.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
引用成员方法
格式:对象 : : 成员方法
- 其他类:其他类对象 : : 方法名
- 本类:this : : 方法名 – 引用处不能是静态
- 父类:super : : 方法名 – 引用处不能是静态
案例 其他类
public class test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
// 添加全部参数
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "周强", "张三丰");
// 使用方法引用,找出 以张开头的并且名字是三个
list.stream().filter(new test()::dd)
.forEach(System.out::println);
// 张无忌
// 张三丰
}
// 引用这个
private boolean dd(String s) {
return s.startsWith("张") && s.length() == 3;
}
}
本类 this 和 父类 super,引用处非静态
super :: 方法
引用构造方法
格式:类名 : : new
JavaBean类
// 因为已经在本类中了,而且构造方法一调用,已经创建了一个对象了
public Student(String s) {
String[] split = s.split(",");
this.name = split[0];
this.age = Integer.parseInt(split[1]);
}
主方法
public static void main(String[] args) {
// 创建集合
ArrayList<String> list = new ArrayList<>();
// 添加元素
Collections.addAll(list, "周芷若,14", "张无忌,15", "赵敏,13", "张翠山,49", "张强,29", "张三丰,100", "张良,35");
// 使用方法引用,快速加工数据
List<Student> collect = list.stream().map(Student::new).collect(Collectors.toList());
for (Student student : collect) {
System.out.println(student);
}
//Student{name = 周芷若, age = 14}
//Student{name = 张无忌, age = 15}
//Student{name = 赵敏, age = 13}
//Student{name = 张翠山, age = 49}
//Student{name = 张强, age = 29}
//Student{name = 张三丰, age = 100}
//Student{name = 张良, age = 35}
}
使用类名引用成员方法
格式:类名 : : 成员方法
条件:
- 抽象方法的形参第一个参数决定了可以引用哪些方法。(指类型是否一致)
- 第二个参数到最后一个参数,必须和引用方法保持一致
// 创建集合
ArrayList<String> list = new ArrayList<>();
// 添加元素
Collections.addAll(list, "aaa", "bbb", "ccc");
list.stream().map(new Function<String, String>() {
@Override
// 抽象方法的形参第一个参数决定了可以引用哪些方法。图里是String 可以引用String里的方法。
// 如果第二个参数到最后一个参数,必须和引用方法保持一致 toUpperCase和抽象方法里第二个参数到最后一个参数一致,所以可以引用
public String apply(String s) {
return s.toUpperCase();
}
}).forEach(System.out::println);
方法引用实现
// 创建集合
ArrayList<String> list = new ArrayList<>();
// 添加元素
Collections.addAll(list, "aaa", "bbb", "ccc");
// 方法引用实现
list.stream().map(String::toUpperCase).forEach(System.out::println);
引用数组的构造方法
引用数组的构造方法,就是创建指定数据类型的数组
格式:数据格式[ ] : : new
细节:需要跟流中的数据类型保持一致
// 创建集合
ArrayList<Integer> list = new ArrayList<>();
// 添加元素
Collections.addAll(list, 1, 2, 3, 4, 5, 6);
// 方法引用实现,转数组
Integer[] array = list.stream().toArray(Integer[]::new);
for (Integer integer : array) {
System.out.println(integer);
}
枚举定义
public enum StateStatus {
UNSTATED(0),
STATED(1);
private final Integer type;
StateStatus(Integer type) {
this.type = type;
}
public Integer getType() {
return type;
}
}