JDk8新特征

目录

 

1,JDK8接口新特性

1.1接口中默认方法

1.2接口中的静态方法

2.Lambda表达式

3.函数式接口

3.1函数式接口概述

3.2JDK提供的常用4种函数式接口

3.3消费型接口

3.4供给型接口

3.5函数型接口

3.6断言型接口

3.7方法引用

4.StreamAPI


1,JDK8接口新特性

从JDK8版本开始, 接口中不仅可以定义出抽象方法, 也可以定义出非抽象方法; JDK8版本中可以在接口中添加默认方法(default), 也可以添加静态方法(static), 默认和静态都是具有方法体; 只有抽象方法没有方法体

1.1接口中默认方法

  JDK中或者很多项目中, 存在很多老接口(存在已经很久, 使用很多的接口), 举例 : Collection 单列集合顶层父接口, 如果需要优化和改造呢? 如果接口中只能定义出抽象方法, 如果父接口中添加抽象方法, 那么所有实现类都会受到影响; 项目也会有影响,需要改动的场景就会很多; 因此在JDK8版本下, 可以在接口中设计default默认修饰的方法, 因为父接口中的默认方法可以直接被实现类使用, 不强制重写

   default 返回值类型 方法名(参数列表){

         // 方法体;

}

  1. default方法的使用:

   a : 父接口中的默认方法, 可以直接被实现类使用, 不需要强制实现类重写

   b : 实现类中可以重写父接口中的默认方法

           1) 类中不能写default 默认修饰, 不写才表示默认修饰

           2) 实现类重写默认方法时, 修饰符需要是public修饰

   c : 类与接口之间可以多实现(一个类可以同时实现多个接口), 如果多个父接口中定义出相同default默认方法,实现类使用就会冲突; 解决方案 : 要求实现类一定要重写冲突的默认方法

      如果实现类重写冲突方法功能时, 发现现在想调用某一个父接口中默认方法实现过程 : 语法  父接口名.super.方法名(实际参数);

   d : 一个类继承一个父类的同时, 还可以实现很多接口;  父优先” , 不管继承关系,还是 方法的重写,以及调用, 一律父优先; 如果父类中没有的, 再到父接口中寻找使用

 

1.2接口中的静态方法

1,问题1 : 父类中静态方法可以被子类继承使用; 子类不能重写从父类继承来静态方法, 原因 : 静态属于类; 子类中定义出的静态方法就属于子类,不是重写的父类

2,JDK8版本中, 可以在接口中定义使用static修饰的静态方法

   static 返回值类型 方法名字(参数列表){

}

3,父接口中静态方法功能不能被实现类使用, 只能通过接口名.直接调用

   实现类不能继承父接口中的静态方法原因 :

       类与接口之间可以多实现; 如果有个实现类同时实现了多个接口, 而多个接口中有相同静态方法, 发生继承冲突性; 解决问题就是重写静态方法, 但是静态方法不能重写; 矛盾,因此父接口中静态方法不让实现类使用

2.Lambda表达式

1,Lambda表达式本质 :

   就是一个接口的实现类对象

2,Lambda表达式使用前提 :

   只能够实现函数式接口(接口中只有一个抽象方法,这个接口称为函数式接口)

   使用注释 : @FunctionalInterface 验证接口是否为一个函数式接口

3,Lambda表达式优势:

   比匿名内部类对象实现更加简洁; 效率更高

4,Lambda表达式使用 :

   (参数列表)->{方法体/Lambda体};

5,解释Lambda表达式:

  1. -> : 称为箭头运算符, 也称为Lambda运算符, 作用就是将需要实现唯一的抽象方法分成两部分
  2. 参数列表 : 唯一抽象方法的参数列表
  3. 方法体 : 唯一抽象方法重写实现过程

6,Lambda表达式使用注意事项:

   a : 参数列表

  1. 参数列表只需要写变量名, 不需要写变量数据类型
  2. 如果参数列表只有一个参数, 那么小括号可以省略 
  3. 只有一个参数  : x->{int t = 10; System.out.println(t*t);}
  4. 方法体只有一行,没有有返回值,可以省略大括号。  x->System.out.println(123);
  5. 方法体只有一句,且有返回值,则return和大括号可以一起省略。(x,y)->{return x+ y;}  =  (x,y) ->x+y

   b : 方法体: 与普通方法一样实现, 如果有返回值类型需要return

  1. 如果方法体只有一句代码, 大括号可以省略
  2. 如果方法有返回值类型, 且只有一句语句代码, 这一句就作为返回值类型, 那么return关键字和大括号可以同时省略

3.函数式接口

 

3.1函数式接口概述

一,说明函数式接口:

  1. 一个接口中只定义了一个抽象方法, 这个接口就称为函数式接口
  2. 函数式接口, 使用注解 : @FunctionalInterface 进行验证
  3. Lambda表达式只能使用于函数式接口, 作为函数式接口的实现类对象

 

二,为什么要有函数式接口?

  1. 函数 : 在java中就用于表示方法, 因为方法本身并不属于某一种数据类型, 是一种功能实现, 作为函数理解
  2. 当在Java代码中定义出一个方法功能时, 需要给方法设计参数列表, 数据类型  变量名, 数据类型  变量名..., 有时候需要的参数不仅仅是数据, 也需要对于数据的处理方式,处理理念(对于数据的处理方式应该是一个方法功能, 就是一个函数); 因此可能会需要将方法作为参数传递, 而又因为方法本身不是数据类型不能传递, 因此将方法封装在一个接口中, 这个接口就是为了封装一个方法而存在的; 以后通过将接口作为参数传递, 实际上就是在传递接口中所封装的方法功能.

举例 : 定义出一个方法功能, 实现对于两个整数的任意操作; 方法实际对于两个整数处理逻辑有很多种情况, 实际处理方式根据客户的需求决定

客户1 : 两个数和

客户2 : 两个数差

客户3 : 两数乘积

... 可能有无数客户对于此方法有不同的需求实现

   public int dealTwoNumber(int a, int b,需要提供对于a和b两数的处理方式){

}

3.2JDK提供的常用4种函数式接口

JDK在1.8版本中,提供了4种内置函数式接口, 为了在不同的场景下可以直接使用而不需要开发人员自己定义

 

  1. 消费型接口:

   Consumer<T>

      void accept(T t) : 对于T类型数据的处理方式

  1. 供给型接口:

   Supplier<T>

      T get(): 获取到T类型数据

  1. 函数型接口:

   Function<T,R>

      R apply(T) : 通过T类型参数, 获取到R类型数据结果

  1. 断言型接口 :

   Predicate<T>

       boolean test(T) : 提供一个T类型参数,判断出T类型参数是否符合规则和要求

 

3.3消费型接口

 

消费型接口:

   Consumer<T> : 来自于java.util.function包

      void accept(T t) : 如果方法中, 已经有一个参数是T类型, 但是方法中需要处理使用这个T类型参数, 可是使用T类型参数的方式可能有很多种, 具体是哪一种, 不能确定, 那么就可以将Consumer接口作为方法参数传递, 相当于传递accept方法, accept功能就是处理T类型参数

 

 

需求 : 定义出一个方法功能, 客户消费指定金额, 这些金额都如何消费的

客户1 : 花了500元, 买了一把大宝剑

客户2 : 花了400元, 买了一双球鞋

客户3 : 花了888元, 买了一套护肤品

... 还有很多很多的客户, 对于指定金额有不同的消费

public class Demo01_Consumer {
	public static void main(String[] args) {
		//客户1 : 花了500元, 买了一把大宝剑
		Consumer<Double> con = x->System.out.println("花了"+ x + "元,买了一把大宝剑");
		testConsumer(500,con);
		
		//客户2 : 花了400元, 买了一双球鞋
		Consumer<Double> con1 = x->{
			if(x <= 500) {
				System.out.println("非常节省,花了" + x + "元买了一双限量版球鞋");
			}else {
				System.out.println("先不买,攒钱");
			}
		};
		testConsumer(400,con1);
	}
	
	/*需求 : 定义出一个方法功能, 客户消费指定金额, 这些金额都如何消费的
	客户1 : 花了500元, 买了一把大宝剑
	客户2 : 花了400元, 买了一双球鞋
	客户3 : 花了888元, 买了一套护肤品
	... 还有很多很多的客户, 对于指定金额有不同的消费
	
	*
	*  分析 : 
	*    1) 需要知道客户消费金额, double money
	*    2) 需要知道客户对于money怎么消费, 于是可以将Cunsumer接口作为方法参数传递,相当于传递accept(Double money)
	*/
	
	public static void testConsumer(double money, Consumer<Double> con) {
         con.accept(money);		
	}
}

 

3.4供给型接口

供给型接口:

   Supplier<T> : 来自于java.util.function包

      T get(): 获取到T类型数据, 而不知道具体该如何获取, 那么可以将Supplier作为方法参数传递, 相当于传递唯一方法功能get, get()不需要参数列表, 只获取到T类型结果

 

需求 : 定义出一个方法功能, 方法能给客户返回一个ArrayList<Integer>容器, 返回的容器中包含几个数据, 集合中存储的数据有什么规律, 根据客户的实际要求决定

客户1 : 存储5个数据, 数据是30-80之间的随机数

客户2 : 存储8个数据, 数据是1-100之间的随机偶数

...

每一个客户都需要得到符合条件的集合容器

import java.util.ArrayList;
import java.util.Random;
import java.util.function.Supplier;
public class Demo02_Supplier {
	public static void main(String[] args) {
		//客户1 : 存储5个数据, 数据是30-80之间的随机数 
		// (0-50) + 30
		Supplier<Integer> sup = ()->new Random().nextInt(51) + 30;
		ArrayList<Integer> list1 = testSupplier(5,sup);
		System.out.println(list1);
		
		//客户2 : 存储8个数据, 数据是1-100之间的随机偶数
		Supplier<Integer> sup1 = ()->{
			Random ran = new Random();
			int number = ran.nextInt(100) + 1;
			while(true) {
				if(number % 2 == 0) {
					break;
				}else {
					number = ran.nextInt(100) + 1;
				}
			}
			return number;
		};
		System.out.println(testSupplier(8,sup1));
	}
	/*需求 : 定义出一个方法功能, 方法能给客户返回一个ArrayList<Integer>容器, 
	 * 返回的容器中包含几个数据, 集合中存储的数据有什么规律, 根据客户的实际要求决定
	客户1 : 存储5个数据, 数据是30-80之间的随机数
	客户2 : 存储8个数据, 数据是1-100之间的随机偶数
	...
	每一个客户都需要得到符合条件的集合容器
	
	*
	*  分析 :
	*    1) 第一个参数 : 需要集合中存储的数据个数  int count
	*    2) 第二个参数 : 需要给出集合中存储的数据获取规律, 可以将Supplier作为方法的参数传递,
	*    相当于传递T get()
	*/
	public static ArrayList<Integer> testSupplier(int count, Supplier<Integer> sup){
		ArrayList<Integer> list = new ArrayList<>();
		// 向list集合中添加元素数据
		for(int i = 1; i <= count ; i ++) {
			// 就需要Integer类型数据, 问题就是Integer类型数据的获取方式没法确定
			list.add(sup.get());
		}
		return list;
	}
}

3.5函数型接口

 

函数型接口:

   Function<T,R> : 来自于java.util.function包

      R apply(T) : 如果方法中已经有数据T类型, 想通过T类型参数, 获取到R类型数据结果, 具体怎么通过T计算出R类型, 方式有很多, 不能具体确定; 那么可以将Function接口作为方法参数传递, 相当于传递apply(T)

 

需求 : 定义出一个方法功能, 根据int类型x的值, 计算出另外一个int类型y的值, y获取方式根据客户的要求决定

客户1 : y值为x的2倍

客户2 : y值为x + 1

客户3 : y值为x-1

...

import java.util.function.Function;
public class Demo03_Function {
	public static void main(String[] args) {
		//客户1 : y值为x的2倍
		Function<Integer,Integer> fun1 = x->x*2;
		System.out.println(testFunction(6,fun1));// 12
		
		//客户2 : y值为x + 1
		Function<Integer,Integer> fun2 = x->x+1;
		System.out.println(testFunction(-9,fun2));// -8
		
		//客户3 : y值为x-1
		Function<Integer,Integer> fun3 = x->x-1;
		System.out.println(testFunction(88,fun3));

	}
	/*需求 : 定义出一个方法功能, 根据int类型x的值, 计算出另外一个int类型y的值, 
	y获取方式根据客户的要求决定
	客户1 : y值为x的2倍
	客户2 : y值为x + 1
	客户3 : y值为x-1
	...
	
	*
	*  分析:
	*    1) 第一个参数 : 需要提供int类型x int x
	*    2) 第二个参数 : 需要提供 计算y值方式(规律), 通过x得到y, 可以将Function接口作为方法参数传递,
	*    实际传递 int apply(int x)
	*/
	
	public static int testFunction(int x, Function<Integer,Integer> fun) {
		return fun.apply(x);
	}
}

3.6断言型接口

断言型接口

Predicate<T> : 来自于java.util.function包

       boolean test(T) : 如果方法中有T类型数据,需要判断出T类型参数是否符合规则和要求,返回boolean类型结果; 可以将Predicate断言型接口作为方法参数传递, 相当于传递test方法功能

提供特有方法:

      Predicate对象.and(Predicate pre);   两个条件都满足才返回真 。  eg:pre.and(x->x > 50).test(ele)

      Predicate对象.or(Predicate pre);      满足一个条件就返回真。

      Predicate对象.negate();                    返回的是调用者判断条件的取反。

需求 : 万能的数据筛选功能; 客户提供一个ArrayList<Integer>容器, 根据客户的要求, 将容器中符合条件的数据筛选出来, 放置到一个新的ArrayList<Integer>容器给客户, 筛选规则由客户决定

客户1 : 筛选出集合中所有小于100的偶数

客户2 : 筛选出集合中所有的奇数

客户3 : 筛选出集合中所有大于50的数

import java.util.ArrayList;
import java.util.function.Predicate;
public class Demo04_Predicate {
	public static void main(String[] args) {
		ArrayList<Integer> list = new ArrayList<>();
		list.add(12);
		list.add(105);
		list.add(99);
		list.add(86);
		list.add(7);
		list.add(120);
		// 客户1 : 筛选出集合中所有小于100的偶数
		Predicate<Integer> pre = x->x < 100 && x % 2 == 0;
		System.out.println(testPredicate(list,pre));
		
		// 客户2 : 筛选出集合中所有的奇数
		Predicate<Integer> pre1 = x->x % 2 != 0;
		System.out.println(testPredicate(list,pre1));
		
		// 客户3 : 筛选出集合中所有大于50的数
		Predicate<Integer> pre2 = x->x > 50;
		System.out.println(testPredicate(list,pre2));
	}
	
	/*需求 : 万能的数据筛选功能; 客户提供一个ArrayList<Integer>容器,
	根据客户的要求, 将容器中符合条件的数据筛选出来, 
	放置到一个新的ArrayList<Integer>容器给客户, 筛选规则由客户决定
	客户1 : 筛选出集合中所有小于100的偶数
	客户2 : 筛选出集合中所有的奇数
	客户3 : 筛选出集合中所有大于50的数
	...
	
	*   分析 : 
	*    1) 第一个参数 : 需要客户提供原始待筛选容器 ArrayList<Integer>
	*    2) 第二个参数 : 需要验证元素是否符合条件的规律(思想),将Predicate作为方法参数,
	*    为了传递test方法功能, boolean test(Integer)
	*/
	public static ArrayList<Integer> testPredicate(ArrayList<Integer> list,Predicate<Integer> pre){
		ArrayList<Integer> newList = new  ArrayList<>();
		// 遍历待筛选的集合, 获取到集合中的每一个元素
		for(int index = 0; index < list.size(); index++) {
			int ele = list.get(index);
			if(pre.test(ele)) {
				newList.add(ele);
			}
		}
		return newList;
	}
}

3.7方法引用

 有一个函数式接口, 可以使用Lambda表达式作为这个函数式接口的实现类对象; 如果Lambda表达式的方法体(Lambda体)实现过程已经被某类型中某个方法实现过了, 就不需要在Lambda表达式中再写一遍相同的代码, 可以使用方法引用代替Lambda表达式

   函数式接口名 变量名 = Lambda表达式;

   方法引用:

函数式接口名 变量名 = 类对象 :: 方法名; // 对象引用, 使用在非静态方法

函数式接口名 变量名 = 类型名 :: 方法名;// 类型引用, 使用在静态方法

public class Demo05_方法引用 {
	public static void main(String[] args) {
         // Lambda表达式实现:
		InterDemo id = x->System.out.println(x);
		id.print(5);// 5
		
		// System系统类中静态成员常量out, 调用返回PrintStream(打印流)对象, 对象名.调用println方法功能做标准数据输出
		// 方法引用实现InterDemo接口
		InterDemo id1 = System.out :: println;
		id1.print(99);// 99
		
		// 方法引用实现InterDemo2接口
		InterDemo2 id2 = x->System.out.println(Integer.parseInt(x) + 1);
		// 对象引用
		InterDemo2 id3 = new NumberPrint() :: print;
		id3.print("125");// 126
		
		// 类型引用
		InterDemo2 id4 = NumberPrint2 :: print;
		id4.print("88");// 93
	}
}

class NumberPrint{
	public void print(String x) {
		System.out.println(Integer.parseInt(x) + 1);
	}
}

class NumberPrint2{
	public static void print(String x) {
		System.out.println(Integer.parseInt(x) + 5);
	}
}

@FunctionalInterface
interface InterDemo{
	public abstract void print(int x);
}

@FunctionalInterface
interface InterDemo2{
	public abstract void print(String x);
}

 

4.StreamAPI

Stream()适用范围(获取数据):①,Collection:Collection对象.Stream()    (返回Stream类型的对象)     ②,Map:不能直接获取,需要获取:keySet().Stream();   valuesSet().Stream();    entrySet().Stream(); ③,数组的获取:Stream.of(数组);

  1. Collection接口中 : 自JDK8版本, 添加了几个默认(default)方法, 其中有一个方法功能stream() : 获取到当前集合的Stream<T>流资源
  2. Stream : 接口,来自于 java.util.stream , 表示一个集合的流资源, 作用就是可以对于当前的集合进行操作, 例如 : 过滤, 遍历, 筛选...
  3. Stream类型中常用的方法功能 : 会让集合的操作更加便捷
  • Stream<T>   filter(Predicate<T> pre) : 功能就是筛选集合中的数据, 根据参数给出的pre表示的规则, 验证都有哪些集合元素符合筛选条件的, 返回值类型Stream<T>, 返回的是接口的实现类对象, 可以继续调用Stream中方法功能
  •  forEach(Consumer<T> con) : 功能就是遍历集合中的每一个元素, 循环遍历过程中, 每一次获取到一个集合数据之后, 想要对这个集合数据进行什么操作, 可以通过参数con设计对于集合数据的使用过程, 返回值类型void
  • long count() : 获取到当前流资源中正在操作的元素的个数,返回值类型long
  • Stream<T>   limit(long size) : 将流资源中的前size个元素获取到, size之后的元素不再操作,返回值类型Stream<T>,可以继续调用Stream中方法功能
  • Stream<T>   skip(long n) : 丢弃流资源中前n个元素, 剩下的元素继续进行操作,返回值类型Stream<T>,可以继续调用Stream中方法功能
  • 只有count和Foreach 为终结方法,其他都为延迟方法。
  • import java.util.ArrayList;
    public class StreamDemo {
    	// 需求 : 定义出一个集合ArrayList<String>, 
    	// 中存储的都是一些人物的姓名, 筛选出所有姓张的, 名字中有3个字的名称数据
    	public static void main(String[] args) {
             ArrayList<String> listName = new ArrayList<>();
             listName.add("张三丰");
             listName.add("张无忌");
             listName.add("赵敏");
             listName.add("周芷若");
             listName.add("张五");
             listName.add("张成功");
             
             // 1. 通过stream方法获取到可以操作集合数据的Stream流资源
             // predicate : 唯一抽象方法 boolean test(T)
             listName.stream().filter(x->x.startsWith("张") && x.length() == 3)
                 .forEach(System.out :: println);
             
             // 2. 姓张的,名字长度为3的姓名有几个
             long number = listName.stream().filter(x->x.startsWith("张") && x.length() == 3).count();
             System.out.println(number);// 3
             
             // 3. 排除前两个姓名之外, 剩下的姓张的,名字长度为3的姓名
             listName.stream().skip(2).filter(x->x.startsWith("张") && x.length() == 3)
               .forEach(System.out :: println);
             
             
             System.out.println("-------");
             
             // 4. 前3个元素中,姓张的,名字长度为3的姓名
             listName.stream().limit(3).filter(x->x.startsWith("张") && x.length() == 3)
              .forEach(System.out :: println);
    	}
    }