Day11+12 函数式接口、Stream、方法引用

函数式接口+Stream流+方法引用

一、函数式接口

  • 函数式接口:一个接口中仅有一个抽象方法的接口。

格式:

修饰符 interface MyFun{
    public abstract func1(参数类型 参数);
}

​ 可以通过在接口上方加@FunctionalInterface来帮助检查是不是函数式接口。

注释的作用仅仅只是帮助检查是否符合函数式接口的要求,即便不加注释,只要满足要求依然是函数式接口。

二、函数式编程

函数式接口结合Lamba表达式,提供了一种较为清爽的编程方式。

2.1Lambda表达式的延迟执行

Lambda表达式具有延迟执行的特性:

public class Day10 {
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		String s1 ="dassad";
		String s2 = "dsanioa";	
		add(3,()-> s1+s2);
	}
	public static void add(int num,str s) {
		if(num == 1) {
			System.out.println("Lmaba准备执行啦!");
			System.out.println(s.pinjie());
		}
	}
}
interface str{
	abstract String pinjie();
}

​ 以字符串拼接为例子,Lambda的延迟,实际上是把Lambda要执行的操作,延缓到了接口对象调用方法的时候来实现,如果不满足执行条件,就不执行Lambda表达式,节省了资源。

​ 如果采用传统的方法:

  1. 先拼接好字符串,传递数字,那么不管数字是否满足判断,字符串都被拼接,消耗资源
  2. 传递所有要拼接的字符串+数字,验证数字之后再拼接,这样较为繁琐,而且字符串数量不能灵活的变化。

2.2Lmbda表达式作参数和返回值的情况

  • Lambda表达式作为参数:

    Lambda表达式做参数实际上就是接口对象作参数,接口对象执行操作的时候要实现接口,这时使用Lambda表达式来完成。

    public class Day12 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //调用的时候确定计算的方法为输入的数字*10
    		System.out.println(add(10,(a)->a*10));
    	}
        //add方法,参数是一个Cal类的对象以及一个数字
    	public static int add(int num,Cal c) {
    		return c.calculate(num);
    	}
    }
    //定义计算接口,内含一个抽象方法calculate(int num)
    interface Cal{
    	abstract Integer calculate(int num);
    }
    
  • Lambda表达式作返回值

    public class Day12 {
    	public static void main(String[] args) throws IOException, ClassNotFoundException {
    		Integer [] a = {1,2,3,4,88,11,66,99,10000,55};
    		Arrays.sort(a,myCompare());
    		for(Integer i :a) {
    			System.out.println(i);
    		}
    	}
    	//返回一个Lambda表达式,作为Comparator<Integer>接口的实现,用于比较
    	public static Comparator<Integer> myCompare() {
    		return (a,b)->a - b;
    	}
    }
    

三、常用的函数式接口

函数式接口大部分位于java.util.function包中被提供。

3.1Supplier接口

java.util.Supplier<T>接口仅包含一个无参的方法:

  • T get() 用来返回一个待定类型的对象

    • 举例:求数组最大元素
    public class Day12 {
    	public static void main(String[] args) throws IOException, ClassNotFoundException {
    			int [] a = {1,5,3,7,9,10,15,66,97,88,15};
    			int maxnum =getMax(a,()->{
    				int max = a[0];
    				for(int j : a) {
    					if(j>max) 
    						max=j;
    				} return max;	});
    			System.out.println(maxnum);
    		}
        //传递数组n,以及Supperlier<T>的一个对象(Lmbda表达式)
    	public static int  getMax(int [] n,Supplier<Integer> sup) {
    		return sup.get();
    	}
    }
    

3.2Consumer接口

java.util.Consumer<T>接口,与Supplier接口不同,它内部的抽象方法

  • void accept(T t) 消费一个泛型数据,是一个有参方法

    public class Day10 {
    	public static void main(String[] args) throws IOException, ClassNotFoundException {
    			String s = "Hello";
            //在使用时,定义accept的使用方式为:将字符串打印输出
    			useConsumer(s,(str)->System.out.println(str));
    		}
    	public static void  useConsumer(String str,Consumer<String> con) {
    		con.accept(str);
    	}
    }
    
  • 该接口中还有一个默认方法andThen

    default Consumer<T> andThen(Consumer<? super T> after)
    

    参数与返回值都是一个Consumer的对象,该方法和它的语义相同,先…再…。具体做什么,可以根据accept(T t)来进行定义

    public class Day10 {
    
    	public static void main(String[] args) throws IOException, ClassNotFoundException {
    			Integer i = 100;
            //第一次处理i值乘以10倍输出,第二次i值+50输出。但是两次i都是最开始给的100,中间并不会传递
    			useConsumer(i,(k)->{k*=10;System.out.println(k);},(k)->{k+=50;System.out.println(k);});		
    		}
    	public static void useConsumer(Integer k,Consumer<Integer> con1,Consumer<Integer> con2) {
    		con1.andThen(con2).accept(k);
    	}
    }
    
练习 格式化打印信息:

字符串中存储了姓名+性别,中间用","隔开,将它们拼接后输出。

public static void main(String[] args) {
String [] array = {"第一个,男" , "第二个,女" , "第三个,外星人"};
		for(String ss :array) {
				consumeString(
						(s)->System.out.print(s.split(",")[0]),
						(s)->System.out.println(s.split(",")[1]),
						ss);
        }
            private static void consumeString(Consumer<String> function1,Consumer<String> function2,String ss) {
		function1.andThen(function2).accept(ss);

3.3 Predicate接口

java.util.function.Predicate<T>接口的主要作用是判断,得到一个boolean值的结果。

  • 抽象方法:boolean test(T t)用于条件判断

    public class Day10 {
    	public static void main(String[] args) throws IOException, ClassNotFoundException {
    			useJudge(100,k->k>10);
    		}
        //判断k是否大于10
    	public static void useJudge(Integer k,Predicate<Integer> p) {
    		if(p.test(k))
    			System.out.println("数字k大于10");;
    	}
    }
    
  • 默认方法:

    • default Predicate<T> and(Predicate<? super T> other) 用法与逻辑""相同

      default Predicate<T> and(Predicate<? super T> other) {
          Objects.requireNonNull(other);
          return (t)> test(t) && other.test(t);
      }
      
    • default Predicate<T> negate()用法与逻辑""相同,对目前的test取反

      default Predicate<T> negate() {
          return (t)> !test(t);
      }//从源码中可以看出,它要在test之前调用,对当前的test值取反
      
    • default Predicate<T> or(Predicate<? super T> other)用法与逻辑""相同

      default Predicate<T> or(Predicate<? super T> other) {
          Objects.requireNonNull(other);
          return (t)> test(t) || other.test(t);
      }
      
练习:集合信息筛选

数组当中有多条“姓名 +性别”的信息如下,请通过 Predicate 接口的 拼装将符 合要求的字符 串筛选到集合
ArrayList 中,需要 同时满足 两 个条件:

  • 必须是女生

  • 名字长度为4

    public class RealDay12 {
    	public static void main(String[] args) {
    		String [] str = {"按时到会 女","时候 女","点搜 男","都不爱沙石堡 男","打谁哦阿斯顿 女","四个字符 女"};
    		ArrayList<String> list = new ArrayList<String>();
    		method(
    				(s)->s.split(" ")[0].length()==4,
    				(s)->s.split(" ")[1].equals("女"),
    				str,list);
    		for(String s :list) {
    			System.out.println(s);
    		}	
    	}
    	private static void  method(Predicate<String> pre1,Predicate<String> pre2,String[] str,ArrayList<String> list) {
    		for(String s :str) {
    			if(pre1.and(pre2).test(s))
    				list.add(s);}
    	}
    

3.4 Function接口

java.util.function.Function<T,R>接口的主要作用是“映射”,即根据T类型的对象,转换成R类型的对象。

且T与R可以是同一类型。

  • 抽象方法:R apply(T t)
  • 默认方法:andThen
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t)> after.apply(apply(t));
}

​ 从源码中可以看出,Function中的andThen方法与Consumer中的andThen方法的区别是,前者的数据是传递下去的,而后者只是对一个数据独立的处理两次。

练习 自定义函数拼接

请使用 Function 进 行函数模 型 的拼接,按照顺序 需要执行 的多个 函数操 作为:
String str = “赵丽颖,20”

  1. 将字符串截取数 字年龄部分, 得到字符串;
  2. 将上一步的字符 串转换成为int类型的数字;
  3. 将上一步的int数 字累加100,得到结果int数字。
public class RealDay12 {
	public static void main(String[] args) {	
		String str = "赵丽颖,20";
		add(
		k->k.split(",")[1],//第一步,分割字符串为字符串数组,返回代表年龄部分的字符串
		k->Integer.parseInt(k)+100,//第二部,将年龄字符串转换为数字,再+100
		str);	
	}	
	private static void add(Function<String,String> function1,Function<String,Integer> function2,String str) {
		int num = function1.andThen(function2).apply(str);
		System.out.println(num);
	}	
}

四、Stream流

java.util.stream.Stream<T>,是java8中新加入的常用流接口。

Stream流和Lambda表达式有许多相似的地方,最关键的一点就是,注重做什么,不在意怎么做。

  • Stram流的基本特征

    • Pipelining:流的中间操作都会返回一个新流,可以不断进行下一步操作

    • 内部迭代,不用再利用循环,具有直接的迭代方法

      在对流进行操作后,流不能再次被使用!所以每次对流操作都会返回一个新流。

  • 流的主要获取方式

    • 由Collection获取(以List为例)

      List<String> list = new ArrayList<String>();
      		Stream<String> stream = list.stream();
      
    • 由数组获取流(比较特殊,是用Stream的静态方法获取)

      Integer [] a = {1,2,3,4,5,6};
      		Stream<Integer> stream = Stream.of(a);
      
    • 由Map获取流(由于是双列数据,分为两种情况)

      • 值/键的流

      • Entry<K,V>流

        Map<String,String> map = new HashMap<String, String>();
        		Stream<String> stream1 = map.keySet().stream();
        		Stream<String> stream2 = map.values().stream();
        		Stream<Entry<String,String>> stream3 = map.entrySet().stream();
        
  • 流的基本方法

    • forEach(逐一处理)

      void forEach(Consumer<? super T> action);,内涵了一个Consumer接口,通过接口内部的accept(T t)方法来对数据进行逐一处理

    • fliter(过滤)

      Stream<T> filter(Predicate<? super T> predicate);,根据test的条件进行过滤

    • map(映射)

      <R> Stream<R> map(Function<? super T, ? extends R> mapper);,根据apply方法对数据进行转换

    • concat(组合),组合两个流,形成一个新流

      Stream<String> stream3 = Stream.concat(stream1,stream2);

集合元素处理练习:

分别采用传统循环方法,和流方法对集合元素进行如下处理:

现在有两个 ArrayList 集合存 储 队伍当中的多个成员姓 名,要 求使用传 统的f o r 循环(或增强f o r 循环 )依次进行以
下若干操作步骤:

  1. 第一个队伍只要名字为3 个字的成员姓名;存储到 一个新 集合中。
  2. 第一个队伍筛选之后只要前3 个人;存储到一个新集合中 。
  3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中 。
  4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中 。
  5. 将两个队伍合并为一个队伍存储到一个新集合中。
  6. 根据姓名创建Person对象;存储到一个新集合中。
  7. 打印整个队伍的Person对象信 息。
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<String>();
ArrayList<String> listnew = new ArrayList<String>();
ArrayList<String> list2 = new ArrayList<String>();
ArrayList<String> list2new = new ArrayList<String>();
Collections.addAll(list1, "实践队是","刘是海屏","上档的次","的赛道的","张三丰");
Collections.addAll(list2, "张践队","刘大声道","又突然","大萨大傻傻的达","你你你","张憨憨","张志超","张大炮");
//采用传统方式处理
for(int i = 0; i<list1.size();i++) {
	if(list1.get(i).length()==3) {
		listnew.add(list1.get(i));
	}	
}
for(int i = 0; i <list2.size();i++) {
	if(list2.get(i).startsWith("张")) {
		list2new.add(list2.get(i));
	}
}


ArrayList<String> finalist1 = new ArrayList<String>();
if(listnew.size()>=3) {
	for(int i = 0;i<3;i++) {
		finalist1.add(listnew.get(i));
	
}
	
}else if(listnew.size()<3&&listnew.size()>0){
	for(int i = 0 ; i <listnew.size();i++) {
		finalist1.add(listnew.get(i));
	}
}

if(list2new.size()>2) {
	list2new.remove(0);
	list2new.remove(0);
}

ArrayList<String> list3 = new ArrayList<String>();
for(String s :list2new) {
	list3.add(s);
}
for(String s :finalist1) {
	list3.add(s);
}
ArrayList<Person> personlist = new ArrayList<Person>();
for(String s :list3) {
	personlist.add(new Person(s));
}

for(Person person : personlist) {
	System.out.println(person.toString());
}
//——————————————————————————————————————————————————————————————————————
//采用Stream方式处理
Stream<String> stream1 = list1.stream();
Stream<String> stream2 = list2.stream();
Stream<String> stream1last=stream1.filter((name)-> name.length()==3).limit(3);
Stream<String> stream2last=stream2.filter((name)->name.startsWith("张")).skip(2);
Stream<String> finalstream = Stream.concat(stream1last, stream2last);
ArrayList<Person> personlist2 = new ArrayList<Person>();
finalstream.forEach((name)->personlist2.add(new Person(name)));
System.out.println("Srteam流的优势");
for(Person person : personlist) {
	System.out.println(person.toString());
}
//最后的输出结果相同,但是Stream流要更加轻便简洁

五、方法引用

​ Lambda表达式相对于实现一个内部类,或者新建一个类的方式来的更加简洁,可是在某些情况下,Lambda表达式也会显的不是很“清爽”:

public class Day13 {
	public static void main(String[] args) {
		String s = "asdno";
		print(s,(str)->System.out.println(str));
	}
	private static void print(String a , Printable b) {
		b.print(a);
	}
}
interface Printable{
	void print(String str);
}

​ 如上述所示情况,Lambda表达式实现了print方法。可是在System.out中已经有了print的方法,采用方法引用的方式,可以更加简便:print(s,System.out::print);这与print(s,(str)->System.out.println(str));

的作用是一样的。

::双冒号的形式就称为方法引用。它的意义是,用print(s,System.out::print);来取代Lambda表达式

方法引用也同样具有可推导可省略的特点,以上述例子为例:将print方法的输出改为Int类型后,会自动匹配print中输出打印int类型的方法。

public class Day13 {
	public static void main(String[] args) {
		int i =10;
		print(i,(str)->System.out.println(str));
	}
	private static void print(int a , Printable b) {
		b.print(a);
	}
}
interface Printable{
	void print(int str);
}

通过对象名引用方法

​ 当类中已经存在某个成员方法,可以直接引用来替代Lambda。还是这个接口,但是这次想要打出若干个*符号,在MyFunction中就已经有现成的方法可以引用。

public class Day13 {

	public static void main(String[] args) {
        //通过建立该类的一个对象,引用对象中的方法来实现打印
		MyFunction m = new MyFunction();
		print(10,m::print);
	}
	private static void print( int num ,Printable b) {
		b.print(num);
	}
}

interface Printable{
	void print(int str);
}
class MyFunction{
	public void print(int num) {
		for(int i =1 ; i<num;i++) {
			System.out.print("*");
		}
	}
}

通过类名引用静态方法

public class Day13 {

	public static void main(String[] args) {
		CC(Math::abs,-10);
	}
	private static void CC(Cac lambda,int a) {
		lambda.calc(a);
	}
}
interface Cac{
	int calc(int num);
}

直接引用Math类中的abs方法替代Lambda表达式实现calc(int num)接口的方法,对-10进行绝对值处理。

通过super引用成员方法

interface Greetable{
	void greet();
}
class Human{
	public void sayHello() {
		System.out.println("hello");
	}
}
class Man extends Human{
	@Override
	public void sayHello() {
		System.out.println("我是Man");
	}
	public void method(Greetable g) {
		g.greet();
	}
	public void show() {
		method(()->super.sayHello());//Lambda方法
		method(super::sayHello);//方法引用
	}	
}

通过this引用成员方法

与上述super引用的情况相似

//......
class Man extends Human{
	@Override
	public void sayHello() {
		System.out.println("我是Man");
	}
	public void method(Greetable g) {
		g.greet();
	}
	public void show() {
        method(()->this.sayHello());//Lambda方法
		method(this::sayHello);//this引用自身的方法
	}	
}

引用类的构造器

//成员类
class Person{
 	public void printUpperCase(String i) {		
	}
	private String name;
	public Person() {}
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return name;
	}
	public Person(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	};
   //函数接口
    interface personBuilder{
	Person buildPerson(String name);
}
    public class Day13 {
	public static void main(String[] args) {
        //采用Lambda表达式
        printName("xiaoming",(str)->new Person(str));
        //引用Person类的构造方法,会自动将xiaoming作为参数构造一个新的Person
		printName("xiaoming",Person::new);
	}
	public static void printName(String name,personBuilder builder) {
		System.out.println(builder.buildPerson(name).getName());
	}
  }

引用数组构造器

interface ArrayBuilder{
	int [] buildArray(int length);
}

public class Day13 {

	public static void main(String[] args) {
        buildArray(10,(i)->new int [i]);//Lambda表达式
		buildArray(10,int []::new);//数组构造器的引用
	}

	private static void buildArray(int k ,ArrayBuilder g) {
		int [] b= g.buildArray(k);
	}	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值