欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

接口和lambda表达式笔记

程序员文章站 2022-08-12 20:54:40
涉及到接口的方法和几种特殊的接口,lambda表达式的作用、目的,以及使用过程中需要注意的一些地方 ......

接口
接口是双方,即服务提供方和想让它们的对象对服务是可用的那些类,之间约定的一种机制。
声明一个接口
public interface IntSequence{ //不提供实现,则该方法为抽象方法,且默认为公有方法,不必为hasNext和next声明为public
boolean hasNext();
int next();
}
实现接口
public class SquareSequence implements IntSequence{//implements 指示SquareSequence类想要遵从 IntSequence 接口
private int i;

public boolean hasNext(){//实现类必须将接口方法声明为public,因为默认情况下,它们在包级别可访问
return true;
}

public int next(){
i++;
return i*i;
}
}
//返回无穷多个平方元素,并且一个对象可以一次一个的处理所有的平方元素

SquareSequence s = new SquareSequence();
double avg = avgrage(s,100);

另外一个IntSequence的实现DigitSequence
public class DigitSequence implements IntSequence{

private int number;

public DigitSequence(int n){
number = n;
}

@Override
public boolean hasNext() {
return number!=0;
}

@Override
public int next() {
int result = number%10;
number = number/10;
return result;
}

public int rest(){
return number;
}
}
当子类型T的任何值不需要转换就能赋值给父类型S的变量时,类型S是类型T(子类)的父类,例如,IntSequence 接口是DigitSequence类的父类。
虽然声明接口类型的变量是可能的,但是永远不会存在类型为接口的对象。所有对象都是类的实例。
IntSequence digits = new DigitSequence(9876);
System.out.println(average.average(digits,100));

从父类转换为子类,即当知道父类型IntSequence存储的就是子类型DigitSequence对象时。
IntSequence s = ...;
DigitSequence digits = (DigitSequence) s;
System.out.println(digits.rest());
这种情况下转换是必须的因为rest方法为digitSequence所特有的。
当然最好在强制转换前使用instantof来避免转换异常。
if(s instantof DigitSequence){
DigitSequence digits = (DigitSequence) s;
...
}

继承接口
public interface Closeable{
void close();
}
public interface Channel extends Closeable{
boolean isOpen();
}
即若想实现channel接口需要提供两个方法。

实现多个接口,可以在implements后面添加多个接口。

常量
定义在接口内的任何变量自动为 public static final

静态方法和默认方法(均在java1.8后可以实现)
public interface IntSequence {

public int next();

public static IntSequence digitsOf(int n){ //静态方法
return new DigitSequence(n);
}

default boolean hasNext(){ //默认方法
return true;
}

解决默认方法冲突
public interface Identified {
default int getID(){return Math.abs(hashCode());}
}
public interface Person {
String getName();
default int getID(){return 0;}
}
public class Employee implements Identified,Person{
@Override
public String getName() {
return null;
}

@Override
public int getID() {
return Person.super.getID();//使用super关键字,可以调用父类型的方法。
}

lambda表达式(匿名函数,没有函数名称的函数)
用处:增强可读性,并且使代码更加简洁
同时当编译器可以推断出变量类型时,可以省略()内的变量类型,且无需为lambda表达式指定返回类型
Runnable task1 = new Runnable() {
@Override
public void run() {
System.out.println("not Lambda");
}
};

Runnable task2 = ()->{System.out.println("Lambda");};

对于Arrays.sort方法,该方法的第二个参数要求是一个comparator接口。
Arrays.sort(strings,(x,y)->x.compareToIgnoreCase(y));
Arrays.sort(strings,String::compareToIgnoreCase);//方法引用,与上式等同
对于类似于 操作符::将方法名称与类或对象名称分隔开有以下三种形式
1. 类::实例方法 比如:String::compareToIgnoreCase 与(x,y)->x.compareToIgnoreCase(y)
2. 类::静态方法 比如:Object::isNull 与x->Object.isNull(x)
3. 对象::实例方法 比如:System.out:;println 与System.out.println(x)
构造函数引用于方法引用类似,区别在于引用的方法名称都是new 比如 Employee::new

使用lambda表达式的目的
1.实现延迟执行 (在另一个单独线程中运行代码、多次运行、恰当时刻运行(排序中比较操作的执行))
多次执行
public static void repeat(int n,Runnable r){
for (int i=0;i<n;i++)r.run();
}

repeat(2,()->System.out.println("ll"));

带有参数的lambda表达式使用
public static void repeat(int n,IntConsumer r){
for (int i=0;i<n;i++)r.accept(i);
}

public interface IntConsumer{
void accept(int value);
}

repeat(10,i->System.out.println("Countdown:"+(9-i)));
2.选择函数式接口
大多数编程语言的函数类型都是结构化的。比如为了将两个字符串映射到一个整数的函数。需要使用类似Function<String,String,Integer>或者(String,String)->int
常用函数式接口
函数式接口 参数类型 返回类型 抽象方法名称 描述 其他方法
Runnable none void run 执行一个没有参数或返回值的操作
Supplier<T> none T get 提供类型为T的值
Consumer<T> T void accept 处理T类型的值
BiConsumer<T,U> T,U void accept 处理T类型和U类型的值
Function<T,R> T R apply 参数类型为T的函数
BiFunction<T,U,R> T,U R apply 参数列行为T,U的函数
UnaryOperator<T> T T apply 对类型T进行一元操作
BinaryOperator<T> T,T T apply 对类型T进行二元操作
Predicate<T> T boolean test 布尔值函数
BiPredicate<T,U> T,U boolean test 带有两个参数的布尔值函数
3.实现自己的函数式接口
@FunctionalInterface //注释标记函数式接口
public interface PixelFunction{ //实现(int,int)-> color
Color apply(int x,int y);
}

lambda表达式的作用域
lambda表达式的方法体与嵌套代码块有着相同的作用域,因此,也适用同样的命名冲突和屏蔽规则。在lambda表达式中不允许声明一个与局部变量同名的参数或局部变量
int f;
Comparator<String> comparator = (f,s)->f.length()-s.length(); //f与上面的int f重名。

访问来自闭包作用域的变量
public static void repeat(String msg ,int count){
Runnable r = ()->{
for (int i =0;i<count;i++){//将lambda表达式转变为带有一个方法的对象,这样*变量的值就可以复制到带对象的实例变量中
System.out.println(msg);
}
};
new Thread(r).start();
}
lambda表达式有三个部分: 1.代码块 2.参数 3.*变量的值(既不是参数变量也不是代码内部定义的变量)
描述带有*变量的代码块的技术名词是闭包,在java中,lambda表达式就是闭包。
值得注意的是lambda表达式可以捕获闭合作用域的变量值而不是变量。
public static void repeat(String msg ,int count){
for(int i = 0;i<count;i++){
new Thread(()->{System.out.println(i);});//不能捕获i,因为i会变化,lambda表达式只能访问来自闭合作用域的final局部变量。
}
}

public static void repeat(String[] msg ,int count){
for(String arg:msg){
new Thread(()->{System.out.println(msg);}).start();
//这个是可以的,因为每一次迭代都会创建一个新的arg变量,作用域是单个循环,而上面的i的作用域是整个循环
}
}
值得注意的是,lambda表达式不能改变任何捕获的变量。例如修改上面的arg

高阶函数
1. 返回函数的方法
public static Comparator<String> compareInDirection(int direction){
return (x,y)->direction*x.compareTo(y);
}
调用 compareInDirection(1)产生升序比较器, 调用 compareInDirection(-1)产生降序比较器
结果可以传递个另一个期望这个接口的方法(Arrays.sort)
Arrays.sort(friends,compareInDirection(1))
2. 修改函数的方法
public static Comparator<String> reverse(Comparator<String> comp){
return (x,y)->comp.compare(x,y);
}
它接收一个函数并返回一个修改后的函数
reverse(String::compareToIgnoreCase)//获得大小写不敏感的降序

局部内部类
public static IntSequence randomInts(int low,int high){
class RandomSequence implements IntSequence{
public int next(){return low+generator.nextInt(high-low);};
public boolean hasNext() {return true;}
}
return new RandomSequence();
};
创建局部类的好处,其一,类名称隐藏在方法范围内。其二,局部类的方法可以访问来自闭合作用域的变量,就像lambda表达式的变量。

匿名类(上面的RandomSequence只调用了一次,用来构造返回值,所以可以使用匿名类)
public static IntSequence randomInts(int low,int high){
return new IntSequence(){
public int next(){return low+generator.nextInt(high-low);};
public boolean hasNext() {return true;}
};
};