文章目录
回顾多线程
-
线程安全
-
影响线程安全的原因
-
//明确1:单个线程不存在线程安全问题 //明确2:多个线程在对同一个资源进行读操作时,也不存在线程安全问题 多个线程在对同一个资源进行写操作时,造成数据的不同步(错乱数据),此时会出现线程安全问题
-
-
解决线程安全
-
同步代码块
-
语法:
-
//对象锁:可以任意类型的对象 synchronized(对象锁){ //对共享资源进行操作的代码 }
-
-
-
同步方法
-
语法:
-
//非静态的方法 public synchronized 返回值类型 方法名(参数列表){ //锁:this //对共享资源进行操作的代码 } //静态方法 public synchronized static 返回值类型 方法名(参数列表){ //锁:类.class //对共享资源进行操作的代码 }
-
-
-
Lock锁
-
完成可以代替同步代码块和同步方法
-
在开发中Lock锁比同步代码块、同步方法更灵活(好用) 【 Condition 】
-
使用:
-
//创建Lock锁对象 Lock l = new ReenTrantLock(); void run(){ .... l.lock(); //获取锁 try{ //对共享资源进行操作的代码 }finally{ l.unlock();//释放锁 } ,... }
-
-
-
-
-
死锁
- 产生死锁的情况:
- 多个线程中使用了嵌套的同步代码块,且在同步代码块中进行对象锁的循环交替,此时会有概率出现死锁
- 避免死锁:
- 代码中不要书写嵌套同步代码块
- 产生死锁的情况:
-
线程状态
- 6种:新建、可运行、锁阻塞、无限等待、计时等待、终止
-
线程通讯
- 概念:A线程去唤醒B线程
- 线程通讯要使用的方法:
- wait()、wait(long time)
- wait():无限等待
- wait(long) : 计时等待
- notify()、notifyAll()
- notify(): 唤醒同一个对象锁下的处于等待状态的任意一个线程
- notifyAll():唤醒同一个对象锁下的处于等待状态的所有线程
- wait()、wait(long time)
-
线程池
-
概念:在一个容器中,预先创建了一些Thread,供程序中使用
-
原理:在线程池中创建一些Thread对象,把线程任务提交给线程池,由线程池分配Thread来执行任务,当任务执行结束之后,Thread对象会回归到线程池中(Thread可以反复使用)
-
使用:
-
//1、创建线程池 ExecutorService es = Executors.newFixedThreadPool(线程数量); //2、定义线程任务 Task task = new Task();//Task类实现Runnable接口 或 实现Callable接口 //3、提交任务给线程池 Future f = es.submit(task); //4、对于 Callable接口 有返回值需要接收任务返回的结果 Object result = f.get();
-
-
一、Lambda表达式
1、 Lambda的介绍
Lambda表达式是JAVA8中提供的一种新的特性,是一个匿名函数(方法)。可以把 Lambda表达式 理解为是一段可以传递的代码 (将代码像数据一样进行传递),可以写出更简洁、更灵活的代码。
2 、Lambda表达式使用前提条件
Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:
- 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。无论是JDK内置的
Runnable
、Comparator
接口还是自定义的接口,只有当接口中的**抽象方法存在且唯一时,**才可以使用Lambda。 - 使用Lambda必须具有上下文推断。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一个抽象方法的接口,称为“函数式接口”。
Lambda表达式可以认为是对匿名内部类的一种简化,但不是所有的匿名内部类都可以简化为Lambda表达式。只有函数式接口的匿名内部类才可以使用Lambda表达式来进行简化。函数式接口是一种比较特殊的接口,接口中只有一个抽象方法是需要我们去实现的。而Lambda表达式体现的就是这个抽象方法的实现。
// 使用Lambda表达式有什么条件?
Lambda表达式的定义需要有函数式接口
// 什么是函数式接口?
只有一个抽象方法需要实现的接口我们称为函数式接口
// Lambda有什么作用?
可以用来简化函数式接口的匿名内部类
代码比较
// 匿名内部类方式创建线程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类方式——启动线程");
}
});
t1.start();
Lambda表达式去创建线程
------------------------------------------------------------
// Lambda表达式去实现
Thread t2 = new Thread(() ‐> System.out.println("Lambda方式创建线程"));
t2.start();
3、Lambda表达式语法格式
Lambd表达式就是对函数式接口中抽象方法的实现,
是对其匿名内部类的一个简写**,只保留了方法的参数列表和方法体**,其他的成分可以省略。
因此Lambda的格式非常简洁,只有三部分组成:
1. 参数列表
2. 箭头 ->
3. 方法体
(参数列表)->{方法体}
4、Lambda的省略格式
1 省略规则说明
整个Lambda格式只有三个部分组成,参数列表,箭头,方法体。
-
箭头不能省(格式的组成)
-
能省只有两个:参数类别,方法体
参数列表的省略规则
-
如果参数列表有参数,参数类型可以省略,一省全省。
-
如果参数列表只有一个参数,类型和小括号都可以一起省略,如果括号省略,类型一定要省略。
方法体的省略规则
如果方法体中只存在一条语句,可以将方法体的大括号,当前语句的分号,如果有返回值对应的return 都可以省略,一省全省。
5、含有参数和返回值的Lambda表达式介绍
我们之前学习过java.util.Comparator接口,就是一个函数式接口,我们需要实现里面的compare抽象方法,就是有参数和返回值的如下:public abstract int compare(T o1, T o2) ; Lambda的格式只有三部分:参数列表,箭头,方法体。返回值类型的表示不管用什么都不写,如果有返回值,只要正常在方法体返回对应数据就可,参数列表保持一致,因此没有有没有返回值,参数列表,变化不大。
代码演示:
// 匿名内部类排序
Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
});
---------------------------------------------------------
//Lambda省略模式
Collections.sort(list, (p1, p2) -> p2.getAge() - p1.getAge() );
6、函数式接口
只有一个抽象方法需要实现的接口,函数式接口。
函数式接口是允许有其他的非抽象方法的存在例如静态方法,默认方法,私有方法。
为了标识接口是一个函数式接口,可以在接口之上加上一个注解: @FunctionalInterface
JDK的 java.util.function 包中的所有接口都是函数式接口。之前学习线程时的Runnable也是函数式接口。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// 我们自己也可以定义函数式接口,比如定义一个游泳的函数式接口:
@FunctionalInterface
public interface Swim {
public abstract void swimming();
}
二、Stream流
1、 Stream流的介绍
Stream流是Java1.8才出现的。处理极大的简化了对于集合、数组等的操作,只要是可以转换成流,
我们都可以借助流式处理。
一个流式操作可以分为三个部分:获取流、中间操作、终端操作。
我们先去将要使用流处理的数组或者集合转换为流,然后再进行中间方法的调用,最后使用终端方法
2、Stream流对象获取
**1)Collection获取流 ** *
自从JDK8开始,Collection存在一个默认方法stream去获取流对象。
所有Collection的实现类都具有该方法,能够直接使用集合对象调用该方法。
default Stream<E> stream() // 返回以此集合作为源的顺序 Stream 。
Stream s = 集合.stream();
2) Map集合获取流
Map中没有专属的获取流的默认方法。可以先将Map集合转换为Collection集合,再转换Stream就可。如果要使用Stream处理键值对对象数据,可以借助entrySet。如果要专门处理值及,可以借助values方法。
//获取键值对对象对应的流
Stream<Map.Entry<String, String>> s1 = map.entrySet().stream();
//获取键对应的流
Stream<String> s2 = map.keySet().stream();
//获取值对应的流
Stream<String> s3 = map.values().stream();
3)数组转换为流对象
Stream接口本身存在一个静态方法:of
static Stream< T > of (T… values) 可以指定该类型的数组或者单个的多个数据也可以转换为流
//数组
String[] arr = {"A", "B"};
Stream<String> s1 = Stream.of(arr);
//单个存在的多个数据
Stream<String> s2 = Stream.of("A", "B", "C");
3、Stream流常用方法
1 、流的终端操作
long count(); // 统计流中数据的个数
void forEach(Consumer<? super T> action); // 遍历流中的数据
collect(Collectors.toList()); // 将流中数据收集到List集合中
collect(Collectors.toSet ()); // 将流中数据收集到Set集合中
Object[] toArray(); // 将流中数据收集到数组中
// forEach方法参数中的函数式接口
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
//省略其他
}
2 、流的中间操作方法:过滤,去重,排序
Stream<T> filter (Predicate<? super T> predicate); // 根据条件过滤不需要的数据
Stream<T> limit (long n); // 获取流中前n个数据,不足n个获取全部
Stream<T> skip (long n); // 跳过流中前n个数据,不足n个则没有数据
Stream<T> distinct (); // 去重(equals比较为true的元素会被去重)
Stream<T> sorted (); // 自然排序
Stream<T> sorted (Comparator<? super T> comparator); // 自定义比较器排序
// filter方法参数类型
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
//省略......
}
3 、流的中间操作方法:映射
// 将流中数据转换为另外一种想要的数据类型
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
//省略...
}
4、 流的中间操作方法:拼接
// 将两个流合并为一个流
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
代码演示
映射与拼接
Stream<String> stream1 = one
.stream()
.filter(name -> name.length() == 3)
.limit(3);
Stream<String> stream2 = two
.stream()
.filter(name -> name.startsWith("张"))
.skip(2);
Stream<String> newStream = Stream.concat(stream1, stream2);
List<Person> personList = newStream
.map(name -> new Person(name)) // String --> 映射成 Object类型
.collect(Collectors.toList()); // 将流中数据收集到List集合中
获取流 + 流的中间操作方法(过滤,去重,排序) + 流终端操作
public class StreamDemo {
public static void main(String[] args) {
//List集合
List<Student> students = new ArrayList<>();
students.add(new Student("小明", 18, 100));
.............
.............
.............
// 1. 筛选出所有及格的成员 并打印
students
.stream()
.filter(student -> student.getScore() >= 60)
.forEach(stu -> System.out.println(stu));
// 2. 对学员信息进行去重 并打印
students
.stream()
.distinct()
// distinct() 使用对象的equals方法比较两个对象是否相同
// 当比较对象内容时,需要:对象中重写equals方法
.forEach(stu -> System.out.println(stu));
// 3. 获取流中前3名学员 并打印
students
.stream()
.limit(3)
.forEach(stu-> System.out.println(stu));
// 4. 对学生按照分数升序排序 并打印
students
.stream()
.sorted((s1,s2) -> s1.getScore() - s2.getScore())
.forEach(stu -> System.out.println(stu));
// 5.
students
.stream()
.distinct()
.filter( stu -> stu.getScore()>=60 )
.limit(3) // 取前三个
.sorted((s1,s2) -> s2.getScore() - s1.getScore())
.forEach(stu -> System.out.println(stu));
}
}
三、File文件类
1 、File类介绍
java.io.File 类是
文件和目录路径名
的抽象表示,主要用于文件和目录的创建、查找和删除等操作。
文件和目录都使用File类来表示
文件和目录都使用路径来表示
例如:
code目录用路径表示 D:\abc\code
123.jpg文件用路径表示 D:\abc\123.jpg
**注意:**路径是唯一的,同一台计算机中不可能存在有两个不同的文件但路径又相同。
目录和文件名 是不区分大小写的
2、 File对象定义
在IO流学习中,会读取文件的信息或者写信息到文件,我们需要构建文件对象来定位计算机中的真实文件。File构造方法如下:
public File(String pathname)
// 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
public File(String parent, String child)
// 从父路径名字符串和子路径名字符串创建新的 File实例。
public File(File parent, String child)
// 从父抽象路径名和子路径名字符串创建新的 File实例
了解相对路径与绝对路径
**绝对路径:**从盘符开始的路径,这是一个完整的路径。
**相对路径:**相对于项目目录的路径,这是一个便捷的路径,开发中经常使用
3、File类常用功能
1) 获取的方法
public String getAbsolutePath() // 返回此File的绝对路径名字符串。
public String getPath() // 将此File转换为路径名字符串。 【构造路径】
public String getName() // 返回由此File表示的文件或目录的名称。 【如果有后缀,包含】
public long length() // 返回由此File表示的文件的长度(文件的大小,单位是字节)。 不能获取目录的长度。File类常用功能
2 )判断的方法
public boolean exists() // 此File表示的文件或目录是否实际存在。
public boolean isDirectory() // 此File表示的是否为目录。
public boolean isFile() // 此File表示的是否为文件。
3 )创建删除的方法
public boolean createNewFile() // 当且仅当具有该名称的文件尚不存在时,创建一个新的空件。
public boolean mkdir() // 创建由此File表示的目录。(单个目录)
public boolean mkdirs() // 创建由此File表示的目录,包括必需但不存在的父目录。(多级目录)
public boolean delete() // 删除由此File表示的文件或目录。
4 )目录遍历的方法
public String[] list() // 返回一个String数组,表示该File目录中的所有子文件或目录。
public File[] listFiles() // 返回一个File数组,表示该File目录中的所有的子文件或目录
四、递归
什么是递归?
递归就是方法自身调用自身的结果
递归通常是由三个部分组成,分别是什么?
递归前进段、边界条件、递归返回段。
当边界条件不满足时,递归前进
当边界条件满足时,递归返回
递归有什么隐患?
每次递归时方法都会入栈,从而占用栈内存资源,当递归次数太多,
栈内存用完时会造成内存溢出错误。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-whcfWK3l-1617099115506)(C:/Users/16096/Desktop/java127/javaSE127学习讲义/JavaSE16全天版本(v2.0)/JavaSE16天全版/day10【递归,字节流,字符流,IO异常处理,Properties】/imgs/day08_01_递归累和.jpg)]
tip:递归一定要有条件限定,保证递归能够停止下来,次数不要太多,否则会发生栈内存溢出。
(递归实现)n的阶乘:n! = n * (n-1) … 3 * 2 * 1
public class DiGuiDemo {
public static void main(String[] args) {
int n = 3;
// 调用求阶乘的方法
int value = getValue(n);
// 输出结果
System.out.println("阶乘为:"+ value);
}
public static int getValue(int n) {
// 1的阶乘为1 , 直到n == 1时,递归结束,开始回归返回值
if (n == 1) {
return 1;
}
return n * getValue(n - 1);
}
}
(递归实现)文件搜索
目的:输出d:\aaa目录中的所有.java文件的绝对路径。
分析:
- 目录搜索,无法判断多少级目录,所以使用递归,遍历所有目录。
- 遍历目录时,获取的子文件,通过文件名称,判断是否符合条件。
代码实现:
public class DiGuiDemo3 {
public static void main(String[] args) {
// 创建File对象
File dir = new File("D:\\aaa");
// 调用打印目录方法
printDir(dir);
}
public static void printDir(File dir) {
// 获取子文件和目录
File[] files = dir.listFiles();
// 循环打印
for (File file : files) {
if (file.isFile()) {
// 是文件,判断文件名并输出文件绝对路径
if (file.getName().endsWith(".java")) {
System.out.println("文件名:" + file.getAbsolutePath());
}
} else {
// 是目录,继续遍历,形成递归
printDir(file);
}
}
}
}
总结
-
Lambda表达式
-
应用点:代码书写的简化
-
Lambda表达式的作用:用来简化针对匿名内部类代码的书写
-
new Thread( new Runnable(){ //重写run public void run(){ ...... } }); //使用Lambda简化: new Thread( () -> { ..... });
-
-
在Java要使用Lambda表达式,提前:针对函数式接口
- 函数式接口:接口中有且仅有一个抽象方法
-
Lambda表达式的标准语法:
-
(参数列表) -> { } // 参数列表 // -> // 方法体
-
-
可以对Lambda进行省略格式:
-
省略参数列表
-
//省略1:省略参数的数据类型 (String str,int n) -> { .... } (str,n) -> {....} //省略2:参数列表只有一个参数时,可以省略数据类型、小括号 (int num) -> { ... } num -> { .... }
-
-
省略方法体
-
//当方法体中仅有一行代码时,可以省略:大括号、return、分号 (str) -> { return str.length(); } str -> str.length
-
-
-
-
Stream流
-
应用点:针对集合、数组进行操作简化
-
概念:流,就相当于生活中的流水线
-
Stream流的应用: 获取流、中间操作、终端操作
-
获取流:
-
集合转为Stream流:
-
//List集合 Stream s = list集合.stream() //Set集合 Stream s = set集合.stream() //Map集合 Stream<K> s = map集合.keySet().stream() Stream<Map.Entry> s = map集合.entrySet().stream()
-
-
数组转为Stream流:
-
Stream s = Stream.of(数组[])
-
-
-
中间操作:
-
常用API方法
-
//过滤 filter() //获取前n个数据 limit( n ) //跑过前n个,获取后面的数据 skip( n ) //去重 distinct() //排序 sorted() //自然排序 sorted(Comparator c) //比较器
-
-
-
终端操作:
-
常用API方法
-
//遍历输出 foeEach() //计算数据个数 count() //收集数据,转为集合 collect() //收集数据,转为数组 toArray()
-
-
-
-
-
File类
File类:表示硬盘上的文件或目录
//硬盘上的文件 String path = "f:/files/hello.txt"; //绝对路径(有盘符) //实例化 File file = new File(path);
//硬盘上的文件 String parent ="f:/files" //父路径 //绝对路径 String child ="demo/HelloWorld.java";//子路径 //相对路径(没有盘符) //父路径+子路径 = "f:/files/demo/HelloWorld" //实例化 File file = new File(parent , child);
//硬盘上的目录 //原目录路径:f:/files/demo/test/123.txt //把原目录拆分: //父路径: f:/files File parent = new File("f:/files"); //子路径:demo/test/123.txt String child = "demo/test/123.txt" //相对路径(没有盘符) //父路径+子路径 File file = new File(parent , child);
String[] list() //获取当前目录下所有文件及子目录的名称 File[] listFiles() //获取当前目录下所有文件及子目录的File对象
e类
File类:表示硬盘上的文件或目录
//硬盘上的文件
String path = "f:/files/hello.txt"; //绝对路径(有盘符)
//实例化
File file = new File(path);
//硬盘上的文件
String parent ="f:/files" //父路径 //绝对路径
String child ="demo/HelloWorld.java";//子路径 //相对路径(没有盘符)
//父路径+子路径 = "f:/files/demo/HelloWorld"
//实例化
File file = new File(parent , child);
//硬盘上的目录
//原目录路径:f:/files/demo/test/123.txt
//把原目录拆分:
//父路径: f:/files
File parent = new File("f:/files");
//子路径:demo/test/123.txt
String child = "demo/test/123.txt" //相对路径(没有盘符)
//父路径+子路径
File file = new File(parent , child);
String[] list() //获取当前目录下所有文件及子目录的名称
File[] listFiles() //获取当前目录下所有文件及子目录的File对象