疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

接口和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;}
};
};
好文要顶 关注我 收藏该文