Java基础 之 lambda、stream概念及实际使用举例

lambda

什么是lambda

        函数式编程,将代码块作为参数传入方法,用更简洁的方法创建一个函数式接口的实例。

什么是函数式接口

        一个有且只含有一个抽象方法的接口(可以有多个默认方法)。

举个栗子:

@FunctionalInterface
public interface Convert {
	Integer test(Integer aString);
}

声明一个接口,接口中有一个抽象方法test()。这个接口就叫函数式接口。

@FunctionalInterface这个注解用于检测该接口是否为一个 函数式接口,如果不是IDE会报错。说这个接口不是一个函数是接口,无法通过编译

概念解释完了。那么lambda就很好理解了。

 

lambda表达式写法规则

(type1 a,type2 b)->{

do something...

}

整个表达式分3个部分:

1,(type1 a,type2 b)——左侧这是函数式接口中的抽象方法的参数列表。

这里:类型可以省略,lambda会根据上下文去自动判断;没有参数列表时写一个()即可;一个参数时可以省略()

2,-> lambda表达式符号。

3,{

do something...

}

右侧为抽象方法的具体实现的代码块。一行代码时可省略return及花括号,直接将这行代码结果作为返回值,多行代码时,如果抽象方法需要返回值,则要写return语句。

lambda典型示例

java中典型的可以使用lambda的有一个接口是runnable接口。用它可以来新建一个线程:

举个栗子:

先看下runnable代码:

 

@FunctionalInterface   //表明该接口是一个函数式接口
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();   唯一一个抽象方法,参数列表为空。

新建一个线程:

		//原始方式
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("原始方法新建线程1");
			}
		}).start();
		//lambda
		new Thread(()->System.out.println("lambda方式建新线程1")).start();

可见lambda方式会简便很多。

其实lambda表达式的直接返回值是一个函数是接口类型的变量,所以如果自定义时,需要使用函数是接口接受lambda表达式,并且在调用时,调用的方法参数列表中的一个参数,应该是此函数式接口的类型。

JDK提供给lambda使用的函数式接口

JDK默认给我们提供了很多函数式接口,都在java.util.function包下。大体分为以下几种类型:

级联

级联就是如果有多个->符号连续时的运算。这时候我们只要将每一个->后面的当做一个lambda进行计算,然后再将结果一一返回即可。

科里化

函数标准化,把多个参数的函数转换成只有一个参数的函数。

 

下面贴一段代码,是我之前写的关于上面几个点的例子:

import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.DoublePredicate;
import java.util.function.DoubleUnaryOperator;
import java.util.function.Function;
import java.util.function.IntBinaryOperator;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import java.util.function.IntUnaryOperator;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

import org.codehaus.groovy.util.StringUtil;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@AllArgsConstructor
@NoArgsConstructor
class MyAge{
	private int age;
	
	public void printMyAge(Function<Integer, String> function){
		System.out.println("我今年"+function.apply(this.age)+"岁");
	}
}

/**
 * JDK提供的几种函数式接口测试
 * @author maybe
 *
 */
@Slf4j
public class TestFunction {

	public static void main(String[] args) {
		
		Function<Integer, String> wer = (Integer x)-> x.toString();
		System.out.println(wer.apply(6534));
		
		//Predicate断言函数 boolean
		Predicate<Integer> predicate = i->i>0;
		log.info("断言函数:{}",predicate.test(-9));

		DoublePredicate doublePredicate = i->i>0;
		log.info("基本类型断言函数:{}",doublePredicate.test(2.5));
		
		
		//Consumer消费者函数 一个输入 无输出
		Consumer<String> consumer = i -> log.info("消费者函数消费 一个数字字符串,{}",i);
		consumer.accept("20");
		
		IntConsumer intConsumer = i ->log.info("基本类型消费者函数消费了一个int数字.{}",String.valueOf(i));
		intConsumer.accept(50);
		
		//funciton函数,一个输入一个输出
		MyAge myAge = new MyAge(20);
		myAge.printMyAge(i->i.toString());
		Function<Integer, String> function = i->i.toString();
		myAge.printMyAge(function.andThen(i->i+"周"));
		
		//Supplier提供者函数 一个输出 无输入
		Supplier<String> supplier = () -> "普通提供者函数提供了这段话";
		log.info(supplier.get());
		
		IntSupplier intSupplier = () -> 666;
		log.info("基本类型int提供者函数提供了{}这段数字",intSupplier.getAsInt());
		
		//一元函数
		UnaryOperator<String> operator = i -> i+"666";
		log.info("一元函数{}",operator.apply("SC"));
		
		DoubleUnaryOperator doubleUnaryOperator = i -> 2*i;
		log.info("double一元函数{}",doubleUnaryOperator.applyAsDouble(50));
		
		//BiFunction函数 两个输入 一个输出
		BiFunction<String, Boolean, String> biFunction = (x,y)->y==true?x+"-yes":x+"-no"; 
		log.info("两个输入有一个输出的BiFunciton函数true,{}",biFunction.apply("两个输入", true));
		log.info("两个输入有一个输出的BiFunciton函数false,{}",biFunction.apply("两个输入", false));
		
		//二元函数
		BinaryOperator<Integer> binaryOperator = (x,y) -> x+y;
		log.info("二元函数结果{}",binaryOperator.apply(2, 5));
		
		IntBinaryOperator intBinaryOperator = (x,y) -> x*y;
		log.info("int二元函数结果{}",intBinaryOperator.applyAsInt(5, 10));
		
		/*
		 * 级联表达式与科里化
		 * 科里化:函数标准化,吧多个参数的函数转换成只有一个参数的函数
		 */
		Function<Integer, IntUnaryOperator> fun = x->y->x+y;
		System.out.println(fun.apply(2).applyAsInt(5));
		
		
	}
	
}

stream流

        stream是一个高级迭代器,一般用于数据处理,配合lambda使用的一种流水线式的处理。特性包括:内部迭代,中间操作和中止操作,惰性求值,方法引用以及并行流。

stream的中间操作

stream的终止操作

惰性求值

调用终止操作前,中间操作不会执行。

并行流

再开启并行流之后,JDK会自动帮我们开启多线程模式进行数据处理。并能保证数据的完整性及排序。

 

 

下面直接看一下使用例子

 

public class TestStream1 {

	public static void main(String[] args) {
		//外部迭代
		int[] nums = {2,3,4};
		int sum = 0;
		for(int i=0;i<nums.length;i++) {
			sum += nums[i];
		}
		System.out.println("外部迭代"+sum);
		
		//内部迭代
		int sum2 = IntStream.of(nums).sum();
		System.out.println("内部迭代"+sum2);
		
		//中间操作,返回一个stream流
		int sum3 = IntStream.of(nums).map(i->i*2).sum();
		System.out.println("内部迭代加map中间操作"+sum3);
		
		//惰性求值
		int sum4 = IntStream.of(nums).map(TestStream1::doubleNum).sum();
		System.out.println("调用了终止操作,结果:"+sum4);
		IntStream.of(nums).map(TestStream1::doubleNum);
		System.out.println("没调用终止操作,不会执行");
		
	}
	
	public static int doubleNum(int i) {
		System.out.println("乘以2");
		return i*2;
	}
}
public class TestStream2 {

	public static void main(String[] args) {
		
		/**
		 * 流的创建
		 */
		List<String> list = new ArrayList<>();
		//通过collection创建一个流
		Stream<String> stream = list.stream();
		//并行流
		Stream<String> stream2 = list.parallelStream();
	
		int[] nums = {5,6,7,8};
		//通过数组创建一个int流
		IntStream stream3 = Arrays.stream(nums);
		
		String str1 = "my name is wm1";
		String str2 = "my name is wm2";
		String str3 = "my name is wm3";
		
		System.out.println("流的中间操作map");
		//流的中间操作map
		System.out.println(stream3.map(i->i+1).sum());
		System.out.println("======================================");
		/*
		 * 通过int直接创建int流
		 * 中间操作过滤,排序,去重
		 * 终止操作循环
		 */
		System.out.println("流的中间操作filter,sorted,distnct,peek,终止操作sum");
		System.out.println("结果:"+IntStream.of(5,6,1,2,1,-5).filter(i->i>0).sorted().distinct().peek(System.out::println).sum());
		
		System.out.println("======================================");
		/*
		 * flatMap,得到A中的B的全部列表 
		 */
		System.out.println("流的中间操作flatMap");
		Stream.of(str1.split(" ")).flatMap(i->i.chars().boxed()).forEach(i->{
			System.out.println((char)i.intValue());
		});
		
		System.out.println("======================================");
		/**
		 * 流的终止操作
		 */
		//forEachOrdered并行流,保证顺序
		System.out.println("流的终止操作,并行流foreach,第一种,不建议");
		Stream.of(str2.split(" ")).parallel().flatMap(i->i.chars().boxed()).forEachOrdered(i->System.out.println((char)i.intValue()));
		//第二种方法,建议
		System.out.println("流的终止操作,并行流foreach,第二种,建议");
		str3.chars().parallel().forEachOrdered(i->System.out.println((char)i));
		System.out.println("去空格版本");
		str3.replace(" ", "").chars().parallel().forEachOrdered(i->System.out.println((char)i));
		
		System.out.println("======================================");
		System.out.println("collect操作");
		Stream.of(str3.split(" ")).collect(Collectors.toList()).forEach(System.out::println);
		System.out.println("toArray");
		Stream.of(Stream.of(str3.split(" ")).toArray()).forEach(System.out::println);
		System.out.println("reduce组合数据");
		Optional<String> reduce = Stream.of(str3.split(" ")).reduce((s1,s2)->s1+"|"+s2);
		System.out.println(reduce.orElse(""));
		System.out.println("MAX");
		Optional<String> max = Stream.of(str1.split(" ")).max((s1,s2)->s1.length()-s2.length());
		System.out.println(max.orElse(""));
		System.out.println("min");
		Optional<String> min = Stream.of(str1.split(" ")).min((s1,s2)->s1.length()-s2.length());
		System.out.println(min.orElse(""));
	}
}
/**
 * 并行流
 * @author maybe
 *
 */
public class TestStream3 {

	public static void main(String[] args) {
		
		//使用JDK自带的线程池 ForkJoinPool.commonPool
//		System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "3");
//		IntStream.range(1, 100).parallel().peek(i->{
//			System.out.println(Thread.currentThread().getName()+":"+i);
//			try {
//				TimeUnit.SECONDS.sleep(3);
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
//		}).count();
		
		//使用自己写的线程池 ForkJoinPool-1
		ForkJoinPool forkJoinPool = new ForkJoinPool(6);
		forkJoinPool.submit(()->{
			IntStream.range(1, 100).parallel().peek(i->{
				System.out.println(Thread.currentThread().getName()+":"+i);
				try {
					TimeUnit.SECONDS.sleep(3);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}).count();;
		});
		forkJoinPool.shutdown();
		
		synchronized (forkJoinPool) {
			try {
				forkJoinPool.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}

 

实践示例

有关效率:其实对于arraylist而言,传统for循环的随机遍历,效率更高一些。迭代器遍历对于LinkedList这类链表遍历更有效率。这段其实我也是略懂。可能有错误,这里不多说无关的了。有空再深入研究一下相关源码。

下面以我做所的业务架设了一个场景来做一组栗子:

一个实体javabean:

 

public class Flight {

	@JSONField(ordinal = 1)
	private int id; //id
	@JSONField(ordinal = 2)
	private String flightNo; //航班号
	@JSONField(format = "yyyy-MM-dd",ordinal = 3)
	private Date qf; //起飞时间
	@JSONField(format = "yyyy-MM-dd",ordinal = 4)
	private Date dd;//到达时间
	private String remarks; //航班备注
	private String money;//航班金额

	public Flight() {}
	
	public Flight(int id,String flightNo,Date qf,Date dd) {
		this.id = id;
		this.flightNo = flightNo;
		this.qf = qf;
		this.dd = dd;
	}
	
	@Override
	public String toString() {
		return JSONObject.toJSONString(this,true);
	}
	省略get set方法......
}

这里简单讲一下:框架使用spring boot,并且使用fastjson,json解析框架。回头有时间会单独写有关fastjson相关内容。

 

@JSONField(ordinal = 1) 指定输出json格式时排序,低的在前。
@JSONField(format = "yyyy-MM-dd",ordinal = 3) 指定date类型输出格式
JSONObject.toJSONString(this,true); fastjson 方法,输出json格式字符串,true代表使用标准的json格式输出

 

下面开始先生成一个list集合来模拟查询出来的航班信息:

 

Flight[] flights = {
				new Flight(1,"sc111",new SimpleDateFormat("yyyy-MM-dd").parse("2018-01-02"),new SimpleDateFormat("yyyy-MM-dd").parse("2018-01-03")),
				new Flight(2,"sc222",new SimpleDateFormat("yyyy-MM-dd").parse("2018-01-04"),new SimpleDateFormat("yyyy-MM-dd").parse("2018-01-05")),
				new Flight(3,"sc333",new SimpleDateFormat("yyyy-MM-dd").parse("2018-01-06"),new SimpleDateFormat("yyyy-MM-dd").parse("2018-01-07"))
			};
List<Flight> flights2 = Arrays.asList(flights);

需求1:遍历出来所有的航班

		//for循环
		for(Flight f :flights2) {
			System.out.println(f.toString());
		}
		//foreach
		flights2.forEach((f)->System.out.println(f.toString()));

简单解释一下:

这里调用的foreach方法,查看源码:

 

void forEach(Consumer<? super T> action);

找到Consumer接口:

 

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * Returns a composed {@code Consumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code Consumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

所以我们的lambda表达式实现的是Consumer接口的accept方法。参数列表为(T t),T为该类动态指定的泛型。关于泛型之后也会专门写文章来记录。

所以这个lambda写时需要一个参数列表,可以省略类型。 后面是遍历之后的抽象方法的具体实现。


 

结果都是:

{
	"id":1,
	"flightNo":"sc111",
	"qf":"2018-01-02",
	"dd":"2018-01-03"
}
{
	"id":2,
	"flightNo":"sc222",
	"qf":"2018-01-04",
	"dd":"2018-01-05"
}
{
	"id":3,
	"flightNo":"sc333",
	"qf":"2018-01-06",
	"dd":"2018-01-07"
}

需求2:遍历出起飞时间晚于2018-01-03的航班

 

//for
for(Flight f :flights2) {
	if(f.getQf().after(date)) {
		System.out.println(f.toString());
	}
}
//lambda
flights2.stream().filter((f)->f.getQf().after(date)).forEach((p)->System.out.println(p.toString()));

先将这个list转入stream流处理。调用filter过滤器,查看源码:

 

Stream<T> filter(Predicate<? super T> predicate);

找到Predicate接口(部分):

 

@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);

所以,我们这里实现filter方法,需要返回值为boolean类型。然后进行foreach遍历。

结果:

 

{
	"id":2,
	"flightNo":"sc222",
	"qf":"2018-01-04",
	"dd":"2018-01-05"
}
{
	"id":3,
	"flightNo":"sc333",
	"qf":"2018-01-06",
	"dd":"2018-01-07"
}

需求3:遍历出起飞时间晚于2018-01-03的航班,并转成list,通过controller返回给前台json数据。

 

//for
List<Flight> flights3 = new ArrayList<Flight>();
for(Flight f :flights2) {
	if(f.getQf().after(date)) {
		flights3.add(f);
	}
}
return flights3;
//lambda
return flights2.stream().filter((f)->f.getQf().after(date)).collect(Collectors.toList());

collect方法用于收集一个流的结果。这里我们将这个流转为list
 

结果这里就不打印了。和需求2一致。

需求4:对这个list的数据进行处理。比如id为偶数的航班为中转航班,现在需要给这类航班添加一个备注信息。

 

//for
for(Flight a :flights2) {
	if(a.getId()%2==0) {
		a.setRemarks("这是一个中转航班");
	}
	System.out.println(a.toString());
}
//lambda
flights2.stream().filter((f)->f.getId()%2==0).forEach((a)->a.setRemarks("这是一个中转航班"));
flights2.forEach(System.out::println);

注意:使用了filter方法,这个流中就不再包含不符合这个filter的数据了。所以后面的foreach方法,是将过滤后符合的数据全部赋值,这时候如果同时遍历打印出所有的数据。那么这个例子中只有一条记录,因为只有2,符合条件。所以这时候要全部遍历打印集合内容,要在赋值后再写foreach方法遍历打印。

这里只有一行代码时,可以使用java8中的方法引用(::)

简单举个例子:有一个类,类里有1个方法,有如下情况:

1,类::静态方法,则函数式接口的抽象方法中的参数列表就是要传入调用的方法的参数列表。

2,实例:普通方法,参数列表同1;

3,类:实例方法,函数式接口的抽象方法的参数列表的额第一个参数为调用者,后面的为传入的参数列表。

4,构造器:类:new  参数列表同1;

这里,就是调用的的System.out的println方法。并且把一个参数(遍历后的对象)传入println中,默认调用toString来打印。

 

结果:

 

 

{
	"id":1,
	"flightNo":"sc111",
	"qf":"2018-01-02",
	"dd":"2018-01-03"
}
{
	"remarks":"这是一个中转航班",
	"id":2,
	"flightNo":"sc222",
	"qf":"2018-01-04",
	"dd":"2018-01-05"
}
{
	"id":3,
	"flightNo":"sc333",
	"qf":"2018-01-06",
	"dd":"2018-01-07"
}

由于remarks没有加入排序,顺序乱了。这里先不管他了。
 

需求5:现在有一个模块。只需要航班和该航班的起飞时间。所以我们要把航班id和起飞时间对应起来。组合一个map

 

//for
Map<Integer, Date> map = new HashMap<Integer, Date>();
for(Flight a :flights2) {
	map.put(a.getId(), a.getQf());
}
for(Map.Entry<Integer, Date> entry : map.entrySet()) {
	System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
}
//lambda
flights2.stream().collect(Collectors.toMap(Flight::getId, Flight::getQf)).forEach((k,v)->System.out.println("k:"+k+",V:"+v));

结果:

 

key:1,value:Tue Jan 02 00:00:00 CST 2018
key:2,value:Thu Jan 04 00:00:00 CST 2018
key:3,value:Sat Jan 06 00:00:00 CST 2018
k:1,V:Tue Jan 02 00:00:00 CST 2018
k:2,V:Thu Jan 04 00:00:00 CST 2018
k:3,V:Sat Jan 06 00:00:00 CST 2018

 

栗子大概就这么多。关于lambda表达式我觉得用处还是挺多的。有空继续深入学习吧。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值