虽然从java8就开始支持lambda特性,如今java更是已经升级了11等更高级的版本,但是本人一直使用的还是java8以下的语法,对于lambda语法更是甚少了解,刚开始接触这个lambda还是十分不习惯的,感觉有点绕;怎么看怎么不顺眼,在我看来,lambada特性单纯只是为了简化java代码的书写的,所以实质上并没有什么用,甚至自己一度觉得很讨厌,但作为Android开发者,为了更好的学习kotlin,也只能迎着头皮接受这个自己讨厌的东西;(获取将来某一天会觉得他很香吧)开始学习java 8的lambda表达式。以下是针对一些教程以及本身对lambda的一些理解所作的作结,写的不好,还请大家指正;
学习的时候参考了很多资料,学着学着就越来越懵逼了,可能是看的资料太多反倒产生了影响。学完又忘记了。额,感觉自己在写日记了。丢~~~
lambda表达式是什么?有什么作用?什么时候可以使用lambda表达式?
按照官方说法:lambda表达式就是一个匿名函数,也就是没有函数名的函数。什么叫没有函数名?
//定义接口
public interface IUser {
String getUserName(String name);
}
public void test() {
//正常情况下的接口实现方法
IUser user = new IUser() {
@Override
public String getUserName(String name) {
return name;
}
};
system.out.print(user.getUserName("达文西"))
}
上面的new IUser{...}
就是一个匿名函数,所谓匿名,就是我完全不需要知道你的名字,我也不关心,我真正关心的只是你的内容,你的实现方法;故此,如果按照以往的写法,如果每一个匿名函数我都需要new xxx
那这些没用的代码就现的很多余了,而用lambda表达式我便可以省略这些没有用的内容,就一句IUser user=name->name
,简单!!!。所以简单来说lambda表示是为了简化java书写冗余的。如果你觉得没有必要,那是真的就没有必要-_-!!。
那什么情况下才能使用lambda表达式呢?那就是只有当你的接口符合函数式接口的时候?什么是函数式接口?只有一个抽象方法的接口就是函数式接口。
上面的 IUser
接口便是函数式接口;
再举个例子
@FunctionalInterface
public interface Comparator<T>{
...
int compare(T o1,T o2);
}
其中的@FunctionalInterface
是用来修饰函数式接口的,也就是说只要有这个修饰符,你的接口就必须有且仅有一个接口。如果你用了这个修饰符,还没有来得及写方法,那他会报No target method found
的错误,如果你的抽象方法不止一个,那就会报Multiple non-overriding abstract methods found in interface xxx
lambda表达式语法
(参数)->{实现方法}
小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
-> 是新引入的语法格式,是运算符,代表指向动作。
大括号内的语法与传统方法体要求基本一致。
有时候()和{}甚至可以省略,比如上面提到的IUser user=name->name
接口的实现;
//完整的lambda的语法
IUser user0 = (String name) -> {
return name;
};
//省略{}
IUser user2 = (String uname) -> uname;
//省略()和{}
IUser user = name -> null;
总结表达式一些总结:
- 当方法体只有一行的时候,花括号{}可以省略;
- 当接口方法有返回值,且方法体只有一行的时候,省略花括号{}的同时可省略return,直接写返回的表达式/值即可,单行代码的执行结果会自动返回;
- 当无参数的时候,()不可以省略;
- 当参数只有一个的时候,()可以省略;
- 当参数的类型确定的时候,可以省略参数的类型;jvm在运行时,会自动根据绑定的抽象方法中的参数进行推导;
//通常使用
Supplier<String> sup1 = new Supplier<String>() {
@Override
public String get() {
return "达文西";
}
};
//lambda表达式
Supplier<String> sup2 = ()-> "达文西";
JAVA内置的4大核心函数式接口
- 消费型接口
ConSumer void accept(T t)
典型的代表就是list集合的使用
private static void test4() {
String[] strs = new String[]{"1", "2", "3", "4", "5"};
List<String> list = Arrays.asList(strs);
//正常情况遍历
for (String str : list) {
System.out.print(str + ";");
}
//使用lambda表达式进行遍历,其中forEach方法接收的就是一个消费型接口;
list.forEach(str -> System.out.println(str + ";"));
}
- 供给型接口
Supplier T get()
- 函数型接口
Function<T,R> R apply(T t)
public static void test5() {
//传统方式
Runnable run1 = new Runnable() {
@Override
public void run() {
System.out.println("传统方式的Runnable");
}
};
Thread t1 = new Thread(run1);
t1.start();
//使用lambda表达式
Thread t2 = new Thread(() -> System.out.println("----------lambda方式----------------"));
t2.start();
}
- 断定型接口
Predicate boolean test(T t)
下面展示的是Java类型系统内部的函数式接口
public static void test6() {
/* Java8中的java.util.function包中提供了一些常用的函数式功能接口*/
// 1.java.util.function.Predicate<T>
//接受参数T,返回一个Boolean类型的结果
Predicate<String> predicate = String::isEmpty;
boolean flag = predicate.test("");
System.out.println(flag);
// 2.java.util.function.Consumer<T>
// 接收一个参数T,不返回结果
Consumer<String> consumer = System.out::println;
consumer.accept("哈哈哈");
// 3.java.util.function.Function<T,R>
// 接收参数对象T,返回结果对象R
Function<String, Integer> function = (String userName) -> {
return userName.equals("admin") ? 1 : 0;
};
int funflag = function.apply("admin");
// 4.java.util.function.Supplier<T>
// 不接收参数,返回T对象创建的工厂
Supplier<String> supplier = () -> UUID.randomUUID().toString();
String s = supplier.get();
// 5.java.util.UnaryOperator<T>
// 接收参数对象T,返回结果参数对象T
UnaryOperator<String> operator = (String userName) -> "Hello " + userName;
//6.java.util.BinaryOperator<T>
// 接收两个T对象,返回一个T对象结果
BinaryOperator<String> binary = (String userName, String userId) -> {
return userName + ":" + userId;
};
}
静态方法的引用
这个怎么解释呢?我的理解就是,使用一个静态的方法去替代你需要实现的接口方法,当然前提是这个静态方法必须跟你的接口方法是一样的(即请求参数以及返回值一致)
例如:
//工具类Utils中含有一个静态类
public class Utils {
public static final int compar(Integer p1, Integer p2) {
return p1 - p2;
}
}
//调用
List<Integer> list = Arrays.asList(33, 44, 55, 33, 22);
Collections.sort(list, Utils::compar2);
其实就相当于你在你的实现类中,调用了外部的静态方法类,且把参数传递过去之后获取到返回值进行返回(即没有对参数进行任何加工就给第三方去处理了。外包出去了....):
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Utils.compar(o1, o2);
}
});
上述的调用方法有一个专业的名字叫【方法应用】,直接使用符号 :: (两个冒号)。
除了上述的静态方法的引用之外,还有以下常见的几种引用方式:
1. 对象引用成员方法
对象名::方法名
2. 类名引用静态方法
类名::静态方法
3. 类的构造器引用
类的构造器(类似于构造方法)引用要在创建对象的时候使用。
类名::new
4. 数组的构造器引用
数组的构造器引用在创建数组的时候使用。
数据类型[]::new
Stream概念的引入(要去做核酸了,今天先到这里)
在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
让我先来看两个例子:
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("黄毛");list1.add("黄文名");list1.add("黄文雄");list1.add("黄文离");list1.add("黄文金");list1.add("叶小平");list1.add("黄狗");
System.out.println("------------方式1-------------");
List<String> list2 = new ArrayList<>();
list1.forEach(name -> {
if (name.startsWith("黄")) {
list2.add(name);
}
});
ArrayList<String> list3 = new ArrayList<>();
list2.forEach(name -> {
if (name.length() == 3) {
list3.add(name);
}
});
list3.forEach(name -> {
System.out.println("符合条件的姓名:" + name);
});
System.out.println("------------方式2-------------");
list1.stream().filter(name -> name.startsWith("黄")).filter(name -> name.length() == 3).forEach(name -> System.out.println("符合条件的姓名:" + name));
}
乍一看方式1跟方式2的结果是一样的,都是想要遍历出带有三个字的姓黄的名字。但是很显然第2种方式更为简洁,就一句话到底。这就是流式思想.
获取流的方式
java.util.stream.Stream 是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)在 Java 8 中, 集合接口有两个方法来生成流: ① stream() − 为集合创建串行流。 ② parallelStream() − 为集合创建并行流。
- 通过Collection(单列)集合获取流
Stream stream(): 获取一个流对象 - 通过Map(双列)集合获取流
通过Map集合获取流(了解),Map集合不能直接获取流对象, 只能间接获取, 有三种间接获取方式。
①. 先获取Map集合中的所有的key, 然后获取所有key的stream流
②. 先获取Map集合中的所有的value,然后获取所有value的stream流
③. 先获取Map集合中的所有的Entry(键值对), 获取所有entry的stream流。 - 通过数组获取流
方式一:使用Stream的静态方法of完成(推荐, 因为参数不仅可以传递数组, 也可以传递任意个数据)
static Stream of(T… values): 根据一个数组获取一个stream流
方式二: 使用Arrays的静态方法stream完成
static Stream stream(T[] array) : 根据一个数组获取对应的stream流
流式布局常见用法:
方法名称 | 方法作用 | 方法种类 | 是否支持链式调用 |
---|---|---|---|
forEach | 逐一处理 | 终结方法 | NO |
count | 获取个数 | 终结方法 | NO |
filter | 过滤 | 非终结方法 | YES |
limit | 获取前几个 | 非终结方法 | YES |
skip | 跳过前几个 | 非终结方法 | YES |
concat | 合并 | 非终结方法 | YES |
map | 映射 | 非终结方法 | YES |
Stream中用于收集的方法:
1. 收集到集合中:
R collect(Collector collector):可以将流中的数据收集到集合。 参数Collector表示收集到哪种集合。Collector是一个接口,如果要传递需要传递Collector实现类对象, 这个实现类对象不能由我们自己去new,要通过Collectors工具类的静态方法进行获取, 使用不同方法获取的Collector,表示收集到了不同的集合中。
static Collector toList():如果使用toList获取的Collector对象,表示将数据收集到List集合中。
static Collector toSet()::如果使用toSet获取的Collector对象,表示将数据收集到Set集合中。
2. stream收集到数组
我们可以使用Stream中的toArray方法进行收集
Object[] toArray(): 将流中的数据收集到数组, 返回Object[]
注意:
1. stream流不会改变源对象.Stream流中的非终结方法返回的都是Stream对象, 返回的Stream是一个新的Stream
2. Stream流只能一次性使用,不能多次操作,否则会报错。
3. stream流本身不保存任何元素.便于理解可以把流看成容器,但是它不是容器.流本身不保存任何元素,他保存的是一些的中间操作步骤.
4. stream流操作是延迟执行的.当调用终结方法时,会按顺序将操作步骤执行.
参考资料:
- https://www.runoob.com/java/java8-lambda-expressions.html
- https://blog.csdn.net/qq_29660549/article/details/106564964
- https://blog.csdn.net/weixin_45735355/article/details/119911204
- https://blog.csdn.net/fengkuagn123/article/details/106315317
- 关于stream的用法
- https://blog.csdn.net/weixin_45590174/article/details/103542176
网友评论