Lambda表达式
有以下代码:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程中执行的代码"+Thread.currentThread().getName());
}
}).start();
代码分析:
- Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的核心;
- 为了指定run方法体,不得不需要Runnable的实现类;
- 为了省去定义一个Runnable 的实现类,不得不使用匿名内部类;
- 必须覆盖重写抽象的run方法,所有的方法名称,方法参数,方法返回值不得不都重写一遍,而且不能出错;
- 而实际上,我们只在乎方法体中的代码。
上面代码可以写成lambda形式如下:
new Thread(()->{
System.out.println("lambda表达式新线程"+Thread.currentThread().getName());
}).start();
Lambda表达式是一个匿名函数,可以理解为一段可以传递的代码。
lambda运行就是将函数作为一个参数传递到方法中。用以简化接口的使用,让代码更加简洁。
lambda的基础语法:
(参数类型 参数1,参数类型 参数2,...)->{
代码体;
}
格式1:无参,无返回值
public interface UserService {
void show();
}
public class Demo02Lambda {
public static void main(String[] args) {
goShow(new UserService() {
@Override
public void show() {
System.out.println("show方法执行了");
}
});
goShow(()->{
System.out.println("lambda show方法执行了");
});
}
public static void goShow(UserService userService){
userService.show();
}
}
格式2:有参且有返回值的
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
private Integer height;
}
public class Demo03Lambda {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("zans",12,156));
list.add(new Person("lis",13,170));
list.add(new Person("ww",21,168));
list.add(new Person("zl",32,163));
// Collections.sort(list, new Comparator<Person>() {
// @Override
// public int compare(Person o1, Person o2) {
// return o1.getAge()-o2.getAge();
// }
// });
Collections.sort(list,((o1, o2) -> {
return o1.getAge()-o2.getAge();
}));
for (Person person : list) {
System.out.println(person);
}
}
}
在lambda表达式的标准写法基础上,可以使用省略写法的规则为:
- 小括号内的参数类型可以省略;
- 如果小括号内有且仅有一个参数,则小括号可以省略;
- 如果大括号内有且仅有一个语句,可以同时省略大括号,return关键字及语句分号。
例如下面例子:
public interface OrderService {
String show(String name);
}
public interface StudentService {
String show(String name,Integer age);
}
public class Demo04Lambda {
public static void main(String[] args) {
goStudent((String name,Integer age)->{
return name+age+"666";
});
//省略写法
goStudent((name,age)->name+age+"123");
goOrder((String name)->{
return name+"333";
});
//省略写法
goOrder(name->name+"123");
}
public static void goStudent(StudentService studentService){
studentService.show("sja",22);
}
public static void goOrder(OrderService orderService){
orderService.show("lili");
}
}
Lambda表达式的使用前提
Lambda表达式的语法是非常简洁的,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意:
1.方法的参数或局部变量类型必须为接口才能使用Lambda
2.接口中有且仅有一个抽象方法(@Functionallnterface)
//@FunctionalInterface:被该注解修饰的接口只能有一个抽象方法
@FunctionalInterface
public interface UserService {
void show();
}
Lambda和匿名内部类的对比
1.所需类型不—样
- 匿名内部类的类型可以是类,抽象类,接口Lambda表达式需要的类型必须是接口
2.抽象方法的数量不—样
- 匿名内部类所需的接口中的抽象方法的数量是随意的,Lambda表达式所需的接口中只能有一个抽象方法
3.实现原理不—样
- 匿名内部类是在编译后形成—个class
- Lambda表达式是在程序运行的时候动忝生成class
函数式接口
如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。
我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。
Lambda表达式的本质:作为函数式接口的实例
Java8中关于Lambda表达式提供的4个基本的函数式接口(作用:在写lambda表达式的时候没有必要自己声明接口):
Consumer
Consumer(有参无返回值的接口),对于lambda表达式需要提供一个返回数据的类型,是用来消
费数据的,使用时需要指定一个泛型来定义参数类型。源码如下:
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
使用:
public class ConsumerText {
public static void main(String[] args) {
test(msg->{
System.out.println(msg+"->转换为小写"+msg.toLowerCase());
});
}
public static void test(Consumer<String> consumer){
consumer.accept("Hello Word");
}
}
默认方法:andThen
如果一个方法的参数和返回值全部是Consumer类型,那么就可以实现效果,消费一个数据的时
候,首先做一个操作,然后再做一个操作,实现组合,而这个方法就是Consumer接口中的default
方法andThen方法。
public class ConsumerText {
public static void main(String[] args) {
test(msg1->{
System.out.println(msg1+"->转换为小写"+msg1.toLowerCase());
},msg2->{
System.out.println(msg2+"->转换为小写"+msg2.toUpperCase());
});
}
public static void test(Consumer<String> c1,Consumer<String> c2){
String str = "Hello Word";
// c1.accept("Hello Word");
// c2.accept("Hello Word");
c1.andThen(c2).accept(str);
c2.andThen(c1).accept(str);
}
}
Function
Function(有参有返回值接口),Function接口是根据一个类型的数据得到另一个类型的数据,前
者称为前置条件,后者称为后置条件。源码如下:
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
使用:传递一个字符串,返回一个数字
public class FunctionTest {
public static void main(String[] args) {
test(msg->{
return Integer.parseInt(msg);
});
}
public static void test(Function<String,Integer> function){
Integer apply = function.apply("666");
System.out.println("apply="+apply);
}
}
默认方法:andThen,也是用来进行组合操作,compose就是andthen反过来的效果,而静态方法
identity则是输入什么参数就返回什么参数 。
public class FunctionTest {
public static void main(String[] args) {
test(msg->{
return Integer.parseInt(msg);
},msg2->{
return msg2 * 10;
});
}
public static void test(Function<String,Integer> function1,Function<Integer,Integer> function2){
Integer i2 = function1.andThen(function2).apply("88");
Integer i2 = function2.compose(function1).apply("88");
System.out.println("i2"+i2);
}
}
Supplier
Supplier(无参有返回值类型,是用来生产数据的)
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
使用:
import java.util.Arrays;
import java.util.function.Supplier;
public class SupplierTest {
public static void main(String[] args) {
fun1(()->{
int arr[]={22,11,56,42,99,8};
//计算出数组中的最大值
Arrays.sort(arr);
return arr[arr.length-1];
});
}
private static void fun1(Supplier<Integer> supplier){
Integer max = supplier.get();
System.out.println("max="+max);
}
}
Predicate
Predicate(有参,返回值为Boolean型接口)
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
使用:如果msg长度大于3,返回true
public class predicateTest {
public static void main(String[] args) {
test(msg->{
return msg.length()>3;
},"hahahha");
}
public static void test(Predicate<String> predicate,String msg){
boolean b = predicate.test(msg);
System.out.println("b"+b);
}
}
在Predicate中的默认方法提供了逻辑关系操作 and or negate isEquals方法
public class predicateTest {
public static void main(String[] args) {
test(msg1->{
return msg1.contains("H");
},msg2->{
return msg2.contains("W");
});
}
public static void test(Predicate<String> p1,Predicate<String> p2){
//P1包含H,同时p2包含w
boolean pp1 = p1.and(p2).test("Hello");
//P1包含H,或者p2包含w
boolean pp2 = p1.or(p2).test("Hello");
//p1不包含H
boolean pp3 = p1.negate().test("Hello");
}
}
方法引用
在Lambda表达式中要执行的代码和我们另一个方法中的代码是一样的,这时就没有必要重写一份
逻辑了,这时我们就可以“引用”重复代码。
public class FunctionRefTest02 {
public static void main(String[] args) {
printMax(FunctionRefTest02::getTotal);
}
public static void getTotal(int a[]){
int sum=0;
for (int i : a) {
sum+=i;
}
System.out.println("数组织和:"+sum);
}
private static void printMax(Consumer<int[]> consumer){
int[] a = {10,20,30,40,50};
consumer.accept(a);
}
}
符号表示 ::
应用场景:如果Lambda表达式所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方法引用。
常见的引用方式:
方法引用在JDK8中使用是相当灵活的,有以下几种形式:
1. instanceName::methodName 对象::方法名
2. ClassName::staticMethodName 类名::静态方法
3. ClassName::methodName 类名::普通方法
4. ClassName::new 类名::new 调用的构造器
5. TypeName[]::new String[]::new 调用数组的构造器
方法引用的注意事项:
1. 被引用的方法,参数要和接口中的抽象方法的参数一样
2. 当接口抽象方法有返回值时,被引用的方法也必须有返回值
对象::方法名
这是最常见的一种用法。如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法
public class FunctionRefTest3 {
public static void main(String[] args) {
Date now = new Date();
Supplier<Long> supplier = ()->{return now.getTime();};
System.out.println(supplier.get());
//通过方法引用
Supplier<Long> supplier1 =now::getTime;
System.out.println(supplier1.get());
}
}
类名::静态方法名
public class FunctionRefTest4 {
public static void main(String[] args) {
Supplier<Long> supplier1 = ()->{
return System.currentTimeMillis();
};
System.out.println(supplier1.get());
//通过方法引用
Supplier<Long> supplier2 = System::currentTimeMillis;
System.out.println(supplier2.get());
}
}
类名::引用实例方法
Java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是拿第一个参数
作为方法的调用者。
public class FunctionRefTest5 {
public static void main(String[] args) {
Function<String,Integer> function=(s)->{
return s.length();
};
System.out.println(function.apply("hello"));
//通过方法引用实现
Function<String,Integer> function2=String::length;
System.out.println(function2.apply("hahahha"));
BiFunction<String,Integer,String > function3 = String::substring;
System.out.println(function3.apply("hhhhhhhh", 3));
}
}
类名::构造器
由于构造器的名称和类名完全一致,所以构造器引用使用 ::new 的格式使用
public class FunctionRefTest6 {
public static void main(String[] args) {
Supplier<Person> person1 = () -> {
return new Person();
};
System.out.println(person1.get());
//方法引用
Supplier<Person> person2 = Person::new;
System.out.println(person2.get());
BiFunction<String,Integer,Person> function = Person::new;
System.out.println(function.apply("zs", 22));
}
}
数组::构造器
public class FunctionRefTest7 {
public static void main(String[] args) {
Function<Integer,String[]> fun = (len)->{
return new String[len];
};
String[] strings = fun.apply(3);
System.out.println("数组的长度:"+strings.length);
//方法引用,调用数组的构造器
Function<Integer,String[]> fun1 = String[]::new;
String[] strings1 = fun1.apply(5);
System.out.println("数组的长度:"+strings1.length);
}
}
Stream API
当我们在需要对集合中的元素进行操作的时候,除了必需的添加,删除,获取外,最典型的操作就
是集合遍历。针对与我们不同的需求总是一次次的循环,这时我们希望有更加高效的处理方式,就
可以通过JDK8中提供的Stream API来解决这个问题。
public class FunctionRefTest8 {
public static void main(String[] args) {
//定义一个集合
List<String> list = Arrays.asList("张三","张三丰","成龙","周星驰");
// 1.获取所有 姓张的信息
// 2.获取名称长度为3的用户
// 3. 输出所有的用户信息
list.stream().filter(s->s.startsWith("张"))
.filter(s->s.length()==3)
.forEach(System.out::println);
}
}
注意:Stream和IO流(InputStream/OutputStream)没有任何关系。
Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是
对数据进行加工处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一
个原材料加工成一个商品。
Stream流的获取方式
根据conllection获取
首先,java.util.Collection 接口中加入了default方法 stream,也就是说Collection接口下的所有的实
现都可以通过steam方法来获取Stream流。
public class streamTest1 {
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
list.stream();
HashSet<Object> set = new HashSet<>();
set.stream();
Vector<Object> vector = new Vector<>();
vector.stream();
}
}
但是Map接口别没有实现Collection接口,那这时怎么办呢?这时我们可以根据Map获取对应的key
value的集合。
public class streamTest1 {
public static void main(String[] args) {
HashMap<Object, Object> map = new HashMap<>();
Stream<Object> stream = map.keySet().stream();
Stream<Object> stream1 = map.values().stream();
Stream<Map.Entry<Object, Object>> stream2 = map.entrySet().stream();
}
}
通过stream.of方法获取
在实际开发中我们不可避免的还是会操作到数组中的数据,由于数组对象不可能添加默认方法,所
有Stream接口中提供了静态方法of。
public class streamTest1 {
public static void main(String[] args) {
Stream<String> a = Stream.of("a", "b", "c");
String[] a1 = {"aa","bb","cc"};
Stream<String> a11 = Stream.of(a1);
Integer[] c1 = {1,2,3,4};
Stream<Integer> c11 = Stream.of(c1);
//基本数据类型不能获取stream
int[] d1 = {1,2,3,4};
Stream.of(d1).forEach(System.out::println);
}
}
Stream 常用方法介绍
终结方法:返回值类型不再是 Stream 类型的方法,不再支持链式调用。本小节中,终结方法包括 count 和forEach 方法。
非终结方法:返回值类型仍然是 Stream 类型的方法,支持链式调用(除了终结方法外,其余方法均为非终结方法)。
Stream注意事项(重要):
- Stream只能操作一次
- Stream方法返回的是新的流
- Stream不调用终结方法,中间的操作不会执行
foreach
forEach用来遍历流中的数据,该方法接收一个Consumer接口,会将一个流元素交给函数处理
void forEach(Consumer<? super T> action);
public class streamTest1 {
public static void main(String[] args) {
Stream.of("a","s","d","f").forEach(System.out::println);
}
}
count
tream流中的count方法用来统计其中的元素个数
long count();
public class streamTest1 {
public static void main(String[] args) {
long count = Stream.of("a", "s", "d", "f").count();
System.out.println(count);
}
}
filter
filter方法的作用是用来过滤数据的。返回符合条件的数据
Stream<T> filter(Predicate<? super T> predicate);
public class streamTest1 {
public static void main(String[] args) {
Stream.of("ab","scd" ,"sz2", "ded", "f2")
.filter(s->s.length()>2)
.filter(s->s.startsWith("s"))
.filter(s->s.contains("z")).forEach(System.out::println);
}
}
limit
Stream<T> limit(long maxSize);
public static void main(String[] args) {
Stream.of("a1", "a2", "a3","bb","cc","aa","dd")
.limit(3)
.forEach(System.out::println);
}
skip
如果希望跳过前面几个元素,可以使用skip方法获取一个截取之后的新流:
Stream<T> skip(long n);
public static void main(String[] args) {
Stream.of("a1", "a2", "a3","bb","cc","aa","dd")
.skip(3)
.forEach(System.out::println);
}
map
如果我们需要将流中的元素映射到另一个流中,可以使用map方法:
Stream<R> map(Function super T, ? extends R> mapper);
public class streamTest1 {
public static void main(String[] args) {
Stream.of("1","2","3","4","5")
// .map(msg->Integer.parseInt(msg))
.map(Integer::parseInt)
.forEach(System.out::println);
}
}
sorted
Stream<T> sorted();
public class streamTest1 {
public static void main(String[] args) {
Stream.of("1","2" ,"3","4","5")
.map(Integer::parseInt)
// .sorted()//自然排序
.sorted((o1,o2)->o2-o1)//根据比较器指定排序规则(倒序)
.forEach(System.out::println);
}
}
distinct
Stream<T> distinct();
public class streamTest1 {
public static void main(String[] args) {
Stream.of("1","2" ,"2","4","1")
.distinct()
.forEach(System.out::println);
Stream.of(
new Person("zs",21)
,new Person("lis",21)
,new Person("zs",21)
).distinct().forEach(System.out::println);
}
}
Stream流中的distinct方法对于基本数据类型是可以直接去重的,但是对于自定义类型,我们是需
要重写hashCode和equals方法来移除重复元素。
match
如果需要判断数据是否匹配指定的条件,可以使用match相关的方法
boolean anyMatch(Predicate super T> predicate); // 元素是否有任意一个满足条件
boolean allMatch(Predicate super T> predicate); // 元素是否都满足条件
boolean noneMatch(Predicate super T> predicate); // 元素是否都不满足条件
public class streamTest1 {
public static void main(String[] args) {
boolean b = Stream.of("1", "4", "5", "3", "6")
.map(Integer::parseInt)
// .anyMatch(s->s>3)
// .allMatch(s->s>0)
.noneMatch(s -> s > 7);
System.out.println(b);
}
}
find
Optional<T> findFirst();
Optional<T> findAny();
public class streamTest1 {
public static void main(String[] args) {
Optional<String> first = Stream.of("1", "4", "5", "3", "6")
.findFirst();
System.out.println(first.get());
Optional<String> any = Stream.of("1", "4", "5", "3", "6").findAny();
System.out.println(any.get());
}
}
max和min
Optional<T> min(Comparator super T> comparator);
Optional<T> max(Comparator super T> comparator);
public class streamTest1 {
public static void main(String[] args) {
Optional<Integer> max = Stream.of("1", "4", "5", "3", "6")
.map(Integer::parseInt)
.max((o1, o2) -> o1 - o2);
System.out.println(max.get());
Optional<Integer> min = Stream.of("1", "4", "5", "3", "6")
.map(Integer::parseInt).min((o1, o2) -> o1 - o2);
System.out.println(min.get());
}
}
reduce
归并计算,为终端操作,可以对中间操作筛选出来的流数据作出一系列的加减乘除等操作。
T reduce(T identity, BinaryOperator<T> accumulator);
public class streamTest1 {
public static void main(String[] args) {
Integer reduce = Stream.of("1", "4", "5", "3", "6")
.map(Integer::parseInt)
.reduce(0, Integer::sum);
System.out.println(reduce);
Integer reduce1 = Stream.of("1", "4", "5", "3", "6")
.map(Integer::parseInt)
.reduce(0, (x, y) -> x > y ? x : y);
System.out.println(reduce1);
}
}
reduce和map组合
public class streamTest1 {
public static void main(String[] args) {
// 1.求出所有年龄的总和
Optional<Integer> reduce = Stream.of(
new Person("zs", 12)
, new Person("zs", 23)
, new Person("zs", 25)
).map(Person::getAge).reduce(Integer::sum);
System.out.println(reduce.get());
// 1.求出所有年龄的最大值
Optional<Integer> reduce1 = Stream.of(
new Person("zs", 12)
, new Person("zs", 23)
, new Person("zs", 25)
).map(Person::getAge).reduce(Math::max);
System.out.println(reduce1.get());
//统计字符a出现的次数
Integer total = Stream.of("a", "a", "a", "b", "b", "c").map(s -> "a".equals(s) ? 1 : 0).reduce(0, Integer::sum);
System.out.println(total);
}
}
concat
public class streamTest1 {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("1", "2", "3");
Stream<String> stream2 = Stream.of("4", "5", "6");
Stream.concat(stream1,stream2).forEach(System.out::println);
}
}
mapToInt
如果需要将Stream中的Integer类型转换成int类型,可以使用mapToInt方法来实现
public class streamTest1 {
public static void main(String[] args) {
Integer arr[] = {1,2,3,4,5};
IntStream toInt = Stream.of(arr).mapToInt(Integer::intValue);
System.out.println(toInt);
}
}