Stream流(Stream API),它是从JDK8以后来有的新特性,是专业用于 对集合或者数组进行便捷操作的。
案例需求:现有一个List集合,元素有 “张三丰”,“张无忌”,“周芷若”,“赵敏”,“张强”,现在需要找出姓张的人,且是3个字的名字,存入到一个新集合中去。
集合做法
List<String> names = new ArrayList<>();
Collections.addAll(names, "张三丰","张无忌","周芷若","赵敏","张强");
System.out.println(names);
传统做法
List<String> list = new ArrayList<>();
for (String name : names) {
if(name.startsWith("张") && name.length() == 3){
list.add(name);
}
}
System.out.println(list);
Stream流做法
List<String> list2 = names.stream().filter(s -> s.startsWith("张")).filter(a -> a.length()==3).collect(Collectors.toList());
System.out.println(list2);
体验完Stream流的创建后,接下来正式介绍Stream流,首先先看Stream流的创建(获取Stream流)。
Stream流的创建主要掌握以下四点:
代码实现
public static void main(String[] args) {
//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(names,"刘德华","张曼玉","蜘蛛精","马德","德玛西亚");
Stream<String> stream1 = names.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> vlaues = map.values();
Stream<Double> vs = vlaues.stream();
Set<Map.Entry<String,Double>> entries = map.entrySet();
Stream<Map.Entry<String, Double>> stream2 = entries.stream();
stream2.filter(e->e.getKey().contains("巴")).forEach(e-> System.out.println(e.getKey()+ "---" + e.getValue()));
//4.数组的Stream流
String[] names3 = {"张翠山", "东方不败", "唐大山", "独孤求败"};
Stream<String> s1 = Arrays.stream(names3);
Stream<String>s2 = Stream.of(names3);
}
中间方法:调用完方法之后其结果是一个新的Stream流,于是可以继续调用方法,这样一来就可以支持链式编程(或者叫流程式编程)
Stream提供的常用中间方法
中间方法的使用演示
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
List<Double> list = new ArrayList<>();
Collections.addAll(list,88.5, 100.0, 60.0, 99.0, 9.5, 99.6, 25.0);
//需求1:找出成绩大于60分的数据,并升序后,再输出。
list.stream().filter(s->s >= 60).sorted().forEach(s-> System.out.println(s));
List<Student> students = new ArrayList<>();
Student s1 = new Student("蜘蛛精", 26, 172.5);
Student s2 = new Student("蜘蛛精", 26, 172.5);
Student s3 = new Student("紫霞", 23, 167.6);
Student s4 = new Student("白晶晶", 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(o1.getHeight(), o1.getHeight()))
.limit(3).forEach(System.out::println);
System.out.println("=================================");
//需求4:取出身高倒数的2名学生,并输出。
students.stream().sorted(((o1, o2) -> Double.compare(o2.getHeight(), o2.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);
}
}
Stream流的终结方法,这些方法的特点是,调用完方法之后,其结果就不再是Stream流了,所以不支持链式编程。
常用的终结方法
代码使用演示
public class StreamTest4 {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
Student s1 = new Student("蜘蛛精", 26, 172.5);
Student s2 = new Student("蜘蛛精", 26, 172.5);
Student s3 = new Student("紫霞", 23, 167.6);
Student s4 = new Student("白晶晶", 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);
// 需求1:请计算出身高超过168的学生有几人。
long size = students.stream().filter(s -> s.getHeight() > 168).count();
System.out.println(size);
// 需求2:请找出身高最高的学生对象,并输出。
Student s = students.stream().max((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())).get();
System.out.println(s);
// 需求3:请找出身高最矮的学生对象,并输出。
Student ss = students.stream().min((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())).get();
System.out.println(ss);
// 需求4:请找出身高超过170的学生对象,并放到一个新集合中去返回。
// 流只能收集一次。
List<Student> students1 = students.stream().filter(a -> a.getHeight() > 170).collect(Collectors.toList());
System.out.println(students1);
Set<Student> students2 = students.stream().filter(a -> a.getHeight() > 170).collect(Collectors.toSet());
System.out.println(students2);
// 需求5:请找出身高超过170的学生对象,并把学生对象的名字和身高,存入到一个Map集合返回。
Map<String, Double> map =
students.stream().filter(a -> a.getHeight() > 170)
.distinct().collect(Collectors.toMap(a -> a.getName(), a -> a.getHeight()));
System.out.println(map);
// Object[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray();
Student[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray(len -> new Student[len]);
System.out.println(Arrays.toString(arr));
}
}
File类:表示当前系统下的文件(也可以是文件夹)。
通过file类提供的方法可以获取文件大小、判断文件是否存在、创建文件、创建文件夹等。
注意:
File对象只能对文件进行操作,不能操作文件中的内容。
File类使用的第一步就是创建File类的对象,但想要创建对象,得先了解File类有哪些构造器。
File类创建对象的代码演示
注意:路径中"" 要写成 “\”,而"/"可以直接使用。
public class FileTest1 {
public static void main(String[] args) {
// 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("console.txt");
System.out.println(f4.length());
}
}
刚才创建File对象的时候,会传递一个文件路径过来,但是File对象封装的路径到底存不存在,或者是文件夹还是文件,其实都是不清楚的,所以我们现在要使用File类中的其他方法来判断。
代码演示
public class Test {
public static void main(String[] args) {
//1.创建文件对象,指代某个文件
File file = new File("day/console.txt");
//2.public boolean exists(); 判断当前文件对象,对应的文件路径是否存在,存在返回true.
System.out.println(file.exists());
//3.public boolean isFile(); 判断当前文件对象指代的是否是文件,是文件返回true,反之。
System.out.println(file.isFile());
//4.public boolean isDirectory();判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之。
System.out.println(file.isDirectory());
}
}
当然,除了判断以外,还有一些获取功能。
File f1 = new File("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("ab.txt");
File f3 = new File("console.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());
File类还提供了创建和删除文件的方法,如下所示:
public class FileTest3 {
public static void main(String[] args) throws Exception {
// 1、public boolean createNewFile():创建一个新文件(文件内容为空),创建成功返回true,反之。
File f1 = new File("console.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():文件可以直接删除,但是文件夹只能删除空的文件夹,文件夹有内容则删除不了。
如何获取一个文件夹中的内容? 在File类中也是有相应方法的,如下所示:
public class FileTest4 {
public static void main(String[] args) {
// 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.当主调是文件时,或者路径不存在时,返回nul。
2.当主调是空文件夹时,返回一个长度为0的数组。
3.当主调是一个有内容的文件夹时,将里面所有一级文件和文件夹路径放在File数组中,并把数组返回。
4.当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在File数组中,包含隐藏文件。
5.当主调是一个文件夹,但是没有权限访问时,返回null。
当要查询或修改多层文件夹时,一次File类方法执行是不够的,这时候就可以使用递归来遍历文件夹。
案例需求:在D:\\
判断下搜索QQ.exe这个文件,然后直接输出。
1.先调用文件夹的listFiles方法,获取文件夹的一级内容,得到一个数组
2.然后再遍历数组,获取数组中的File对象
3.因为File对象可能是文件也可能是文件夹,所以接下来就需要判断
判断File对象如果是文件,就获取文件名,如果文件名是`QQ.exe`则打印,否则不打印
判断File对象如果是文件夹,就递归执行1,2,3步骤
所以:把1,2,3步骤写成方法,递归调用即可。
代码实现
public class Test3 {
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);
}
}
}
}
}