一、可变参数
1、介绍
- 在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以使用可变参数对其简化。可变参数底层其实就是一个数组,好处是在传递数据的时候,省的我们自己创建数组并添加元素,JDK底层帮我们自动创建数组并添加元素了。
2、格式
//格式: 修饰符 返回值类型 方法名(参数类型... 形参名){ }
public static int getSum(int ... args) {
int result = 0;
for (int agr: args) {
result = result + agr;
}
return result;
}
//使用
System.out.println(getSum(10, 18, 30, 55));
3、注意点
- 一个方法只能有一个可变参数;
- 如果方法中有多个参数,可变参数要放到最后。
4、应用场景
- Collections类中实现添加元素的一些方法,使用了可变参数。
//Collections addAll方法源码
public static <T> boolean addAll(Collection<? super T> c, T... elements) {
boolean result = false;
for (T element : elements)
result |= c.add(element);
return result;
}
//addAll使用
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
//原来写法
//list.add(12);
//list.add(14);
//list.add(15);
//list.add(1000);
//采用工具类 完成 往集合中添加元素
Collections.addAll(list, 5, 222, 1,2);
System.out.println(list);
}
- Stream类中的静态方法 of 方法
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
二、Collections工具类
1、介绍
java.utils.Collections
是集合工具类,用来对集合进行操作。
2、常用方法
public static void shuffle(List<?> list)
: 打乱集合顺序。public static <T> void sort(List<T> list)
: 将集合中元素按照默认规则排序。public static <T> void sort(List<T> list,Comparator<? super T> )
: 将集合中元素按照指定规则排序。
3、Comparator比较器
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("rose",18));
list.add(new Student("jack",16));
list.add(new Student("abc",20));
//实现Comparator比较器,使用自定义排序
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge()-o2.getAge();//以学生的年龄升序
}
});
三、Lambda表达式
1、介绍
Lambda表达式,也可称为闭包,是Java 8中引入的一个新特性。一个lambda表达式是一个匿名函数,并且这个函数没有名称且不属于任何类。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
2、语法
(parameters) -> expression
或
(parameters) ->{ statements; }
- 以下是lambda表达式的重要特征:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:只有一个参数时,无需定义圆括号;但多个参数需要定义圆括号。
- 可选的大括号:如果主体只包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值,则编译器会自动返回值,大括号需 要指定表达式返回了一个数值。
3、表达式例子
```java
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
```
3、使用Lambda表达式
- 要使用lambda表达式,需要建立本身的功能接口或使用Java提供的预定义功能接口。
- 功能性接口(或单一抽象方法接口),又称函数式接口,指的是有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,这样的接口可以隐式转换为 Lambda 表达式。例如:可运行接口、可调用接口、动作监听者等等。
public class LambdaTest {
//定义功能接口
@FunctionalInterface
interface MathOperation {
int operation(int a, int b);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
public static void main(String[] args) {
//不声明类型
MathOperation add = (x, y) -> x + y;
//声明类型
MathOperation sub = (int x, int y) -> x - y;
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
//函数作为参数
LambdaTest test = new LambdaTest();
test.operate(10, 15, (x, y)-> x + y);
//lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
/*
int var = 100;
MathOperation error = (x, y) -> {
var = x + 100; //error: Variable used in lambda expression should be final or effectively final
return x - y;
};
*/
}
}
4、变量作用域
- lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
- lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
- Lambda 表达式中可以直接访问外层的局部变量;
- 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
5、函数式接口
- 介绍
函数式接口,指的是有且仅有一个抽象方法,但是可以有多个非抽象方法的接口;又称功能性接口或单一抽象方法接口 - 不能使用以下类型的方法来声明一个函数式接口:
- default方法
- 静态方法
- 从Object类继承的方法(如:Comparator接口中的equals()方法)
- 案例说明
/*一个函数式接口可以重新声明Object类中的方法。该方法不被视为抽象方法。 Comparator接口有两个抽象方法:compare()和equals()。 equals()方法是Object类中的equals()方法的重新声明。 */ @FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); }
6、注解 @FunctionalInterface
- @FunctionalInterface该注解主要是:一种信息性注释类型,用于指示接口类型声明是Java语言规范中定义的函数式接口(功能接口)。
- 如果接口上只有一个抽象方法,但我们没有对其加上@FunctionalInterface 编译器仍然将其看作函数式接口。该注解是给编译器做检查使用的,如果使用了该注解,编译器就会检查该接口中的抽象方法是不是只有一个,如果有多个就会报错。
7、四大内置函数式接口
- 消费型接口: Consumer< T>; void accept(T t) 有参数,无返回值的抽象方法;
@FunctionalInterface public interface Consumer<T> { void accept(T t); }
- 供给型接口: Supplier < T>; T get() 无参有返回值的抽象方法;
@FunctionalInterface public interface Supplier<T> { T get(); }
- 断定型接口: Predicate; boolean test(T t):有参,但是返回值类型是固定的boolean;
@FunctionalInterface public interface Predicate<T> { boolean test(T t); }
- 函数型接口: Function<T,R> ;R apply(T t)有参有返回值的抽象方法;
@FunctionalInterface public interface Function<T, R> { R apply(T t); }
- 示例
//消费型接口: Consumer< T> void accept(T t)有参数,无返回值的抽象方法; Consumer<String> consumer = (x)->System.out.println(x); consumer.accept("Consumer"); //供给型接口: Supplier < T> T get() 无参有返回值的抽象方法; Supplier<String> supplier = ()->{ return "Supplier";}; System.out.println(supplier.get()); //断定型接口: Predicate<T> boolean test(T t):有参,但是返回值类型是固定的boolean; Predicate<String> predicate = (x)->x == null; System.out.println(predicate.test("abc")); //函数型接口: Function<T,R> R apply(T t)有参有返回值的抽象方法; Function<String, String> function = (x)->{return x + "-" + "bbb";}; System.out.println(function.apply("function"));
四、不可变集合
1、什么是不可变集合
是一个长度不可变,内容也无法修改的集合
2、使用场景
如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。当集合对象被不可信的库调用时,不可变形式是安全的。
3、不可变集合的分类
3.1、不可变的list集合
/**
* 1. 创建不可变list集合
*/
List<String> stockCode = List.of("SH6000000", "SH6000001", "SH6000002", "SH6000003");
/** error: java.lang.UnsupportedOperationException
stockCode.add("SH3000000");
stockCode.set(0, "SH3000000");
stockCode.remove("SH3000000");
*/
3.2、不可变的set集合
/**
* 1. 创建不可变set集合(获取一个不可变的Set集合时,参数一定要保证唯一性,不可重复)
*/
Set<String> stockCode = Set.of("SH6000000", "SH6000001", "SH6000002", "SH6000003");
/** error: java.lang.UnsupportedOperationException
stockCode.add("SH3000000");
stockCode.remove("SH3000000");
*/
3.2 不可变的map集合
/**
* 1. 创建键值对不超过10个的不可变Map集合
*/
Map.of("SH6000000", "9.90", "SH6000001", "19.90", "SH6000002", "29.90");
/**
* 2. 创建键值对超过10个的不可变Map集合
*/
Map<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");
//方法1:
Set<Map.Entry<String, String>> entries = hm.entrySet();
Map.Entry[] arr = entries.toArray(new Map.Entry[0]);
Map immutableMap1 = Map.ofEntries(arr);
//可简化为如下写法:
Map immutableMap2 = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));
/**
* toArray 方法在底层会比较集合的长度跟数组的长度两者的大小
* 如果集合的长度 <= 数组的长度:数据在数组中放的下,此时不会创建新的数组,而是直接用;
* 如果集合的长度 > 数组的长度 :数据在数组中放不下,此时会根据实际数据的个数,重新创建数组; 因此new Map.Entry[0] 直接指定长度为0即可,不用指定实际长度。
*/
/**
* 方法2:使用copyOf, jdk10或以上版本可用
* copyOf实现
* static <K, V> Map<K, V> copyOf(Map<? extends K, ? extends V> map) {
* if (map instanceof ImmutableCollections.AbstractImmutableMap) {
* return (Map<K,V>)map;
* } else {
* return (Map<K,V>)Map.ofEntries(map.entrySet().toArray(new Entry[0]));
* }
* }
*/
Map<String, String> immutableMap3 = Map.copyOf(hm);
五、Stream流
1、介绍
- 概念
- Stream流的思想
- Stream流的三类方法
- 获取Stream流
- 创建一条流水线,并把数据放到流水线上准备进行操作
- 中间方法
- 流水线上的操作
- 一次操作完毕之后, 还可以继续进行其他操作
- 终结方法
- 一个Stream流只能有一个终结方法
- 是流水线上的最后一个操作
- 获取Stream流
2、常见生成Stream流的方式
- Collection体系集合
使用默认方法stream()生成流, default Stream stream() - Map体系集合
把Map转成Set集合,间接的生成流 - 数组
通过Arrays中的静态方法stream生成流 - 同种数据类型的多个数据
通过Stream接口的静态方法of(T… values)生成流
/*1. 生成方式*/
//Collection体系:使用默认方法stream()生成流
List<String> lst = new ArrayList<>();
Stream<String> lstStream = lst.stream();
Set<String> set = new HashSet<>();
Stream<String> setStream = set.stream();
//Map体系:把Map转成Set集合,间接的生成流
Map<String, String> map = new HashMap<>();
Stream<String> valStream = map.values().stream();
Stream<String> keyStream = map.keySet().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
//数组:通过Arrays中的静态方法stream生成流
String[] array = {"hello", "world", "test1"};
Stream<String> arrStream = Arrays.stream(array);
//同种数据类型的多个数据:通过Stream接口的静态方法of(T... values)生成流
Stream<String> strArrStream = Stream.of("hello", "world", "test1");
Stream<Integer> intArrStream = Stream.of(1, 2, 3);
3、Stream流的中间操作方法
- 概念
- 中间操作的意思是, 执行完此方法之后, Stream流依然可以继续执行其他操作。
- 常见方法
方法名 | 说明 |
---|---|
Stream filter(Predicate predicate) | 用于对流中的数据进行过滤 |
Stream limit(long maxSize) | 返回此流中的元素组成的流,截取前指定参数个数的数据 |
Stream skip(long n) | 跳过指定参数个数的数据,返回由该流的剩余元素组成的流 |
static Stream concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
Stream distinct() | 返回由该流的不同元素(根据Object.equals(Object) )组成的流 |
Stream map(Function<? super T, ? extends R> mapper) | 对流中的每个元素应用一个函数,将其映射为新的元素 |
/*2. 中间操作方法*/
//filter
List<String> lst1 = new LinkedList<>();
lst1.add("SZ.300000");
lst1.add("SZ.300001");
lst1.add("SZ.300002");
lst1.add("SH.600000");
lst1.add("SH.600001");
lst1.add("SH.600002");
//a. 重写Predicate中的test方法进行filter
// idea提示:Anonymous new Predicate<String>() can be replaced with lambda
lst1.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("SH");
}
}).forEach(x->System.out.println(x));
System.out.println("--------");
//b. 因为Predicate接口中只有一个抽象方法test,可以使用lambda表达式来简化
lst1.stream().filter((String x)-> { return x.startsWith("SH"); }).forEach(x->System.out.println(x));
System.out.println("--------");
//c. 进一步简化lambda
lst1.stream().filter(x->x.startsWith("SH")).forEach(x->System.out.println(x));
System.out.println("--------");
//limit && skip
//取前3个数据在控制台输出
lst1.stream().limit(3).forEach(x->System.out.println(x));
System.out.println("--------");
//跳过2个元素,把剩下的元素中前2个在控制台输出
lst1.stream().skip(2).limit(2).forEach(x->System.out.println(x));
System.out.println("--------");
//concat && distinct
Stream<String> stream1 = lst1.stream().limit(4);
Stream<String> stream2 = lst1.stream().skip(2);
//合并stream1和stream2得到的流,并把结果在控制台输出,要求字符串元素不能重复
Stream.concat(stream1, stream2).distinct().forEach(x->System.out.println(x));
4、Stream流的终结操作方法
- 概念
- 终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作
- 常用方法
方法名 | 说明 |
---|---|
void forEach(Consumer action) | 对此流的每个元素执行操作 |
long count() | 返回此流中的元素数 |
5、Stream流的收集操作
- 概念
- 对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中
- 常用方法
方法名 | 说明 |
---|---|
R collect(Collector collector) | 把结果收集到集合中 |
- 工具类Collectors 提供了具体的收集方式
方法名 | 说明 |
---|---|
public static Collector toList() | 把元素收集到List集合中 |
public static Collector toSet() | 把元素收集到Set集合中 |
public static Collector toMap(Function keyMapper, Function valueMapper) | 把元素收集到Map集合中 |
/*3. collect*/
//处理过程:
//1.filter负责过滤数据的.
//2.collect负责收集数据; 获取流中剩余的数据, 但是他不负责创建容器, 也不负责把数据添加到容器中.
//3.Collectors.toList() : 在底层会创建一个List集合.并把所有的数据添加到List集合中.
Set<String> set1 = lst1.stream().filter(x->x.startsWith("SZ")).collect(Collectors.toSet());
System.out.println(set1);
//toMap
// collect方法只能获取到流中剩余的每一个数据.
//在底层不能创建容器,也不能把数据添加到容器当中
//Collectors.toMap 创建一个map集合并将数据添加到集合当中
// s 依次表示流中的每一个数据
ArrayList<String> list = new ArrayList<>();
list.add("zhangsan,23");
list.add("lisi,24");
list.add("wangwu,25");
Map<String, Integer> map = list.stream().filter(
s -> {
String[] split = s.split(",");
int age = Integer.parseInt(split[1]);
return age >= 24;
}
).collect(Collectors.toMap(
//第一个lambda表达式就是如何获取到Map中的键
//第二个lambda表达式就是如何获取Map中的值
s -> s.split(",")[0],
s -> Integer.parseInt(s.split(",")[1]) ));
System.out.println(map);
六、方法引用
1、介绍
- 概述
方法引用就是指直接使用已经存在的方法,当作函数式接口中抽象方法的方法体。(函数式接口就类似于C里的函数指针,而方法引用就是函数指针指向的具体函数实现) - 使用要求
- 1.需要有函数式接口(FunctionalInterface)
- 2.被引用的方法必须已经存在
- 3.被引用方法的形参和返回值需要跟抽象方法保持一致
- 4.被引用方法的功能要满足当前需求
- 方法引用符
:: 该符号为引用运算符,而它所在的表达式被称为方法引用 - 案例说明
public class FunctionDemo { public static void main(String[] args) { //需求:创建一个数组,进行倒序排列 Integer[] arr = {3, 5, 4, 1, 6, 2}; /** * 实现要点,使用数字自带的Arrays.sort方法进行排序 * public static <T> void sort(T[] a, Comparator<? super T> c) { * } */ /** * 实现方式 */ //1. 匿名内部类 Arrays.sort(arr, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } }); System.out.println(Arrays.toString(arr)); //2.lambda表达式 Arrays.sort(arr, (Integer o1, Integer o2) -> {return o2 - o1;}); System.out.println(Arrays.toString(arr)); //3.简化lambda表达式 Arrays.sort(arr, (o1, o2) -> o2 - o1); System.out.println(Arrays.toString(arr)); /**4.方法引用 * - 需要有函数式接口(FunctionalInterface) * - 被引用的方法必须已经存在 * - 被引用方法的形参和返回值需要跟抽象方法保持一致 * - 被引用方法的功能要满足当前需求 */ Arrays.sort(arr, FunctionDemo::compareTo); System.out.println(Arrays.toString(arr)); } public static Integer compareTo(Integer o1, Integer o2) { return o2 - o1; } }
2、方法引用的分类
-
2.1 引用静态方法
- 格式
类名::静态方法 - 范例
Integer::parseInt - 案例说明
/* 需求: 集合中有以下数字,要求把他们都变成int类型 "1","2","3","4","5" */ ArrayList<String> list = new ArrayList<>(); Collections.addAll(list, "1","2","3","4","5"); //1.使用匿名内部类 list.stream().map(new Function<String, Integer>() { @Override public Integer apply(String x) { return Integer.parseInt(x); } }).forEach(x->System.out.println(x)); /* 2.使用方法引用(引用静态方法) * public static int parseInt(String s) */ list.stream().map(Integer::parseInt).forEach(x->System.out.println(x));
- 格式
-
2.2 引用成员方法
引用其他类的成员方法 引用本类的成员方法 引用父类的成员方法 格式 其他类对象::方法名 this::方法名 super::方法名 范例 FunctionDemo demo = new FunctionDemo; demo::FunctionDemo; ==> new FunctionDemo::operatorFunc this::operatorFunc super::operatorFunc 注意事项 – 类的静态方法是没有this的,如果要在静态方法中使用引用本类的成员方法,需要创建本类的对象 类的静态方法是没有super的,如果要在静态方法中使用引用本类的成员方法,需要创建本类的对象 -
2.3 引用构造方法
- 格式
类名::new - 目的
创建这个类的对象 - 范例
//Collectors.toList()方法的实现就是通过引用构造方法的形式进行对象创建 Collector<T, ?, List<T>> toList() { return new CollectorImpl<>(ArrayList::new, List::add, (left, right) -> { left.addAll(right); return left; }, CH_ID); } //说明:CollectorImpl类中的构造方法第一个参数是Supplier<A>,而Supplier<A>是一个函数式接口,它的抽象方法是没有参数,返回一个泛型类型A对象,而构造函数恰恰就是返回一个对象的。 CollectorImpl(Supplier<A> supplier, BiConsumer<A, T> accumulator, BinaryOperator<A> combiner, Set<Characteristics> characteristics); @FunctionalInterface public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); }
- 格式
-
2.4 其他调用方式
- 使用类名引用成员方法
- 格式
类名::成员方法 - 范例
String::toUpperCase - 使用要求
- 需要有函数式接口
- 被引用的方法必须已经存在
- 被引用方法的形参,需要跟抽象方法的第二个形参到最后一个形参保持一致,返回值需要保持一致。
- 抽象方法形参的详解:
- 第一个参数:表示被引用方法的调用者,决定了可以引用哪些类中的方法。例如:在Stream流当中,第一个参数一般都表示流里面的每一个数据。假设流里面的数据是String类型,那么使用这种方式进行方法引用,只能引用String这个类中的方法。
- 第二个参数到最后一个参数:跟被引用方法的形参保持一致,如果没有第二个参数,说明被引用的方法需要是无参的成员方法。
- 代码示例
//1.创建集合对象 ArrayList<String> list = new ArrayList<>(); //2.添加数据 Collections.addAll(list, "aaa", "bbb", "ccc", "ddd"); //3.变成大写后进行输出 //拿着流里面的每一个数据,去调用String类中的toUpperCase方法,方法的返回值就是转换之后的结果。 /** * 1. toUpperCase函数定义:public String toUpperCase(); * 2. map的抽象函数定义:public interface Function<T, R> { R apply(T t);} * 3. 抽象方法中的第一个参数T类型对应流中的String类型,则引用处只能使用String类的成员方法;抽象方法没有第二个参数,并且 String::toUpperCase为无参的成员方法,因此可以使用类的成员方法作为引用 */ list.stream().map(String::toUpperCase).forEach(s -> System.out.println(s));
- 抽象方法形参的详解:
- 被引用方法的功能需要满足当前的需求
- 局限性
- 不能引用所有类中的成员方法。具体可以引用的类,跟抽象方法的第一个参数有关,这个参数是什么类型的,那么就只能引用这个类中的方法。
- 格式
- 引用数组的构造方法
- 格式
数据类型[]::new - 范例
int[]::new - 目的
创建数组 - 注意事项
数组的类型需要与流中的数据类型保持一致 - 代码示例
//1.创建集合并添加元素 ArrayList<Integer> list = new ArrayList<>(); Collections.addAll(list, 1, 2, 3, 4, 5); //2.收集到数组当中 /*Integer[] arr = list.stream().toArray(new IntFunction<Integer[]>() { @Override public Integer[] apply(int value) { return new Integer[value]; } });*/ Integer[] arr2 = list.stream().toArray(Integer[]::new);
- 格式
- 使用类名引用成员方法
3、方法引用使用技巧
- 思考有没有满足需求的现有方法;
- 如果存在这样的方法,则思考该方法是否满足方法引用的规则,是否满足哪类方法引用。
- 引用静态方法
- 引用成员方法
- 引用构造方法
4、匿名内部类、Lambda表达式、方法引用比较
- 4.1、匿名内部类可进化为方法引用;
- 4.2、匿名内部类可进化为lambda;
- 4.3、lambda可进化为方法引用;
- 4.4、方法引用为最简洁的写法,其次lambda,最后是匿名内部类。
- 4.5、方法引用常见搭配流使用,尤其流处理中使用map进行映射处理时,适合使用方法引用。