Java8新特性:lambda表达式(下)

3.lambda表达式高级拓展

方法引用

方法引用是结合lambda表达式的一组语法特性,在开发过程中方法引用配合lambda表达式可以对代码进行简化,但是相应的会损失掉一些可读性。

方法引用具体分为静态方法引用、实例方法引用和构造方法引用。首先创建测试用类,

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
	private String name, gender;
	private int age;

	// 静态方法比较 age
	public static int compareAge(Person p1, Person p2) {
		return p1.getAge() - p2.getAge();
	}

	// 实例方法比较 Name
	public static int compareName(Person p1, Person p2) {
		return p1.getAge().hashCode() - p2.getAge().hashCode();
	}
}

此处使用 lombok语法支持,增加所有属性的构造方法和空构造方法。

1)静态方法引用的使用

  • 原始形式:类名.静态方法名()
  • 引用形式:类名::静态方法名
public class Test() {
	public static void main(String[] args) {
		List<Person> personList = new ArrayList<>();
		personList.add(new Person("a", "male", 16));
		personList.add(new Person("c", "female", 32));
		personList.add(new Person("de", "female", 34));

		// 使用匿名内部类的方式对 personList进行排序
		Collections.sort(personList, new Comparator() {
			@override
			public int compare(Person o1, Person o2) {
				return o1.getAge() - o2.getAge();
			}
		});
		System.out.println(personList);

		// lambda表达式实现
		Collections.sort(personList, (p1, p2) -> p1.getAge() - p2.getAge());
		System.out.println(personList);

		// 静态方法引用
		Collections.sort(personList, Person::compareAge);
		System.out.println(personList);
	}
}

2)实例方法引用的使用

  • 原始形式:类实例对象.实例方法名()
  • 引用形式:类实例对象::实例方法名
public class Test() {
	public static void main(String[] args) {
		Person p = new Person();
		List<Person> personList = new ArrayList<>();
		personList.add(new Person("a", "male", 16));
		personList.add(new Person("c", "female", 32));
		personList.add(new Person("de", "female", 34));
		
		// 实例方法引用
		Collections.sort(personList, p::compareName);
		System.out.println(personList);
	}
}

3)构造方法引用的使用

与实例方法和静态方法不同,构造方法的引用需要绑定函数式接口

创建一个函数式接口,用于初始化 Person类对象,

@FunctionalInterface
interface IPerson {
	Person initPerson(String name, String gender, int age);
}

public class Test() {
	public static void main(String[] args) {

		// 构造方法引用,绑定函数式接口 IPerson
		IPerson ip = Person::new;
		Person a = ip.initPerson("a", "male", 16);
	}
}

Stream

1)概述及演示

Stream流的引入是针对存储多个数据的容器(如数组、集合等)在批量处理数据过程中的繁杂操作提出的API,可以通过结合lambda表达式通过串行和并行两种方式完成对批量数据的增强操作。

public class Test() {
	public static void main(String[] args) {
		List<String> accounts = new ArrayList<>();
		accounts.add("tom");
		accounts.add("jerry");
		accounts.add("beta");

		// 用户名大于3才算有效账号
		// 传统方式遍历集合
		for (String account: sccounts) {
			if (account.length() > 3)
				System.out.println(account);
		}

		// 通过迭代器进行处理(实际上for循环就是使用了迭代器)
		Iterator<String> it = accounts.iterator();
		while(it.hasNext()) {
			String account = it.next();
			if (account.length() > 3)
				System.out.println(account);
		}

		// 通过Stream流方式简化代码
		List validAccounts = accounts.stream().filter(s -> s.length()>3).collect(Collectors.toList());
		System.out.println(validAccounts);
	}
}

首先通过stream方法获取相应数据的流对象,之后通过filter方法配合 lambda表达式对流中的数据进行过滤。最后以List的形式返回结果。

2)Stream API

Stream的处理流程可总结为,
数 据 源 − > 数 据 转 换 − > 获 取 结 果 数据源 -> 数据转换 -> 获取结果 >>

获取Stream对象的方式有多种,举例如下

  • 从集合中获取,集合对象.stream()集合对象.parallelStream()方法可获取集合的普通Stream对象和支持并发处理的Stream对象
  • 从数组中获取,Arrays.stream(T[] t)方法可获取数组的Stream对象
  • 通过缓冲流获取Stream对象,如BufferReader对象.lines()方法
  • 通过静态工厂方法获取Stream对象,如 java.util.stream包中对基本类型都创建了流对象的构造器。java.nio.file中也提供了流对象的构造方法
  • 自定义流对象

Stream API大致可分为两个主要操作方式和一个辅助操作方式,

  1. 中间操作API,intermediate操作
    可以理解为逻辑处理,操作结果是一个Steam对象,一个流程中可以有多个连续的中间操作。中间操作只记录操作方式,不做具体执行,直到结束操作发生时才对数据最终执行。
    **i.**无状态:数据处理时不受之前的中间操作影响。主要包含 map/filter/parallel/sequential/unorder等操作
    **ii.**有状态:数据处理时,受到前置中间操作影响。主要包含 distinct/sorted/limit/skip等操作
  2. 结束操作API,terminal操作
    一个Stream流对象的处理流程只能有一个结束操作。一旦这个操作被触发,就会开启数据处理的整个中间过程,最终生成结果。该过程是不可逆的。
    **i.**非短路操作:当前的Stream对象必须处理完集合中所有数据才能得到处理结果。注意包含forEach/forEachOrdweed/toArray/reduce/collect/min/max/count/iterator
    **ii.**短路操作:当前的Stream对象在处理过程中一旦满足某个条件,就可以得到结果。主要包含anyMatch/allMatch/noneMatch/findFirst/findAny

3)Stream对象对集合处理

由批量数据得到Stream对象
public class Test {
	public static void main(String[] args) {
		// 多个数据得到stream
		Stream stream1 = Stream.of("adad", "dadads", "dewf");

		// 由数组得到Stream对象
		String[] strArrays = new String[] {"a", "c", "f"};
		Stream stream2 = Arrays.stream(strArrays);

		// 由列表得到Stream对象
		List<String> list = new ArrayList<>();
		list.add("dsad");
		list.add("dahdi");
		Stream stream3 = list.stream();

		// 由集合得到Stream对象
		Set<String> set = new HashSet<>();
		set.add("dsad");
		set.add("dahdi");
		Stream stream4 = set.stream();

		// 由Map得到Stream对象
		Map<String> map = new HashMap<>();
		map.put("dsad", 1000);
		map.put("dahdi", 1200);
		Stream stream5 = map.entrySet().stream();
	}
}
Stream对象对基本数据类型的底层封装

jdk8中目前只针对基本类型中最常使用的 int、long和double类型进行了封装。以 int类型为例,

public class Test {
	public static void main(String[] args) {
		Stream stream = IntStream.of(new int[] {20, 30, 40});
		stream.foreach(System.out::println);

		IntStream.range(1, 5).forEach(System.out::println);		// 输出 1,2,3,4
		
	}
}
Stream对象转换得到指定数据类型
public class Test {
	public static void main(String[] args) {

		// 由Stream对象得到数组
		String[] strArrays = new String[] {"a", "c", "f"};
		Stream stream2 = Arrays.stream(strArrays);
		String strArr = stream2.toArray(String::new);

		// 由Stream对象得到字符串,拼接对象中的元素
		strArrays = new String[] {"a", "c", "f"};
		stream2 = Arrays.stream(strArrays);
		String str = stream2.collect(Collectors.joining()).toString();
		
		// 由Stream对象得到列表
		List<String> list = new ArrayList<>();
		list.add("dsad");
		list.add("dahdi");
		Stream stream3 = list.stream();
		List<String> strList = (List<String>) stream3.collect(Collectors.toList());

		// 由Stream对象得到集合
		Set<String> set = new HashSet<>();
		set.add("dsad");
		set.add("dahdi");
		Stream stream4 = set.stream();
		set<String> strSet = (Set<String>) stream3.collect(Collectors.toSet());


		// 由Stream对象得到Map
		strArrays = new String[] {"a", "c", "f"};
		stream2 = Arrays.stream(strArrays);
		Map<String, Integer> strMap = Map<String> stream2.collect(Collectors.toMap(x->x, y->"haha:"+y));
		System.out.println(strMap);		// 得到 ["a"="haha:a", "b"="haha:b", "c"="haha:c"]
	}
}
Stream中常见API操作
public class Test {
	public static void main(String[] args) {

		List<String> accountList = new ArrayList<>();
		accountList.add("songjiang");
		accountList.add("linchong");
		accountList.add("luzhishen");
		
		// map()中间操作,接收一个FunctionalInterface,对数据逐个操作
		accountList = accountList.stream().map(x->"梁山好汉:"+x).collect(Collectors.toList());

		// filter()过滤
		accountList = accountList.stream().filter(x->x.length()>5).collect(Collectors.toList());

		// forEach 增强for循环
		accountList.forEach(x->System.out.println("forEach->"+x));
		
		// 如果需要多次循环建议不要使用多次的forEach,因为多次调用forEach实际上是开启了多次Stream操作
		// 建议使用peek()完成多次循环遍历,在一次遍历过程中完成所有步骤的操作(将多次循环合并)
		accountList.stream().peek(x->"peek1:"+x)
				   .peek(x->"peek2:"+x).forEach(System.out::println);
		
	}
}
Stream中对数字运算的支持
public class Test {
	public static void main(String[] args) {
		List<Integer> intList = new ArrayList<>();
		intList.add(20);
		intList.add(19);
		intList.add(7);
		intList.add(8);
		intList.add(86);
		intList.add(11);
		intList.add(3);
		intList.add(20);

		// skip跳过部分数据
		intList.stream().skip(3).forEach(System.out::println);	// 跳过前三个数据

		// limit显著输出数据数目
		intList.stream().skip(3).limit(2).forEach(System.out::println);		// 跳过前三个数据且只对之后两个数据进行处理

		// distinct剔除重复数据
		intList.stream().distinct().forEach(System.out::println);

		// sorted排序
		intList.stream().sorted().forEach(System.out::println);

		// max、min获取极值,需传入一个Comparator
		Optional optional = intList.stream().max((x, y) -> x - y);
		System.out.println(optional.get());

		// reduce进行合并操作
		optional = intList.stream().reduce((cumSum, x) -> cumSum + x);
		System.out.println(optional.get());
	}
}

4)Stream性能

  1. 在基本数据类型的操作上(如 ArrayList<Integer>)建议使用迭代器、增强for循环和普通for循环。如果是多核环境,可以使用parallelStream()并行处理能够有效提升性能
  2. 当面对复杂数据的处理时(如 ArrayList<className>),并行Stream在多核环境下可以有效提升数据处理的性能

5)并行Stream的线程安全

并行Stream对象底层原理是将大任务拆分为多个子任务执行。

public class Test {
	public static void main(String[] args) {
		List<Integer> list = new ArrayList<>();
		for (int i=0; i<1000; i++) {
			list.add(i);
		}

		// 将list中的元素复制到另一个List
		// 串行Stream
		List list2 = new ArrayList<>();
		list.stream().forEach(x -> list2.add(x));
		System.out.println(list.size());
		System.out.println(list2.size());
		// 并行Stream
		List list3 = new ArrayList<>();
		list.parallelStream().forEach(x -> list3.add(x));
		System.out.println(list3.size());
	}
}

上述代码的运行结果为,

1000
1000
995

出现了数据丢失的情况,原因是 ArrayList类并非是线程安全的类,且 Stream API中明确写明forEach方法并不是线程安全的。解决这一问题可以使用 Stream API中提供的,在官方文档中明确说明的并行情况下保证线程安全的方法。如

public class Test {
	public static void main(String[] args) {
		List<Integer> list = new ArrayList<>();
		for (int i=0; i<1000; i++) {
			list.add(i);
		}

		// collect方法用于收集最终的Stream处理结果,是线程安全的方法
		List<Integer> list4 = list.parallelStream().collect(Collectors.toList());
		System.out.println(list4.size());
	}
}

此外更简单的解决方法是使用线程安全的并发集合类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值