引言:
JDK8之前在接口中添加新的方法就需要修改它的实现类
因此为了兼容老的接口JDK8就增加了接口新特性
在JDK8之前接口只能定义抽象方法,它的默认修饰符是public abstract
//JDK8之前的写法
interface MyInterface1{
void eat();
void sleep();
void play();
//此时等效于 public abstract void eat();
}
JDK8接口主要增加了默认方法和静态方法以及四个函数式接口
接口新增加的方法
默认方法:
默认方法可以有方法体,可以被实现类使用
注意1: 默认方法必须用default修饰
默认方法格式:
default 返回值类型 方法名(参数列表){
//方法体语句
}
如果不用default就会默认public abstract修饰,此时不能有方法体!
//定义一个接口
interface Myface{
public abstract void eat(); //抽象方法
//这里定义一个默认方法
default void fun() {
System.out.println("接口中的默认方法");
}
}
//定义一个实现类
class Test implements Myface{
//抽象方法依旧需要重写,先重写eat方法
//实现了某接口,就拥有了某接口的默认方法
@Override
public void eat(){
System.out.println("实现接口中的方法");
}
}
//测试类
class Test1{
public static void main(String[] arg){
Test t = new Test();
t.eat();
//使用接口中的默认方法
t.fun();
}
//输出结果:
//实现接口中的方法
//接口中的默认方法
}
注意2:重写默认方法时权限修饰符必须是public
借用以上代码
//定义一个接口
interface Myface{
public abstract void eat(); //抽象方法
//这里定义一个默认方法
default void fun() {
System.out.println("接口中的默认方法");
}
}
//定义一个实现类
class Test implements Myface{
//抽象方法依旧需要重写,先重写eat方法
@Override
public void eat(){
System.out.println("实现接口中的方法");
}
//重写接口中的fun方法
@Override
public void fun(){
System.out.println("重写接口中的默认方法");
}
}
//测试类
class Test1{
public static void main(String[] arg){
Test t = new Test();
t.eat();
//使用接口中的默认方法
t.fun();
}
此时引入一个问题?如果一个类实现两个接口,而这两个接口中有相同的默认方法,此时会发生什么?
因为在JDK8之前我们的接口都是抽象方法,即使出现相同的方法名也无关紧要,因为他们都没有方法体,实现类重写抽象方法也不会造成歧义性,而现在我们应该怎么做呢?
实现多个接口时如果有相同方法签名的默认方法,必须对该默认方法进行重写
//定义第一个接口,包含show方法
interface Myface1{
default void show() {
System.out.println("这是Myface1的show方法");
}
}
//定义第二个接口,包含show方法
interface Myface2{
default void show() {
System.out.println("这是Myface2的show方法");
}
}
//定义一个实现类,此时的实现类必须重写show方法。
class Test1 implements Myface1,Myface2{
//没有重写的话会报错,不重写不知道该实现哪个方法
@Override
public void show() {
System.out.println("重写两个接口中的show方法");
}
}
//测试类
class Test{
public static void main(String[] arg){
Test1 tt = new Test1();
tt.show();
}
}
上边的案例我们可以看到,实现类必须重写两个接口中相同的方法,
那么我们怎么使用两个接口中的方法呢?
基本格式: 接口名.super.方法名
这里使用上述代码,只对实现类进行修改
class Test1 implements Myface1,Myface2{
//没有重写的话会报错,不重写不知道该实现哪个方法
@Override
public void show() {
System.out.println("重写两个接口中的show方法");
}
//定义一个方法去调用接口中的方法
public void show2(){
Myface1.super.show(); //调用Myface1接口中的show方法
Myface2.super.show(); //调用Myface2接口中的show方法
}
}
默认方法注意:
1.父接口默认方法可以直接被实现类所使用,不强制重写(实现单接口,或者是多接口没有相同的默认方法)
2.实现类重写接口中的方法,权限修饰符必须是public
3.重写父接口中默认方法时如需要调用接口中的默认方法可以使用:接口名.super.方法名(参数列表);
4.如果一个类继承一个父类的同时实现多个接口,原则是:"父优先"
静态方法:
JDK8还可以在接口中定义静态方法
格式:
static 返回值类型 方法名(参数列表){
//方法体
}
静态方法的使用比较简单,有个注意事项
父接口中的静态方法不能被实现类所直接使用,只能通过:接口名.直接调用
代码演示:
interface MyJieKou{
//定义一个静态方法
static void run(){
System.out.println("接口中的静态方法执行");
}
}
//定义一个实现类
class TestJieKou implements MyJieKou{
//这里类不拥有接口中的静态方法
}
//测试类
class Test{
public static void main(String[] args){
TestJieKou t1 = new TestJieKou();
//此时不能使用 t1.run()
//如果需要接口中的静态方法,则可以直接调用
MyJieKou.run();
}
}
函数式接口:
在函数式接口的学习前如果你还不会使用Lambda表达式,那么你很有必要先去了解一下我的另一篇文章:Lambda表达式入门
前言:为什么要引入函数式接口?
我的理解是,为了满足不同用户对同一方法的不同要求(这里不是重载的理念)
我们知道方法的参数列表是一些数据类型,而有时我们还需要一些方法作为参数,然而方法又不属于数据类型,那么我们怎么来实现这个操作呢?
函数式接口是JDK1.8引入的新概念,我们学习函数式接口一般会学习四个,他们分别是:
1.消费型接口:
Consumer<T>
void accept(T t) : 对于T类型数据的处理方式
2.供给型接口:
Supplier<T>
T get(): 获取到T类型数据
3.函数型接口:
Function<T,R>
R apply(T) : 通过T类型参数, 获取到R类型数据结果
4.断言型接口 :
Predicate<T>
boolean test(T) : 提供一个T类型参数,判断出T类型参数是否符合规则和要求
接下来我们采用具体的案例来分别进行演示:
消费型接口
Consumer< T >
接受输入单个参数,不返回结果的操作
Consumer<T> 来自:java.util.function
方法:
accept(T t) :对给定的参数执行此操作
解释:如果有这么一个参数t,我们需要对它进行处理,
可处理的方式很多,我们不能确定如何操作,就可以使用消费型接口作为
参数,对传入的参数进行不同的处理,根据用户自己的需求去实现
案例分析:
需求 : 定义出一个方法功能, 客户消费指定金额, 这些金额都如何消费的
客户1 : 花了500元, 买了一把大宝剑
客户2 : 花了400元, 买了一双小花猫
… 还有很多很多的客户, 对于指定金额有不同的消费
那么用户这么多,需求又不一样,我们怎么定义这个方法呢,此时我们的函数式接口就可以很好的实现
代码分析:
import java.util.function.Consumer;
//使用的时候需要导包哦!
public class Test_消费型接口 {
public static void main(String[] args) {
//用户1
Consumer<Double> c1 = (x) -> System.out.println("花费"+x+"买一把大宝剑");
buy(500,c1);
//用户2,还可以对钱进行判断,执行相应的操作
Consumer<Double> c2 = (x) ->{
if(x > 300) {
System.out.println("太贵了,先不买吧");
}else {
System.out.println("花费"+x+"买一只小花猫");
}
};
buy(100,c2);
//输出结果:
//花费500.0买一把大宝剑
//花费100.0买一只小花猫
}
//定义一个方法去实现不同的用户完成不同的需求
//注意,泛型只能使用引用数据类型,所以要使用基本数据类型的包装类
public static void buy(double money,Consumer<Double> con) {
//接受同一个参数,执行不同的方法
con.accept(money);
}
}
供给型接口
Supplier< T >
不接受参数,返回需要的结果
Supplier< T > 来自 java.util.function
方法: T get() //返回用户指定的类型
获取T类型的结果,却不知道怎么获取
案例分析:
需求 : 定义出一个方法功能, 方法能给客户返回一个ArrayList容器, 返回的容器中包含几个数据, 集合中存储的数据有什么规律, 根据客户的实际要求决定
客户1 : 存储5个数据, 数据是30-80之间的随机数
客户2 : 存储8个数据, 数据是1-100之间的随机偶数
…
每一个客户都需要得到符合条件的集合容器
代码分析:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
public class Test_供给型接口 {
public static void main(String[] args) {
//定义用户1需要获取的方式
Supplier<Integer> sup1 = () ->{
Random ran = new Random();
int i = ran.nextInt(51)+30;
return i;
};
System.out.println(getList(5,sup1));
//定义用户2需要获取的方式
Supplier<Integer> sup2 = () ->{
Random ran = new Random();
while(true) {
int i = ran.nextInt(100)+1;
if(i % 2 == 0) {
return i;
}
}
};
System.out.println(getList(8,sup2));
//输出结果:
// [71, 60, 66, 64, 41]
// [66, 82, 10, 12, 76, 58, 56, 52]
}
//定义一个用户需要获取的方法
public static List<Integer> getList(int n,Supplier<Integer> sup){
List<Integer> list = new ArrayList<>();
for(int i = 0; i < n; i++) {
//get()方法来返回用户需要的结果
list.add(sup.get());
}
return list;
}
}
函数型接口
Function < T, R>
Function<T,R> 来自 java.util.function
方法: R apply(T t) 将此函数应用于指定的参数
解释:如果方法中已经有数据T类型,想通过T类型的参数,获得R类型的结果,具体怎么通过T计算出R类型的,方式很多,不能具体确定,那么就可以将Function接口作为方法参数传递,相当于传递apply();
需求 : 定义出一个方法功能, 根据int类型x的值, 计算出另外一个int类型y的值, y获取方式根据客户的要求决定
客户1 : y值为x的2倍
客户2 : y值为x + 1
客户3 : y值为x-1
…
代码分析:
import java.util.function.Function;
public class Test_函数式接口 {
public static void main(String[] args) {
//这里定义三个函数,第一个参数是传入的数据类型,第二个参数是需求的返回值的数据类型
Function<Integer,Integer> f1 = (x) -> 2*x;
Function<Integer,Integer> f2 = (x) ->x+1;
Function<Integer,Integer> f3 = (x) -> x-1;
System.out.println(get(10,f1));
System.out.println(get(10,f2));
System.out.println(get(10,f3));
}
//定义方法来传递不同的参数返回用户需求的方法
public static int get(int x,Function<Integer,Integer> fun) {
return fun.apply(x);
}
}
断言型接口
Predicate < T >
Predicate<T> 来自 java.util.function
方法: boolean test(T) 在给定的参数上评估这个谓词
解释:如果方法中有T类型数据,需要判断出T类型参数是否符合规则
和要求,返回boolean类型结果,可以将Predicate断言型接口作为
方法参数传递,相当于传递test方法功能
需求 : 万能的数据筛选功能; 客户提供一个ArrayList容器, 根据客户的要求, 将容器中符合条件的数据筛选出来, 放置到一个新的ArrayList容器给客户, 筛选规则由客户决定
客户1 : 筛选出集合中所有小于100的偶数
客户2 : 筛选出集合中所有的奇数
客户3 : 筛选出集合中所有大于50的数
代码分析:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class Test_断言型接口 {
public static void main(String[] args) {
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(120);
list2.add(43);
list2.add(68);
list2.add(98);
list2.add(23);
list2.add(76);
//创建一个集合
Predicate<Integer> p1 = (x) -> x > 100 && x%2 ==0;
System.out.println(panduan(list2,p1));
Predicate<Integer> p2 = (x) -> x % 2 != 0;
System.out.println(panduan(list2,p2));
Predicate<Integer> p3 = (x) -> x > 50;
System.out.println(panduan(list2,p3));
}
//传递一个集合,返回筛选后的集合
public static List<Integer> panduan(ArrayList<Integer> list,Predicate<Integer> pre){
ArrayList<Integer> li = new ArrayList<>();
for(int i = 0; i < list.size(); i++) {
int len = list.get(i);
//断言型返回一个boolean类型
if(pre.test(len)) {
li.add(len);
}
}
return li;
}
}