Java中的Lambda表达式详细解析!详细解析Collection和Map中的表达式的使用方式

举报
攻城狮Chova 发表于 2022/05/30 20:18:08 2022/05/30
【摘要】 本篇文章中对Java中的Lambda表达式的使用进行详细的分析说明。从介绍Lambda表达式开始,分析了Java中的变量作用域和匿名内部类。然后通过一个个具体的使用实例说明了Java中的Lambda表达式的具体使用方式。通过这篇文章,基本上能够熟练掌握Java Lambda的具体使用方式。

Lambda表达式

  • JVM内部是通过invokedynamic指令来实现Lambda表达式的
  • Lambda中允许将一个函数作为方法的参数,即函数作为参数传递进方法中
  • 使用Lambda表达式可以使代码更加简洁

变量作用域

  • Lambda表达式只能引用标记了final的外层局部变量.即不能在Lambda表达式内部修改定义在作用域外的局部变量,否则会导致报错
  • Lambda表达式中可以直接访问外层的局部变量
  • Lambda表达式中外层局部变量可以不用声明为final, 但是必须不可被后面的代码修改,即隐性地具有final的语义
  • Lambda表达式中不允许声明一个与外层局部变量同名的参数或者局部变量

使用示例

匿名内部类

  • 匿名内部类: 匿名内部类仍然是一个类,不需要指定类名,编译器会自动为该类取名
    • Java中的匿名内部类:
    public class MainAnonymousClass {
      public static void main(String[] args) {
      	new Thread(new Runnable(){
      		@Override
      		public void run(){
      			System.out.println("Anonymous Class Thread run()");
      		}
      	}).start();;
      }
    }
    
    • 使用Lambda表达式实现匿名内部类:
    public class MainLambda {
      public static void main(String[] args) {
      	new Thread(
      			() -> System.out.println("Lambda Thread run()")
      		).start();;
      }
    }
    

带参函数

  • 带参函数的简写:
List<String> list = Arrays.asList("I", "love", "you", "too");
Collections.sort(list, new Comparator<String>() { // 接口名
	@Override
	public int compare(String s1, String s2) { // 方法名
		if(s1 == null)
			return -1;
		if(s2 == null)
			return 1;
		return s1.length() - s2.length(); 			
	}
});
  • 上述代码通过内部类重载了Comparator接口的compare() 方法来实现比较逻辑. 采用Lambda表达式可简写如下:
List<String> list = Arrays.asList("I", "love", "you", "too");
Collections.sort(list, (s1, s2) -> { // 省略参数表类型
	if (s1 == null)
		return -1;
	if (s2 == null)
		return 1;
	return s1.length() - s2.length();
});
  • 上述代码根内部类的作用一样
  • 除了省略了接口名和方法名,代码中的参数类型也可以省略
  • 因为javac类型推断机制,编译器能够根据上下文信息推断出参数的类型

Java四大内置函数

Consumer
  • 消费型接口Consumer< T > : void accept(T t);
  • 供给型接口Supplier< T > : T get();
  • 函数式接口Function< T,R > : R apply(T t);
  • 断言型接口Predicate< T >: boolean test(T t);

Collection

forEach
  • 增强型for循环:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
for(String str : list) {
	if (str.length() > 3)
		System.out.println(str);
}
  • 使用forEach() 方法结合匿名内部类实现:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach(new Consumer<String>(){
	@Override
	public void accept(String str) {
		if (str.length() > 3) {
			System.out.println(str);
		}
	}
});
  • 使用Lambda表达式实现如下:
// 使用forEach()结合Lambda表达式迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach(str -> {
	if (str.length() > 3) {
		Systemm.out.println(str);
	}		
});

上述代码给forEach() 方法传入一个Lambda表达式,不需要知道accept() 方法,也不需要知道Consumer接口,类型推导已经完成了这些

removeIf
  • 该方法签名: boolean removeIf(Predicate<? super E> filter);
    • 删除容器中所有满足filter指定条件的元素
      • Predicate是一个函数接口,里面有一个待实现的方法boolean test(T t)
  • 如果需要在迭代过程中对容器进行删除操作必须使用迭代器, 否则会抛出ConcurrentModificationException.
  • 使用迭代器删除列表元素:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {
	if (it.next().length > 3) {
		it.remove();
	}
}
  • 使用removeIf() 方法结合匿名内部类实现:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(new Predicate<String>(){
	@Override
	public boolean test(String str) {
		return str.length() > 3;
	}
});
  • 使用removeIf结合Lambda表达式实现:
Array<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(str -> str.length() > 3);

使用Lambda表达式不需要记忆Predicate接口名,也不需要记忆test() 方法名,只需要此处需要一个返回布尔类型的Lambda表达式

replaceAll
  • 该方法签名: void replaceAll(UnaryOperator<E> operator);
    • 对每个元素执行operator指定的操作,并用操作结果来替换原来的元素
      • UnaryOperator是一个函数接口,里面有待实现的方法T apply(T t)
  • 使用下标实现元素替换:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
for (int i = 0; i < list.size(); i ++) {
	String str = list.get(i)
	if (str.length() > 3) {
		list.set(i, str.toUpperCase());
	}
}
  • 使用replaceAll结合匿名内部类实现:
ArrayList<String> list =new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.replaceAll(new UnaryOperator<>(String){
	@Override
	public String apply(String str) {
		if (str.length() > 3) {
			return str.toUpperCase();
		}
		return str;
	}
});

代码调用replaceAll() 方法,并使用匿名内部类实现UnaryOperator接口

  • 使用Lambda表达式实现:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.replaceAll(str -> {
	if (str.length > 3) {
		return str.toUpperCase();
	}
	return str;
});
sort
  • 该方法定义在List接口中,方法签名: void sort(Comparator<? super E> c);
    • 根据c指定的比较规则对容器进行排序
      • Comparator接口中需要实现接口int compare(T o1, T o2)
  • 使用Collectionssort() 方法:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
Collections.sort(list, new Comparator<String>() {
	@Override
	public int compare(String str1, String str2) {
		return str1.length() - str2.length();
	}
});
  • 直接使用List.sort() 方法,结合Lambda表达式:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.sort((str1, str2) -> str1.length() - str2.length());
spliterator
  • 该方法签名: Spliterator<E> spliterator();
    • Spliterator既可以像Iterator那样逐个迭代,也可以批量迭代,批量迭代可以降低迭代的开销
    • Spliterator是可拆分的,一个Spliterator可以通过调用Spliterator<T> trySplit() 方法来尝试分成两个.一个是this, 一个是新返回的元素.这两个迭代器代表的元素没有重叠
    • 可通过多次调用Spliterator.trySplit() 方法来分解负载,以便于多线程处理
stream和parallStream
  • Stream()parallStream() 分别返回该容器的Stream视图表示
  • parallStream() 返回并行的Stream
  • StreamJava函数式编程的核心类

Map

forEach
  • 该方法签名: void forEach(BiConsumer<? super K,? super V> action);
    • Map中的每个映射执行action操作
      • BiConsumer是一个函数接口,里面有一个待实现方法 void accept(T t, U u);
  • 使用Java 7之前的方式输出Map中所有的对应关系:
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
for (Map.Entry<Integer, String> entry : map.entrySet()) {
	system.out.println(entry.getKey() + "=" + entry.getValue());
}
  • 使用MapforEach() 方法,结合匿名内部类:
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.forEach(new BiConsumer<Integer, String>() {
	@Override
	public void accept(Integer k, String v) {
		System.out.println(k + "=" + v);
	}
});
  • 使用Lambda表达式:
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.forEach((k, v) -> System.out.println(k + "=" + v));
getOrDefault
  • 该方法签名: V getOrDefault(Object key, V defaultValue);
    • 按照给定的key查询Map中对应的value, 如果没有找到则返回defaultValue
  • 查询Map中指定键所对应的值,如果不存在则返回NoValue:
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
System.out.println(map.getOrDefault(4,"NoValue"));
putIfAbsent
  • 该方法签名: V putIfAbsent(K key, V value);
    • 只有在不存在key值的映射或映射值为null时,才将value指定的值放入到Map中,否则不对Map做修改
    • 该方法将判断和赋值合二为一,使用起来更加方便
remove
  • 该方法签名: remove(Object key);
    • 根据指定的key值删除Map中映射关系
  • 该方法签名: remove(Object key, Object value);
    • 只有在当前Mapkey正好映射到value时才删除该映射
replace
  • 该方法签名: replace(K key, V value);
    • 只有在当前Mapkey的映射存在时才用value去替换原来的值
  • 该方法签名: replace(K key, V oldValue, V newValue);
    • 只有在当前Mapkey的映射存在且等于oldValue时,才用newValue去替换原来的值,否则不做任何操作
replaceAll
  • 该方法签名: replaceAll(BiFunction<? super K, ? super V, ? extends V> function);
    • Map中的每个映射执行function操作,并用function的执行结果替换原来的value
    • 其中BiFunction是一个函数接口,里面有一个待实现的方法R apply(T t, U u)
  • 使用Java 7以前的方式将Map中的映射关系的单词都转换成大写:
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
for (Map.Entry<Integer, String> entry : map.entrySet()) {
	entry.setValue(entry.getValue().toUpperCase());
}
  • 使用replaceAll方法结合匿名内部类:
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.replaceAll(new BiFunction<Integer, String, String>(){
	@Override
	public String apply(Integer k, String v) {
		return v.toUpperCase();
	}
});
  • 使用Lambda表达式实现:
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.replaceAll(<k, v> -> v.toUpperCase());
merge
  • 该方法签名: merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction);
    • 如果Map中的key对应的映射不存在或者为null, 则将value, value不可能为null关联到key
    • 否则执行remappingFunction, 如果执行结果非null, 则用该结果与key关联,否则在Map中删除key的映射
    • 其中BiFunction是一个函数接口,里面有一个待实现方法R apply(T t, U u)
  • merge()方法语义复杂,但使用的方式明确,经典的使用场景: 将新的错误信息拼接到原来的信息上:
map.merge(key, newMsg, (v1, v2) -> v1 + v2);
compute
  • 该方法签名: compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);
    • remappingFunction计算的结果关联到key上,如果计算结果为null, 则在Map中删除key的映射
  • 使用compute实现将新的错误信息拼接到原来的信息上:
map.compute(key, (k, v) -> v == null ? newMsg : v.concat(newMsg));
computeIfAbsent
  • 该方法签名: V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);
    • 只有在当前Map中不存在key值的映射或映射值为null时,才调用mappingFunction, 并在mappingFunction执行结果非null时,将结果跟key关联
    • Function是一个函数接口,里面有待实现方法R apply(T t)
  • computeIfAbsent() 常用来对Map的某个key值建立初始化映射.比如在实现一个多值映射时 ,Map的定义可能是Map< K, Set< V > >, 要向Map中插入新值:
Map<Integer, Set<String>> map = new HashMap<>();
if (map.containsKey(1)) {
	map.get(1).add("one");
} else {
	Set<String> valueSet = new HashSet<String>();
	valueSet.add("one");
	map.put(1, valueSet);
}
  • 使用Lambda表达式实现:
Map<Integer, Set<String>> map = new HashMap<>();
map.computeIfAbsent(1, v -> new HashSet<String>()).add("one");
computeIfPresent
  • 该方法签名: V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);
    • 作用与computeIfAbsent() 相反
    • 只有当前Map中存在key值的映射且非null时,才调用remappingFunction, 如果remappingFunction执行结果为null, 则删除key的映射,否则使用该结果替换key原来的映射
  • Java7之前的等效代码:
if (map.get(key) != null) {
	V oldValue = map.get(key);
	V newValue = remappingFunction.apply(key, oldValue);
	if (newValue !=null) {
		map.put(key, newValue);
	} else {
		map.remove(key);
	}
	return newValue;
}
return null;
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。