一,JDK8新特性:Stream
1,认识Stream
-
也叫Stream流,是jdk8开始新增的一套API (java.util.stream.*),可以用于操作集合或者数组的数据。
-
优势: Stream流大量的结合了Lambda的语法风格来编程,提供了一种更加强大,更加简单的方式操作集合或者数组中的数据,代码更简洁,可读性更好。
案例需求:有一个List集合,元素有"河南大学","郑州大学","河南科技大学","黄淮学院","华北水利水电大学","河南工业大学","河南农业大学"
,找出河南开头,且是6个字的校名,存入到一个新集合中去。
HashSet<String> set = new HashSet<>();
Collections.addAll(set,"河南大学", "郑州大学", "河南科技大学", "黄淮学院", "华北水利水电大学", "河南工业大学", "河南农业大学");
用传统方式来做,代码是这样的
// 找出河南开头,且是6个字的校名,存入到一个新集合中去。
List<String> list = new ArrayList<>();
for (String name : set) {
if(name.startsWith("张") && name.length() == 3){
list.add(name);
}
}
System.out.println(list);
用Stream流来做,代码是这样的(ps: 是不是想流水线一样,一句话就写完了)
List<String> list = set.stream().filter(s -> s.startsWith("河南") && s.length() == 6).collect(Collectors.toList());
System.out.println(list);
}
下面我们就把目光定睛在Stream流的获取,对流的处理,以及总结方法。
2,Stream流的创建
主要掌握下面四点:
1、如何获取List集合的Stream流?
2、如何获取Set集合的Stream流?
3、如何获取Map集合的Stream流?
4、如何获取数组的Stream流?
// 1、如何获取List集合的Stream流?
List<String> names = new ArrayList<>();
Collections.addAll(names, "张三丰","张无忌","周芷若","赵敏","张强");
Stream<String> stream = names.stream();
// 2、如何获取Set集合的Stream流?
Set<String> set = new HashSet<>();
Collections.addAll(set, "刘德华","张曼玉","蜘蛛精","马德","德玛西亚");
Stream<String> stream1 = set.stream();
stream1.filter(s -> s.contains("德")).forEach(s -> System.out.println(s));
// 3、如何获取Map集合的Stream流?
Map<String, Double> map = new HashMap<>();
map.put("古力娜扎", 172.3);
map.put("迪丽热巴", 168.3);
map.put("马尔扎哈", 166.3);
map.put("卡尔扎巴", 168.3);
Set<String> keys = map.keySet();
Stream<String> ks = keys.stream();
Collection<Double> values = map.values();
Stream<Double> vs = values.stream();
Set<Map.Entry<String, Double>> entries = map.entrySet();
Stream<Map.Entry<String, Double>> kvs = entries.stream();
kvs.filter(e -> e.getKey().contains("巴"))
.forEach(e -> System.out.println(e.getKey()+ "-->" + e.getValue()));
// 4、如何获取数组的Stream流?
String[] names2 = {"张翠山", "东方不败", "唐大山", "独孤求败"};
Stream<String> s1 = Arrays.stream(names2);
Stream<String> s2 = Stream.of(names2);
总结:Collection接口的实现类都可以通过对象调用stream( ) 方法来获取Stream对象。而对于map这个特殊的集合只能先获取其keys或values的对象,得到一个集合然后在调用其中的Stream对象 。当然也可以通过内部对键值对的封装的Set集合获取Stream对象。
而对于数组可以通过Arrays.stream()方法来获取。
对于不太确定或者已经确定的数据都可以视同Stream类的静态方法 of()来获取对象。很香。类比于Collections的addAll方法。
Stream<String> dx = Stream.of("河南大学", "郑州大学", "河南科技大学", "华北水利水电大学", "河南工业大学");
3,Stream的常用方法
上面学习了Stream流的获取,下面再学习 第二步,Stream流常见的中间方法。
Stream****提供的常用中间方法 | 说明 |
---|---|
Stream filter(Predicate<? super T> predicate) | 用于对流中的数据进行过滤。 |
Stream sorted() | 对元素进行升序排序 |
Stream sorted(Comparator<? super T > comparator) | 按照指定规则排序 |
Stream limit(long maxSize) | 获取前几个元素 |
Stream skip(long n) | 跳过前几个元素 |
Stream distinct() | 去除流中重复的元素。 |
Stream map(Function <? super T ,? extends R> mapper) | 对元素进行加工,并返回对应的新流 |
static Stream concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
通过下面的代码做一个练习:
下面的代码大量的运用lambda,如果没有学,可以看前面的笔记,有详细的介绍。对于lambda表达式中filter方法。可以这样理解其他的也类似。s -> s >= 60 前面代表当前对象,后面的代表的是条件,也就是只有当后面成立的时候(为true)才会将当前的元素,添加到Stream流中。基本都是这种思想
List<Double> scores = new ArrayList<>();
Collections.addAll(scores, 88.5, 100.0, 60.0, 99.0, 9.5, 99.6, 25.0);
// 需求1:找出成绩大于等于60分的数据,并升序后,再输出。
scores.stream().filter(s -> s >= 60).sorted().forEach(s -> System.out.println(s));
List<Student> students = new ArrayList<>();
Student s1 = new Student("lucky", 26, 172.5);
Student s2 = new Student("Lucy", 26, 172.5);
Student s3 = new Student("bob", 23, 167.6);
Student s4 = new Student("jack", 25, 169.0);
Student s5 = new Student("张一", 35, 183.3);
Student s6 = new Student("之辈", 34, 168.5);
Collections.addAll(students, s1, s2, s3, s4, s5, s6);
// 需求2:找出年龄大于等于23,且年龄小于等于30岁的学生,并按照年龄降序输出.
students.stream().filter(s -> s.getAge() >= 23 && s.getAge() <= 30)
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
.forEach(s -> System.out.println(s));
// 需求3:取出身高最高的前3名学生,并输出。
students.stream().sorted((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()))
.limit(3).forEach(System.out::println);
System.out.println("-----------------------------------------------");
// 需求4:取出身高倒数的2名学生,并输出。 s1 s2 s3 s4 s5 s6
students.stream().sorted((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()))
.skip(students.size() - 2).forEach(System.out::println);
// 需求5:找出身高超过168的学生叫什么名字,要求去除重复的名字,再输出。
students.stream().filter(s -> s.getHeight() > 168).map(Student::getName)
.distinct().forEach(System.out::println);
// distinct去重复,自定义类型的对象(希望内容一样就认为重复,重写hashCode,equals)
students.stream().filter(s -> s.getHeight() > 168)
.distinct().forEach(System.out::println);
Stream<String> st1 = Stream.of("张三", "李四");
Stream<String> st2 = Stream.of("张三2", "李四2", "王五");
Stream<String> allSt = Stream.concat(st1, st2);
allSt.forEach(System.out::println);
还有一个map方法:这个方法用于对值进行操作然后得到返回之后的数据添加到Stream流中的。也很常用
// 需求,将字符串中的数据先切分,然后找出其中的偶数并输出
String str = "1,2,3,4,5,6,8,4,6,3,56,3,5,3";
String[] split = str.split(",");
Stream<String> stream = Stream.of(split);
stream.map(Integer::parseInt).filter(s -> s % 2 == 0).forEach(System.out::println);
4,Stream流终结方法(重点)
终结方法指的是调用完成后,不会返回新Stream了,没法继续使用流了。
Stream****提供的常用终结方法 | 说明 |
---|---|
void forEach(Consumer action) | 对此流运算后的元素执行遍历 |
long count() | 统计此流运算后的元素个数 |
Optional max(Comparator<? super T> comparator) | 获取此流运算后的最大值元素 |
Optional min (Comparator<? super T> comparator) | 获取此流运算后的最小值元素 |
5,收集Stream流
收集Stream流:就是把Stream流操作后的结果转回到集合或者数组中去返回。
Stream流:方便操作集合/数组的手段; 集合/数组:才是开发中的目的
Stream 提供的常用终结方法 | 说明 |
---|---|
R collect(Collector collector) | 把流处理后的结果收集到一个指定的集合中去 |
Object[] toArray() | 把流处理后的结果收集到一个数组中去 |
Collectors 工具类提供了具体的收集方式 | 说明 |
---|---|
public static Collector toList() | 把元素收集到List集合中 |
public static Collector toSet() | 把元素收集到Set集合中 |
public static Collector toMap(Function keyMapper , Function valueMapper) | 把元素收集到Map集合中 两个参数分别为每一个k和每一个v |
二,File类
要使用类和之前的思路一样,肯定是要获取到对象。两种方法,一种是通过静态方法,另一种就是通过构造方法。File不能通过静态方法获取的对象。那么就是构造方法啦,下面就来学习构造方法
构造器 | 说明 |
---|---|
public File(String pathname) | 根据文件路径创建文件对象 |
public File(String parent, String child) | 根据父路径和子路径名字创建文件对象 |
public File(File parent, String child) | 根据父路径对应文件对象和子路径名字创建文件对象 |
注意:
- File既可代表文件,也可以代表文件夹。File封装的对象仅仅是一个路径名称,这个路径可以是存在也可以是不存在的,也允许<这个到后面的io就会理解,因为如果不存在那么java会帮我们创建>
- 需求我们注意的是:路径中"\“要写成”\\“, 路径中”/"可以直接用
示例代码:
// 1、创建一个File对象,指代某个具体的文件。
// 路径分隔符
// File f1 = new File("D:/resource/ab.txt");
// File f1 = new File("D:\\resource\\ab.txt");
File f1 = new File("D:" + File.separator +"resource" + File.separator + "ab.txt");
System.out.println(f1.length()); // 文件大小
File f2 = new File("D:/resource");
System.out.println(f2.length());
// 注意:File对象可以指代一个不存在的文件路径
File f3 = new File("D:/resource/aaaa.txt");
System.out.println(f3.length());
System.out.println(f3.exists()); // false
// 我现在要定位的文件是在模块中,应该怎么定位呢?
// 绝对路径:带盘符的
// File f4 = new File("D:\\code\\javasepromax\\file-io-app\\src\\itheima.txt");
// 相对路径(重点):不带盘符,默认是直接去工程下寻找文件的。
File f4 = new File("file-io-app\\src\\itheima.txt");
System.out.println(f4.length());
在学习方法之前还需要了解两个概念绝对路径和相对路径:<在后面常用>:
绝对路径:
相对于盘符,即 C盘,D盘
File file1 = new File(“D:\\demo\\a.txt”);
相对路径:
在idea中是相对于项目目录:
File file3 = new File(“模块名\\a.txt”);
下面就来学习相对方法:
常用方法1:判断文件类型,获取文件信息
方法名称 | 说明 |
---|---|
public boolean exists() | 判断当前文件对象,对应的文件路径是否存在,存在返回true |
public boolean isFile() | 判断当前文件对象指代的是否是文件,是文件返回true,反之。 |
public boolean isDirectory() | 判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之。 |
public String getName() | 获取文件的名称(包含后缀) |
public long length() | 获取文件的大小,返回字节个数 |
public long lastModified() | 获取文件的最后修改时间。 |
public String getPath() | 获取创建文件对象时,使用的路径 <也就是构造器中的路径,给相对就是相对> |
public String getAbsolutePath() | 获取绝对路径 |
示例代码:
// 1.创建文件对象,指代某个文件
File f1 = new File("D:/resource/ab.txt");
//File f1 = new File("D:/resource/");
// 2、public boolean exists():判断当前文件对象,对应的文件路径是否存在,存在返回true.
System.out.println(f1.exists());
// 3、public boolean isFile() : 判断当前文件对象指代的是否是文件,是文件返回true,反之。
System.out.println(f1.isFile());
// 4、public boolean isDirectory() : 判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之。
System.out.println(f1.isDirectory());
// 除了这些判断功能还有一些获取的功能
File f1 = new File("D:/resource/ab.txt");
// 5.public String getName():获取文件的名称(包含后缀)
System.out.println(f1.getName());
// 6.public long length():获取文件的大小,返回字节个数
System.out.println(f1.length());
// 7.public long lastModified():获取文件的最后修改时间。
long time = f1.lastModified();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
System.out.println(sdf.format(time));
// 8.public String getPath():获取创建文件对象时,使用的路径
File f2 = new File("D:\\resource\\ab.txt");
File f3 = new File("file-io-app\\src\\itheima.txt");
System.out.println(f2.getPath());
System.out.println(f3.getPath());
// 9.public String getAbsolutePath():获取绝对路径
System.out.println(f2.getAbsolutePath());
System.out.println(f3.getAbsolutePath());
常用方法2:创建文件,删除文件
方法名称 | 说明 |
---|---|
public boolean createNewFile() | 创建一个新的空的文件 |
public boolean mkdir() | 只能创建一级文件夹 |
public boolean mkdirs() | 可以创建多级文件夹 |
方法名称 | 说明 |
---|---|
public boolean delete() | 删除文件、空文件夹 |
// 1、public boolean createNewFile():创建一个新文件(文件内容为空),创建成功返回true,反之。
File f1 = new File("D:/resource/itheima2.txt");
System.out.println(f1.createNewFile());
// 2、public boolean mkdir():用于创建文件夹,注意:只能创建一级文件夹
File f2 = new File("D:/resource/aaa");
System.out.println(f2.mkdir());
// 3、public boolean mkdirs():用于创建文件夹,注意:可以创建多级文件夹
File f3 = new File("D:/resource/bbb/ccc/ddd/eee/fff/ggg");
System.out.println(f3.mkdirs());
// 3、public boolean delete():删除文件,或者空文件,注意:不能删除非空文件夹。
System.out.println(f1.delete());
System.out.println(f2.delete());
File f4 = new File("D:/resource");
System.out.println(f4.delete());
1.mkdir(): 只能创建单级文件夹、
2.mkdirs(): 才能创建多级文件夹
3.delete(): 文件可以直接删除,但是文件夹只能删除空的文件夹,文件夹有内容删除不了。并且删除后的文件不仅回收站
常用方法3:遍历文件夹
方法名称 | 说明 |
---|---|
public String[] list() | 获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。 |
public File[] listFiles() | 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点) |
// 1、public String[] list():获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。
File f1 = new File("D:\\course\\待研发内容");
String[] names = f1.list();
for (String name : names) {
System.out.println(name);
}
// 2、public File[] listFiles():(重点)获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
File[] files = f1.listFiles();
for (File file : files) {
System.out.println(file.getAbsolutePath());
}
File f = new File("D:/resource/aaa");
File[] files1 = f.listFiles();
System.out.println(Arrays.toString(files1));
文件的遍历中的几个注意事项:
1.当主调是文件时,或者路径不存在时,返回null
2.当主调是空文件夹时,返回一个长度为0的数组
3.当主调是一个有内容的文件夹时,将里面所有一级文件和文件夹路径放在File数组中,并把数组返回
4.当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在FIle数组中,包含隐藏文件
5.当主调是一个文件夹,但是没有权限访问时,返回null
三,递归
1,认识递归
什么是递归?
递归是一种算法,从形式上来说,方法调用自己的形式称之为递归。
递归的形式: 有直接递归、间接递归,如下面的代码。
public class RecursionTest1 {
public static void main(String[] args) {
test1();
}
// 直接方法递归
public static void test1(){
System.out.println("----test1---");
test1(); // 直接方法递归
}
// 间接方法递归
public static void test2(){
System.out.println("---test2---");
test3();
}
public static void test3(){
test2(); // 间接递归
}
}
// 上面只是对递归的形式做一个说明,不具有实际意义
直接递归就是自己调用自己的方法。间接递归是自己调用别的方法,但是别的方法又调用了自己的方法。
从需求来学习:之前我们可能学过阶层,可以通过基础的java代码得到结果,那么怎样通过递归来获取呢?
假设f(n)表示n的阶乘,那么我们可以推导出下面的式子
f(5) = 1+2+3+4+5
f(5) = f(4)+5
f(4) = f(3)+4
f(3) = f(2)+3
f(2) = f(1)+2
f(1) = 1
可以推导是一个公式:
即: f(n) = f(n-1) * n
我们也会发现一个出口:
就是f(1) =1的时候,这个时候我们就无法在套用公式,这个就是我们的递归出口。
总结一下,递归的三个条件:
1. 要有递归公式
2. 要有递归出口 / 终止条件
3. 递归的方法必须走向递归出口
比如计算阶层的方法可以这样写:
public static void main(String[] args) {
System.out.println("5的阶乘是:" + f(5));
}
//求n个数的阶乘
public static int f(int n){
// 递归出口
if(n == 1){
return 1;
}else {
// 递归体
return f(n - 1) * n;
}
}
综合案例:
从指定的文件夹找出所有符合或者包含条件的文件:并打印
public static void main(String[] args) throws Exception {
searchFile(new File("D:/") , "QQ.exe");
}
/**
* 去目录下搜索某个文件
* @param dir 目录
* @param fileName 要搜索的文件名称
*/
public static void searchFile(File dir, String fileName) throws Exception {
// 1、把非法的情况都拦截住
if(dir == null || !dir.exists() || dir.isFile()){
return; // 代表无法搜索
}
// 2、dir不是null,存在,一定是目录对象。
// 获取当前目录下的全部一级文件对象。
File[] files = dir.listFiles();
// 3、判断当前目录下是否存在一级文件对象,以及是否可以拿到一级文件对象。
if(files != null && files.length > 0){
// 4、遍历全部一级文件对象。
for (File f : files) {
// 5、判断文件是否是文件,还是文件夹
if(f.isFile()){
// 是文件,判断这个文件名是否是我们要找的
if(f.getName().contains(fileName)){
System.out.println("找到了:" + f.getAbsolutePath());
Runtime runtime = Runtime.getRuntime();
runtime.exec(f.getAbsolutePath());
}
}else {
// 是文件夹,继续重复这个过程(递归)
searchFile(f, fileName);
}
}
}
}