一、集合排序
1、集合排序API
(1)集合排序概述
元素之间的比较可以是数字大小的比较、字符串的字典比较、对象的属性比较等,在java中实现集合排序的方式可以分为两大类
- 使用集合排序API
- 使用支持自动排序的集合
(2)Collections.sort()方法
集合的工具类,提供了很多便于操作集合的方法,其中sort()方法用于排序
方法定义为:void sort(List
/**
* @document: 集合中元素的排序
* @Author:SmallG
* @CreateTime:2023/8/8+14:05
*/
public class Demo01 {
public static void main(String[] args) {
//创建一个List集合
List<Integer> list = new ArrayList<>();
Random random = new Random();
//产生随机的数字
//往集合中随机保存十个数据
for (int i = 0; i < 10; i++) {
list.add(random.nextInt(100) + 1);
}
//显示集合中的数据
System.out.println(list);
//排序
Collections.sort(list);
System.out.println(list);
}
}
(3)Comparable接口
用于定义其子类是可以比较大小的,是比较大小的前提,在使用Collections.sort()这个方法的时候,排序的元素都必须是Comparable接口的实现类,Comparable接口有个用于比较大小的抽象方法compareTo,在使用compareTo(T t)方法时返回一个整数:
- 返回值 > 0时,表示当前对象比参数给定的对象大
- 返回值 < 0时,表示当前对象比参数给定的对象小
- 返回值 = 0时,表示当前对象和参数给定的对象相等
/**
* @document: 集合中元素的排序
* @Author:SmallG
* @CreateTime:2023/8/8+14:05
*/
public class Demo01 {
public static void main(String[] args) {
Student student1 = new Student("Tom", 18);
Student student2 = new Student("Jerry", 16);
Student student3 = new Student("SmallG", 21);
System.out.println("student1与student2比较:" + student1.compareTo(student2));
System.out.println("student2与student3比较:" + student2.compareTo(student3));
//构建集合
List<Student> list1 = Arrays.asList(student1, student2, student3);
//排序
System.out.println(list1);
// [Student{name='Tom', age=18}, Student{name='Jerry', age=16}, Student{name='SmallG', age=21}]
Collections.sort(list1);
System.out.println(list1);
// [Student{name='Jerry', age=16}, Student{name='SmallG', age=21}, Student{name='Tom', age=18}]
}
}
class Student implements Comparable<Student> {
String name;
int age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student o) {
int c = this.name.charAt(0) - o.name.charAt(0);
return c;
}
}
(4)comparator接口
用于定义比较逻辑,可用于在集合外部元素的比较逻辑
核心方法:compare()方法,用于比较两个元素的大小
int compare(T o1,T o2)
实现compare()方法返回值要求,判断字符串时,首字母相同按第二个字符进行排序
- 若o1 > o2则返回值 > 0
- 若o1 < o2则返回值 < 0
- 若o1 == o2则返回值 = 0
/**
* @document: Comparator接口比较逻辑
* @Author:SmallG
* @CreateTime:2023/8/8+15:04
*/
public class Demo03 {
public static void main(String[] args) {
Student student1 = new Student("Tom", 18);
student1.score = 85.6;
Student student2 = new Student("SmallG", 14);
student2.score = 85.5;
Student student3 = new Student("SmallY", 21);
student3.score = 75.6;
//构建List集合
List<Student> list = Arrays.asList(student1, student2, student3);
System.out.println(list);
//[Student{name='Tom', age=18, score=85.6}, Student{name='SmallG', age=14, score=85.5}, Student{name='SmallY', age=21, score=75.6}]
//排序 临时使用年龄进行排序 ,匿名内部类
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
});
System.out.println(list);
//[Student{name='SmallG', age=14, score=85.5}, Student{name='Tom', age=18, score=85.6}, Student{name='SmallY', age=21, score=75.6}]
//按照年龄从大到小排
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o2.age - o1.age;
}
});
System.out.println(list);
//[Student{name='SmallY', age=21, score=75.6}, Student{name='Tom', age=18, score=85.6}, Student{name='SmallG', age=14, score=85.5}]
//自定义临时排序规则,按照名字进行排序
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
//String类已经实现了Comparable的接口
return o1.name.compareTo(o2.name);
}
});
System.out.println(list);
//[Student{name='SmallG', age=14, score=85.5}, Student{name='SmallY', age=21, score=75.6}, Student{name='Tom', age=18, score=85.6}]
//按照成绩从小到大排列
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
if (o1.score-o2.score>0){
return 1;
}else if (o1.score-o2.score<0){
return -1;
}else{
return 0;
}
}
});
System.out.println(list);
//[Student{name='SmallY', age=21, score=75.6}, Student{name='SmallG', age=14, score=85.5}, Student{name='Tom', age=18, score=85.6}]
}
}
其他方法:
reversed():返回当前比较器的逆序比较器,将它原来的比较规则进行颠倒。
thenComparing(Comparator< ? super T > other):返回一个组合比较器,用于对两个比较规则进行联合排序,如果原始比较器认为两个元素相等,则使用传入的other比较器进一步比较
/**
* @document: Comparator的其他方法
* @Author:SmallG
* @CreateTime:2023/8/8+15:39
*/
public class Demo02 {
public static void main(String[] args) {
Student student1 = new Student("Tom", 18);
student1.score = 85.6;
Student student2 = new Student("SmallG", 18);
student2.score = 85.5;
Student student3 = new Student("SmallY", 21);
student3.score = 75.6;
//构建List集合
List<Student> list = Arrays.asList(student1, student2, student3);
//定义一个比较器,年龄升序的比较规则
Comparator<Student> c1 = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
};
//定义一个年龄降序的比较器,如果年龄相等按照添加的顺序进行排列
Comparator<Student> c2 = c1.reversed();
//先升序pail
Collections.sort(list, c1);
System.out.println("年龄升序排列:" + list);
Collections.sort(list, c2);
System.out.println("年龄降序排列:" + list);
//如果在年龄一样的情况下按照名字比较,组合使用比较器
Comparator<Student> c3 = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
};
//声明一个组合比较器 先按照年龄比较,如果年龄相同按照名字比较
Comparator<Student> c4 = c1.thenComparing(c3);
Collections.sort(list, c4);
System.out.println(list);
}
}
2、自动排序的集合
(1)树数据结构
一种非线性数据结构,由一组节点和节点之间的连接关系(边)组成
(2)二叉搜索树
- 对于根节点,左子树中所有节点的值 < 根节点的值 < 右子树中所有节点的值
- 任意节点的左、右子树也是二叉搜索树
(3)红黑树
红黑树通过遵守以下五条规则来保持树的平衡性
- 每个节点要么是红色,要么是黑色
- 根节点是黑色
- 每个叶子节点都是黑色
- 如果一个节点是红色的,则其两个子节点必须都是黑色的
- 从任意节点到其每个叶子节点的简单路径上,黑色节点的数量相同
(4)TreeMap
- 基于红黑树数据结构实现
- 键的有序性,键的排序可以是按照键类型的自然排序或者通过传入的Comparator对象来定义
- 查找效率:由于红黑树是一种自平衡的二叉搜索树,treeMap中的查找、插入和删除操作的时间复杂度都是O(log n),其中n是映射中键值对的数量。
- 允许null键:要注意在自定义比较器中处理null键的情况,否则可能导致异常
/**
* @document: TreeMap
* @Author:SmallG
* @CreateTime:2023/8/8+16:30
*/
public class Demo04 {
public static void main(String[] args) {
//创建TreeMap集合对象
TreeMap<Integer, String> treeMap = new TreeMap<>();
treeMap.put(5, "Tom");
treeMap.put(3, "Jerry");
treeMap.put(9, "SmallG");
treeMap.put(2, "SmallY");
//遍历集合
for (Map.Entry<Integer, String> entry : treeMap.entrySet()) {
System.out.println("Key:" + entry.getKey() + " Value: " + entry.getValue());
}
System.out.println("________________________________________");
//返回临近的较大键值对 8
Map.Entry<Integer, String> entry1 = treeMap.higherEntry(8);
System.out.println("临近8的较大键值对:" + entry1);
//返回临近的较小键值对
Map.Entry<Integer, String> entry2 = treeMap.lowerEntry(8);
System.out.println("临近8的较小键值对:" + entry2);
//获取TreeMap的子集(from,to) ,返回的结果是from和to之间,左闭右开
SortedMap<Integer, String> sortedMap = treeMap.subMap(3, 5);
System.out.println(sortedMap);
//可以设置是否包含边界(from或者to)
NavigableMap<Integer, String> map = treeMap.subMap(3, false, 5, true);
System.out.println(map);
System.out.println("_______________________________");
//返回逆序集合
System.out.println(treeMap.descendingMap());
}
}
(5)TreeSet
实现了SortedSet接口的有序集合,底层使用了一个TreeMap的key来存储所有的元素,TreeSet中不允许包含重复元素。
/**
* @document: TreeSet集合
* @Author:SmallG
* @CreateTime:2023/8/8+16:51
*/
public class Demo05 {
public static void main(String[] args) {
//创建员工对象
Employee emp1 = new Employee("Tom", 18);
Employee emp2 = new Employee("Jerry", 16);
Employee emp3 = new Employee("SmallG", 20);
Employee emp4 = new Employee("SmallY", 22);
TreeSet<Employee> set1 = new TreeSet<>();
set1.add(emp1);
set1.add(emp2);
set1.add(emp3);
set1.add(emp4);
System.out.println("set1" + set1);
//定义排序规则 名字升序排列
Comparator<Employee> c = new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
return o1.name.compareTo(o2.name);
}
};
//创建一个自定义排序规则
TreeSet<Employee> set2 = new TreeSet<>(c);
set2.addAll(set1);
System.out.println(set2);
}
}
class Employee implements Comparable<Employee> {
String name;
int age;
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Employee o) {
return this.age - o.age;
}
}
二、Lambda和Stream
1、Lambda表达式
允许代码块作为参数传递给方法或者作为返回值在方法中返回,是一个匿名函数,可以像普通方法一样传递参数并执行代码,使用 "->" 来将参数列表和方法体分开。好处 :用于简化代码,增强代码的可读性。
(1)函数接口:只有一个抽象方法的接口
用@FunctionalInterface注解标识,使用这个注解后只能定义一个方法。
/**
* @document: 声明一个函数式接口
* @Author:SmallG
* @CreateTime:2023/8/9+9:34
*/
@FunctionalInterface
public interface MyFunction {
int apply(int x, int y);
}
(2)基于Lambda表达式实现函数接口
/**
* @document: 实现MyFunction方法,两数加法
* @Author:SmallG
* @CreateTime:2023/8/9+9:39
*/
public class Demo07 {
public static void main(String[] args) {
//匿名内部类
MyFunction add1 = new MyFunction() {
@Override
public int apply(int x, int y) {
return x + y;
}
};
System.out.println(add1.apply(3, 5)); //8
//通过Lambda表达式实现接口中的方法 (参数1,参数2) ->返回值
MyFunction add2 = (x, y) -> x + y;
System.out.println(add2.apply(3, 5)); //8
}
}
(3)Lambda完整语法
参数列表:(x,y),如果只有一个参数可以省略括号
箭头:->,左侧是参数列表右侧是主体
主体:x + y,Lambda主体可以是一个表达式(不需要使用return返回)或代码块(需要return)
/**
* @document: 实现MyFunction方法,两数加法
* @Author:SmallG
* @CreateTime:2023/8/9+9:39
*/
public class Demo07 {
public static void main(String[] args) {
//通过Lambda表达式实现接口中的方法 (参数1,参数2) ->返回值
MyFunction add2 = (x, y) -> x + y;
System.out.println(add2.apply(3, 5)); //8
//完整语法
MyFunction add3 = (x, y) -> {
System.out.println("x=" + x + ",y=" + y);
int sum = x + y;
return sum;
};
System.out.println(add3.apply(5, 6));
//x=5,y=6
//11
//方法只有一个参数,括号可以不用写
MyFunction1 result = x -> x += 2;
System.out.println(result.apply(2)); //4
}
}
(4)使用Lambda过滤文件夹内容
/**
* @document: 基于Lambda表达式实现
* @Author:SmallG
* @CreateTime:2023/8/9+10:29
*/
public class FilterFiles2 {
public static void main(String[] args) {
File file1 = new File("C:\\Program Files\\Internet Explorer");
//使用Lambda表达式 过滤文件
FileFilter filter = pathname -> pathname.isFile() && pathname.getName().startsWith("i");
//获取过滤后的文件列表
File[] files = file1.listFiles(filter);
for (File file : files) {
System.out.println(file.getName());
}
}
}
(5)函数引用
函数引用语法形式:持有者 :: 方法名
函数引用可以分为以下四种类型:
- 静态方法引用:引用一个已有的静态方法,例如:Math :: abs
- 类方法引用:引用一个已有的实例类型方法,例如:String :: length
- 对象方法引用:引用一个已有的对象方法,例如:out :: println
- 构造函数引用:引用一个已有的构造函数(使用较少)例如:ArrayList :: new
/**
* @document: 函数式引用
* @Author:SmallG
* @CreateTime:2023/8/9+10:44
*/
public class Demo08 {
public static void main(String[] args) {
String str = "Hello World";
//快速的获取字符串的某个字符
//使用chatAt方法来实现Function接口的实现
//泛型Integer是charAt方法的参数类型,Character是方法的返回值类型
//对象方法引用
Function<Integer, Character> function = str::charAt;
System.out.println(function.apply(0)); //H
//类实例方法引用
List<String> list = Arrays.asList("Tom", "Jerry", "smallG");
//首字母 忽略大小写排序
Collections.sort(list, String::compareToIgnoreCase);
System.out.println(list); //[Jerry, smallG, Tom]
//类静态方法引用,将字符串转化为整数
Function<String, Integer> function1 = Integer::parseInt;
int num = function1.apply("123");
System.out.println(num); //123
//构造器引用 创建一个ArrayList对象,通过get方法获取这个集合对象
Supplier<ArrayList<String>> runnable = ArrayList::new;
ArrayList<String> list1 = runnable.get();
list1.add("Tom");
System.out.println(list1); //[Tom]
}
}
函数式引用和Lambda表达式对比:
/**
* @document:
* @Author:SmallG
* @CreateTime:2023/8/9+11:27
*/
public class Demo09 {
public static void main(String[] args) {
List<String> names = Arrays.asList("Tom", "Jerry", "Spike");
//输出集合的内容
names.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
System.out.println("———————————————————");
//输出集合的内容
names.forEach(s -> System.out.println(s));
System.out.println("———————————————————");
//函数式引用 输出集合的内容
names.forEach(System.out::println);
}
}
将字符串转化成大写形式:
/**
* @document:
* @Author:SmallG
* @CreateTime:2023/8/9+11:36
*/
public class Demo10 {
public static void main(String[] args) {
String s = "hello world";
//匿名内部类
Function<String, String> function = new Function<String, String>() {
@Override
public String apply(String s) {
return s.toUpperCase(Locale.ROOT); //把小写字母全部变大写
}
};
System.out.println(function.apply(s));
System.out.println("___________________________");
//Lambda表达式实现小写变大写
Function<String, String> function2 = s1 -> s1.toUpperCase(Locale.ROOT);
System.out.println(function2.apply(s));
System.out.println("___________________________");
//使用函数式引用来实现
Function<String, String> function3 = String::toUpperCase;
System.out.println(function3.apply(s));
}
}
2、Stream
(1)概述
允许在数据集上进行功能性操作,在使用流进行操作的时候,不会改变原始数据集合中的数据,而是返回一个新的流。
(2)示例:找出 J 开头的名字
/**
* @document:
* @Author:SmallG
* @CreateTime:2023/8/9+14:05
*/
public class Demo12 {
public static void main(String[] args) {
List<String> names = Arrays.asList("Tom", "Jerry", "Spike", "SmallG", "JAKE");
//找出所有以J开头的人名
List<String> newNames = new ArrayList<>();
//遍历names集合
for (String s : names) {
if (s.startsWith("J")) {
newNames.add(s);
}
}
System.out.println(newNames); //[Jerry, JAKE]
//使用Stream API找出以J开头的人名
List<String> filterNames = names.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("J");
}
}).collect(Collectors.toList());
System.out.println(filterNames); //[Jerry, JAKE]
//使用lambda表达式进行简化
List<String> filterNames2 = names.stream().filter(s -> s.startsWith("J")).collect(Collectors.toList());
System.out.println(filterNames2); //[Jerry, JAKE]
}
}
(3)常用的Stream API方法
- filter:过滤流中的元素,只保留符合条件的元素
- map:对流中的元素进行映射,将一个元素映射为另一个元素
- flatMap:对流中的元素进行扁平化映射,将一个元素映射为多个元素
- sorted:对流中的元素进行排序
- distinct:去重,保留流中的不同元素
- limit:限制流中元素的数量
- skip:跳过流中的前N个元素
- forEach:遍历流中的元素
- reduce:对流中的元素进行归约,得到一个结果
- collect:将流中的元素收集到一个集合中
- min和max:找出流中的最小值和最大值
- count:统计流中元素的数量
- anyMatch、allMatch和noneMatch:判断流中的元素是否满足某一个条件
- findFirst和findAny:找到流中的第一个元素和任意一个元素
- parallel和sequential:切换流的并行和串行模式
(4)stream API 的使用步骤
1、创建流
2、中间操作
3、终止操作
4、并行操作:对大数据集可以用并行操作来提高效率。
示例:映射数据
- 使用stream()方法将集合转化为流
- 使用filter()方法筛选
- 使用collect()方法将筛选后的结果转换为List集合
- 使用map()方法将字符串映射为字符串长度
/**
* @document: stream 映射数据
* @Author:SmallG
* @CreateTime:2023/8/9+14:27
*/
public class Demo13 {
public static void main(String[] args) {
//定义一个集合
List<String> names = Arrays.asList("Tom", "Jerry", "Spike", "SmallG", "JAKE");
//计算names集合中每个名字的长度
List<Integer> namesLength = names.stream().map(s -> s.length()).collect(Collectors.toList());
System.out.println(namesLength);
}
}
示例:统计数据
- 使用count()方法统计元素个数
- 使用max()和min()方法求最大值和最小值
- 使用sum()方法求元素的总和
- 使用average()方法求元素的平均值
/**
* @document: Stream数据统计
* @Author:SmallG
* @CreateTime:2023/8/9+14:43
*/
public class Demo14 {
public static void main(String[] args) {
List<Integer> number = Arrays.asList(4, 2, 5, 3, 1);
long count = number.stream().count(); //统计集合中所有数据的个数
int max = number.stream().max(Integer::compare).orElse(0);
int min = number.stream().min(Integer::compare).orElse(0);
int sum = number.stream().mapToInt(Integer::intValue).sum(); //求和
double avg = number.stream().mapToInt(Integer::intValue).average().orElse(0);
System.out.println("所有数据的个数:" + count);
System.out.println("最大值:" + max);
System.out.println("最小值:" + min);
System.out.println("所有数据的和:" + sum);
System.out.println("平均值:" + avg);
//对数据进行排序
List<Integer> sortList = number.stream().sorted().collect(Collectors.toList());
System.out.println(sortList);
}
}
三、练习
1 利用Lambda表达式过滤当前类目录中Java源文件,并且统计Java文件行数
获取当前源文件夹目录中全部Java源文件,统计每个文件的代码行数(代码行数不包含空行)。
提示:可以使用Lambda过滤当前源文件夹的文件列表,找到java源文件,再将每个文件打开统计代码行数,如果是空行可以跳过不统计。
运行效果如下图所示:
/**
* @document: 利用Lambda表达式过滤当前类目录中Java源文件,并且统计Java文件行数
* @Author:SmallG
* @CreateTime:2023/8/9+11:44
*/
public class FileCount {
public static void main(String[] args) throws IOException {
File file = new File("src/day03");
//使用Lambda表达式过滤文件
FileFilter filter = pathname -> pathname.isFile() && pathname.getName().endsWith(".java");
File[] files = file.listFiles(filter);
int count = 0;
for (File file1 : files) {
FileReader fr = new FileReader(file1);
BufferedReader br = new BufferedReader(fr);
String line = br.readLine();
while (line != null) {
if (line.trim().isEmpty()) {
line = br.readLine();
continue;
}
count++;
line = br.readLine();
}
}
System.out.println("Java文件行数 " + count);
}
}
2 利用Stream API 对一组学生数据按照年龄进行排序
创建Student类来表示学生,并定义name、age和grade等属性记载数据;然后创建一组学生数据,并使用Stream API对其按照年龄进行排序。
运行效果如下图所示:
/**
* @document: 利用Stream API对一组学生数据按照年龄进行排序
* @Author:SmallG
* @CreateTime:2023/8/9+15:09
*/
public class StudentSortAge {
public static void main(String[] args) {
Student student1 = new Student("Tom", 21, 90);
Student student2 = new Student("Jerry", 19, 78);
Student student3 = new Student("Spike", 18, 67);
Student student4 = new Student("SmallG", 23, 70);
Student student5 = new Student("SmallY", 18, 98);
List<Student> students = Arrays.asList(student1, student2, student3, student4, student5);
List<Student> collect = students.stream().sorted(((o1, o2) -> o1.age - o2.age)).collect(Collectors.toList());
for (Student student : collect) {
System.out.println(student);
}
}
}
class Student {
String name;
int age;
int grade;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", grade=" + grade +
'}';
}
public Student(String name, int age, int grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
}
3 利用Stream API 计算一组学生分数平均值
创建Student类来表示学生,并定义name、age和grade等属性;然后创建一组学生数据,并使用Stream API计算其分数平均值。
运行效果如下图所示:
/**
* @document: 利用Stream API计算一组学生分数平均值
* @Author:SmallG
* @CreateTime:2023/8/9+17:11
*/
public class StudentGradeAvg {
public static void main(String[] args) {
Student student1 = new Student("Tom", 21, 90);
Student student2 = new Student("Jerry", 19, 78);
Student student3 = new Student("Spike", 18, 67);
Student student4 = new Student("SmallG", 23, 70);
Student student5 = new Student("SmallY", 18, 98);
List<Student> students = Arrays.asList(student1, student2, student3, student4, student5);
List<Integer> studentGrade = new ArrayList<>();
for (Student student : students) {
studentGrade.add(student.grade);
}
double v = studentGrade.stream().mapToInt(Integer::intValue).average().orElse(0);
System.out.println("Average grade: " + v);
}
}