一、接口
1、概念
接口(interface)用来描述类应该做什么,而不指定他们具体应该如何做。一个类可以实现一个或多个接口
public interface Comparable
{
int compareTo(Object other);
}
任何实现Comparable接口的类都需要包含compareTo方法,这个方法有一个Object参数。
接口中的所有方法都自动为public方法,因此在接口中声明方法时,不必提供关键字public。
接口还可以定义常量,但绝不会有实例字段,实例字段和方法实现的任务应该由实现接口的那个类来完成。
因此接口可以看为没有实例字段的抽象类。
2、实现
让类实现一个接口,通常需要两个步骤
1、使用关键字implements将类声明为给定的接口
2、对接口中的方法提供定义
class Emloyee implements Comparable
public int compareTo(Object otherObject)
{
Employee other = (Employee) otherObejct;
return Double.compare(salary,other.salary);
}
在声明接口中,所有的方法自动为public, 不过在实现接口时,必须把方法声明为public。
为什么不能在Employee类中直接提供compareTo方法,而必须实现Comparbale接口呢?
主要原因是Java是一种强类型语言,在调用方法时候编译器要能检查这个方法确实存在
if(a[i].compareTo(a[j]>0))
{
//.......
}
编译器必须确认a[i]一定有一个compreTo方法,如果a是一个Comparable对象的数组,就 可以确保有compareTo方法,因为每个实现Comparable接口的类都必须提供这个方法的定义。
3、接口属性
接口不是类,不能使用new来实例化一个接口,但可以声明接口的变量。接口变量必须引用实现了这个接口的类对象
x = new Comparable //错误
Comparable x //正确
x = new Employee(....)
使用关键字instanceof检查一个对象是否实现某个特定的接口
if(anObject instanceof Comparable)
接口不能包含实例字段,但可以包含常量。与接口中的方法都自动设置为public一样,接口中的字段总是public static final。
4、接口与抽象类
Java的每个类只能扩展一个类,不能扩展第二个类,接口可以提供多重继承,同时还能避免多重继承的复杂性和低效性。
允许在接口增加静态方法,但通常将静态方法放在伴随类。
5、默认方法
可以为接口方法提供一个默认实现,必须用default修饰符标记这样一个方法。
6、接口与回调
回调(callback)是一种常见的程序设计模式,在这种模式下,可以指定某个特定事件发生时应该采取的动作。
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Instant;
public class TimerTest {
public static void main(String args[])
{
var listener = new TimePrinter();
var timer = new Timer(500,listener);
timer.start();
JOptionPane.showMessageDialog(null,"Quit program?");
System.exit(0);
}
}
class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
System.out.println("时间为:"+ Instant.ofEpochMilli(event.getWhen()));
Toolkit.getDefaultToolkit().beep();
}
}
使用TimerPrinter接口类重写了ActionListener接口中的actionPerformed方法,输出事件与响铃,
将TimerPrinter的实例listenter对象传入Timer()构造器中。timer.start()开始执行,Timer()构造器第一个参数为时间间隔,第二个为监听器对象,即回调函数或对象。
使用 JOptionPane.showMessageDialog(null,"Quit program?");弹出对话框,等待点击,第一个参数与为null,对话框显示在屏幕中央。
javax.swing.Timer
- Timer(int interval,ActionListener listener)
//构造一个定时器,每经过interval毫秒执行listenter监听器一次
- void start() //启动定时器,一旦启动调用监听器的actionPerformed
- void stop() //停止定时器
java.awt.Toolkit
- static Toolkit getDuefaultToolkit() //获得默认的工具箱
- void beep() //响铃
7、Comparator接口
如果我们假设希望按照长度增长的方法排序,要处理这种方法使用Arrays.sort(),这个方法还有第二种版本,有一个数组和一个比较器(comparator)
public interface Comparator<T>
{
int compare(T first,T second);
}
要利用长度比较,可以定义一个实现Comparator<String>的类
class lengthComparable implements Comparable<String>
{
public int compare(String first,String second)
{
return first.length()-second.length();
}
}
二、lambda表达式
1、概念
Lambda表达式,也可称为闭包,是JDK8的新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中),可以使代码变的更加简洁紧凑。Lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。
lambda表达式本质是一个匿名函数
public int compare(String frist,String second)
{
return frist.length()-second.length();
}
//可以转换为:
(String frist,String second)-> return frist.length()-second.length();
语法格式如下:
()->结果表达式
(参数1,参数2,参数n)->结果表达式
()->{代码块}
(参数1,参数2,参数n)->{代码块}
既可以理解为
(parameters) -> expression
(parameters) ->{ statements; }
1、 特别注意不能只在某些分支返回一个值,这是不合法的,如:
(int x)->{if(x>=0)return 1;}
2、即使lamdba表达式没有参数,仍然要提供空括号
()->{for(int i = 100;i>=0;i++) System.out.println(i);}
3、如果可以推导出参数类型,则可以忽略其类型:
Compartor<String> comp = (first,second)->first.length()-second.length();
2、函数式接口
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式,这种接口称为函数式接口。
定义一个函数式接口如下
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
}
那么就可以使用Lambda表达式来表示该接口的一个实现
GreetingService greetService1 = message -> System.out.println("Hello " + message);
以Arrays.sort方法为例,它的第二个参数需要一个Compartor实例,Compartor就是只有一个方法的接口,所以可以提供一个lambda表达式
Arrays.sort(words,(first,second)->first.length()-second.length());
函数式接口:
前提:只有一个抽象方法的接口、
结构:接口名 接口对象名 = lambda表达式
实际上,在Java中,lambda表达式所能做的也只是转换为函数式接口。
3、lambda表达式实现无参、有参抽象方法
很多函数接口的抽象方法是无参数的,如线程接口Runnable接口只有一个run()方法,这样的无参抽象方法在lambda表达式中使用“()”表示。而对于有参方法,使用如下结构
(参数1,参数2,参数n)->{代码块}
public class TimerTest {
interface Add
{
abstract int add(int a,int b);
}
public static void main(String args[])
{
Add aaa = (x,y)->x+y;
int result = aaa.add(1,2);
System.out.println(result);
}
}
4、lambda表达式可以更改类成员的变量,但无法更改final修饰的变量
public class TimerTest
{
public static void main(String args[])
{
var xxxx = new Variable();
xxxx.print();
xxxx.action();
xxxx.print();
}
}
interface VariableInterface2
{
void method();
}
class Variable
{
private int value = 100;
//利用lambda表达式修改常量值,函数式接口
public void action()
{
VariableInterface2 v = ()->{
value = -2;
};
v.method();
}
public void print()
{
System.out.println(value);
}
}
5、lambda表达式与异常处理
很多接口的抽象方法为了保证程序的安全性,会在定义时抛出异常,但lambda表达式中并没有抛出异常的语法,这是因为lambda表达式会默认抛出抽象方法原有的已存异常,当此方法被调用时候则需要进行异常处理。
三、方法引用
方法引用可以认为是Lambda表达式的一种特殊形式,lamdba表达式为单方法即可转换为方法的引用。
有时,lambda表达式涉及一个方法。例如假设你希望只要出现一个定时器时间就打印这个事件的对象。
可以调用:
var timer = new Timer(1000,event->System.out.println(event));
也可以直接把println方法传递到Timer构造器中,如下:
var timer = new Timer(1000,System.out::println);
表达式System.out::println是一个方法的引用(method reference),它指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法,在这个例子中,会生成一个ActionListener,.它的actionPerformed(ActionEvent e)方法要调用System.out.println(e)。
注意:编译器需要根据上下确定使用哪一个方法,在我们的例子中,方法的引用用System.out::println必须转换为一个包含ActionListener实例(void actionPerformed(ActionEvent e))这样会从10个重载的println方法中选出println(Object x)方法,因为Object与ActionEvent最匹配。
利用::运算符分割方法名与对象或类名,主要有三种情况
1、object::instanceMethod
2、class::staticMethod
3、class::instanceMethod
例1、引用方法成员
对象名::方法名
package cn.aaa.bbbb.java;
public class Test {
public static void main(String args[])
{
//对象名引用方法: MethodInterface B = A::add;
var A = new Method();
MethodInterface AA = A::add;
int BB = AA.add(1,2);
//lambda表达式,函数式接口:MethodInterface D = (a,b)->C.add(a,b);
var C = new Method();
MethodInterface CC = (a,b)->C.add(a,b);
int DD = CC.add(1,2);
//若方法为static,用类名引用 MethodInterface FF = MethodDemo::add;
MethodInterface F = MethodDemo::sub_add;
int FF = F.add(1,2);
}
}
interface MethodInterface
{
int add(int a,int b);
}
class Method
{
public int add(int a,int b)
{
return a+b;
}
}
class MethodDemo
{
public static int sub_add(int a,int b)
{
return a+b;
}
}
1,这种模式下, concatString()方法不能标记为静态方法,否则编译会报错。
2,这里的对象可以是父对象,比如可以使用:
super:: concatString
例2、引用静态方法
类名::静态方法名
public class TimerTest {
public static void main(String args[])
{
//方法的引用
StaticMethodInterface methodInterface = StaticMethodDemo::add;
int result = methodInterface.method(1,2);
System.out.println(result);
}
}
//创建函数式接口
interface StaticMethodInterface
{
int method(int a,int b);
}
class StaticMethodDemo
{
public static int add(int a,int b)
{
return a+b;
}
}
例3、类::实例方法
//
下表列出了方法引用的示例
注意,只有当lambda表达式的体只调用一个方法而不做其他操作时,才能把lambda表达式重写为方法引用。
例如 s->s.length()==0,这个有一个方法调用,但还有个比较,所以不能使用方法引用。
四、构造器的引用
构造器引用与方法引用很类似,只不过方法名为new。假设person::new是person构造器的一个引用,选择哪个构造器这取决于上下文,假设有一个字符串列表,可以把他转换为person对象数组,所有要在各个字符串上调用构造器。
ArraysList<String> name = ...;
Stream<person>stream = names.stream().map(person::new);
List<person> people = stream.collect(Collect.toList());
map方法会为各个列表元素调用person(String),如果有多个person构造器,编译器从上下文推到出一个带有String参数的构造器。
lambda表达式有3种引用构造的语法,分别为引用无参构造器,引用有参构造器,引用数组构造器。
例1、引用无参构造器
类名::new
package cn.aaa.bbbb.java;
public class Constructors {
public static void main(String args[])
{
ConstructorsInterface a = ConstructorsDemo::new;
//a.action(2);
a.action();//编译器根据上下文推断,从而调用无参构造器
}
}
interface ConstructorsInterface
{
ConstructorsDemo action();
}
class ConstructorsDemo
{
public ConstructorsDemo()
{
System.out.println("调用无参构造器");
}
public ConstructorsDemo(int i)
{
System.out.println("调用有参构造器,参数为"+i);
}
}
创建函数式接口, 测试类中有无参与有参两种构造器,接口抽象方法返回值为测试类名,并且无参。利用引用无参构造器创建接口对象,并且调用接口对象所创造的方法创建测试类对象,并查看输出结果。
例2、引用有参构造器
//代码同上,调用a.action(2);
例3、引用数组构造器
/**/
五、变量作用域
一个lambda表达式包含:参数、一个代码块、自由变量的值(这里指非参数而且不在代码中定义的变量)
考虑如下例子:
public static void repeatMessage(String str)
{
ActionListener listener = event->{
System.out.println(str);
};
}
调用如下
repeatMessage("Hello");
其中lamdba表达式中变量test,该变量并不是在lambda表达式中定义的。实际上test为repeatMessage方法中的一个参数,故称test为lambda表达式的自由变量,表示lambda表达式的数据结构必须存储test自由变量的值,即为Hello。我们说它被lambda表达式所捕获(captured)。
可以看到lambda表达式可以捕获外围作用域中变量的值。但捕获是具有限制的
1、在lamdba表达式,只能引用值不会改变的变量
例:
public static void countDown(int start) { ActionListener listener = event->{ start--; //ERROR,不能引用改变的变量 System.out.println(start); }; }2、在lamdba表达式中引用一个变量,而这个变量可能在外部改变
例:
public static void repeat(String str) { for(int i = 0;i<10;i++) ActionListener listener = event->{ System.out.println(i+str); //ERROR,i值在外部已经改变 }; }
综上:
1、lambda表达式中捕获的变量必须为事实最终变量。事实最终变量是指,这个变量在初始化后就不会再为它赋新值。
2、lambda表达式的体与嵌套块有相同的作用域,在lambda表达式声明一个局部变量同名的参数或局部变量是不合法的。
六、处理lambda表达式、Fuction接口
使用lambda表达式的重点是延迟执行(deferred)
使用lambda表达式都需要创建或调用已有的函数式接口,java.util.function包提供了很多预定义函数式接口,就是没有实现任何功能,仅用来封装lambda表达式对象。
该包常用接口是Function<T,R>接口,Function接口是函数式接口,所有有一个抽象方法。
T:被操作的类型,可理解为方法参数类型
R:操作结果类型,可以理解为方法的返回类型
Function接口提供了3个已实现的方法。如下表
常用的函数式接口
例1、使用lambda表达式拼接ip地址
package cn.aaa.bbbb.java;
import java.util.function.Function;
public class ConnectIp
{
public static void main(String args[])
{
Integer[] ip = {192,168,1,1};
FunctionDemo functionDemo = new FunctionDemo();
System.out.println(functionDemo.function.apply(ip));
}
}
class FunctionDemo
{
//方法参数为Integer数组
Function<Integer[], String> function = (ip)->{
//创建字符序列
StringBuilder str = new StringBuilder();
for (Integer num:ip)
{
//添加数组元素,并以.分割,形如num.num.num.num.
str.append(num);
str.append('.');
}
str.deleteCharAt(str.length()-1);//删除最后追加的.
//以字符串返回到参数ip中
return str.toString();
};
}
例2、将一个动作重复n次
1、使用Runnable函数式接口,利用lambda表达式
public class hello_world{
public static void main(String[] args) {
repeat(10,()->System.out.println("hello"));
}
public static void repeat(int n,Runnable action)
{
for (int i = 0;i<n;i++)
action.run();
}
}
当调用action.run()时会执行lambda表达式的主体
2、利用方法的引用实现函数式接口
public class hello_world{
public static void main(String[] args) {
//repeat(10,()->System.out.println("hello"));
Runnable PP = Print::printf;
repeat(10,PP);
}
public static void repeat(int n,Runnable action)
{
for (int i = 0;i<n;i++)
action.run();
}
}
class Print
{
public static void printf()
{
System.out.println("hello world");
}
}
定义测试类Print,创建接口对象PP,PP引用测试类中的printf方法, 再通过repeat实现
例3、利用IntConsumer函数式接口,显示动作出现在哪一次迭代
public class Math_{
public static void main(String[] args)
{
//利用lambda表达式实现函数式接口
repeat(10,i->System.out.println("Countdown:"+(9-i)));
}
public static void repeat(int n,IntConsumer action)
{
for (int i = 0;i<n;i++)
action.accept(i);
}
}
interface IntConsumer
{
void accept(int value);
}