1、Lambda表达式
Lambda表达式就是简化的匿名内部类对象,使用它来创建只有一个抽象方法的接口的实现类的实例。只有一个抽象方法的接口称为函数式接口,函数式接口可以包含默认方法和类方法,当一个接口使用@FunctionalInterface来标注,那么该接口只能是一个函数式接口,如下所示的Func:
@FunctionalInterface
public interface Func<P, R>
{
R apply(P p);
}
Lambda表达式由形参列表()、箭头->、代码块{}组成:形参的参数类型可以省略(因为与其所赋值的接口中抽象方法的参数类型一致);如果只有一个参数那么形参列表的圆括号可以省略;如果代码块中只有一条语句,而且该语句不是定义变量或对象,那么可以省略花括号,且该语句后面不跟分号;如果代码块中只有一条return语句,可以省略return标识符。
Lambda表达式中对所在方法的局部变量、所在类的数据成员的访问规则与局部内部类相同,即访问局部变量的话如果这个局部变量不是final类型的话其会隐式成为final,其值不能改变。Lambda表达式内变量不能与局部变量名相同。Lambda内可以访问和改变所在类的数据成员。
Lambda表达式的本质就是使用比匿名内部类更简洁的语法来创建函数式接口的实例,匿名内部类对象与lambda表达式有两点不同:this在lambda指的是lambda所在类的对象,在匿名内部类中则是当前匿名对象;匿名内部对象访问所在方法的局部变量的话,局部变量必须显示声明为final类型,而lambda是隐式的将局部变量作为final类型,如果对局部变量进行修改的话会出错。
interface Product
{
public String getName(int No);
}
class Bar
{
public void test(Product p)
{
p.getName(100);
}
public void func1() //使用普通内部类
{
class GoodProduct implements Product
{
public String getName(int No)
{
return "name";
}
}
test(new GoodProduct());
}
public void func2() //使用匿名内部类
{
test(new Product(){
public String getName(int No)
{
return "name";
}
});
}
public void func3() //使用Lambda表达式
{
test((int No)->{return "name";}); //省略写法:test(No->"name");
}
}
因为Lambda表达式的结果就是对象实例,所以可以使用Lambda表达式进行赋值:
//Runnable是java中一个函数式接口,只包含一个抽象方法run()
Runnable r = ()->{
System.out.println("run() start");
};
r.run();
Lambda表达式的目标类型必须是明确的函数式接口,如上面的Bar类中func3方法中将Lambda表达式赋给函数式接口Product类型的参数,编译器就知道传入的Lambda表达式的目标类型就是函数式接口。再如上面将Lambda表达式赋值给函数式接口Runnable类型,编译器就知道传入的Lambda表达式的目标类型就是函数式接口。而如果将Lambda表达式直接赋值给Object对象则会失败,因为Lambda表达式的目标类型必须是明确的函数式接口,而此时编译器不知道该Lambda表达式哪个函数式接口,解决方法是使用函数式接口对Lambda表达式进行强制类型抓换:
Object o = ()->{}; //错误
Object o = (Runnable)()->{};
Lambda表达式常见的用法就是将Lambda表达式赋值给函数式接口类型的变量或参数,以及使用函数式接口对Lambda表达式进行强制类型转换。
java 8在java.util.function包下定义了大量的函数式接口,我们可以直接拿来使用,而不用再自己定义接口:
xxxConsumer:包含一个accept()抽象方法,它没有返回值,只是对参数进行处理,当然处理的逻辑由Lambda表达式来实现。Consumer接口的accept()接收一个对象类型参数,IntConsumer接口的accept()接收一个int类型参数,longConsumer、doubleConsumer以此类推。
xxxFunction:包含一个apply()抽象方法,该方法对参数进行处理或转换,然后返回一个新的值。Function接口的apply()的参数类型和返回类型都是对象,如果参数是基本类型的话可以使用IntFunction、DoubleFunction等,参数和返回值都是基本类型的话可以使用IntToDoubleFunction、IntToLongFunction等,如果参数类型和返回类型相同的话可以使用UnaryOperator接口,如果需要接收两个参数的话可以使用接口BiFunction,如果两个参数与返回值类型都相同的话可以使用BinaryOperator。
xxxPredicate:包含一个test()抽象方法,用来判断参数是否满足指定条件,返回一个boolean值。有传入参数类型为对象类型的Predicate,传入参数类型为基本类型的IntPredicate、DoublePredicate等。
xxxSupplier:包含一个getAsXXX()的抽象方法,该方法无参数,只是实现特定的计算来返回一个数据。有返回对象类型的Supplier、返回基本类型的BooleanSupplier、IntSupplier等。
下面为自己定义函数式接口和直接使用Consumer接口的对比:
@FunctionalInterface
interface MyConsumer<T>
{
void accept(T object);
}
class Foo
{
public void func(MyConsumer<Integer> action)
{
Integer value = 100;
action.accept(value);
}
}
import java.util.function.*;
class Foo
{
public void func(Consumer<Integer> action)
{
Integer value = 100;
action.accept(value);
}
}
只有一个参数那么形参列表的圆括号可以省略。
代码块中只有一条语句,那么可以省略花括号。
代码块中只有一条return语句,可以省略return标识符。
2、方法引用
方法引用(Method Reference,又称方法参考)就是Lambda表达式,有时候你会发现某些类的静态方法的操作流程与你自行定义的Lambda表达式相同,这时候就可以使用静态方法的引用,通过双冒号来实现。比如下面的静态方法引用StringOrder::byLength、Integer::compare:
import java.util.Arrays;;
class StringOrder
{
public static int byLength(String s1, String s2)
{
return s1.length() - s2.length();
}
}
public class Test
{
public static void main(String[] args)
{
String[] names = {"java", "c++", "c#"};
Arrays.sort(names, StringOrder::byLength /*(s1, s2)->StringOrder.byLength(s1, s2)*/); //以字符串长短排序
}
}
Integer[] ary = new Integer[]{3, 5, 0, 8, 1};
Arrays.parallelSort(ary, Integer::compare); //Arrays.parallelSort()为指定的数组排序,这行语句与下面的语句等价
Arrays.parallelSort(ary, (n1, n2)->{
if(n1 > n2)
return 1;
else if(n1 < n2)
return -1;
else
return 0;
});
除了指向静态方法的引用,还有指向某个对象的实例方法的引用、指向某个类型的实例方法的引用、指向构造方法的引用:
package xu;
import java.util.*;
import java.util.stream.*;
public class Test
{
public static void main(String[] args)
{
//1、指向某个对象的实例方法的引用
IntStream is = IntStream.builder().add(10).add(13).add(-1).build(); //IntStream的使用参考“集合(1)”章节
is.forEach(System.out::println);//out是静态成员,该语句等价于 is.forEach(item->System.out.println(item));
//2、指向某个类型的实例方法的引用
String str = Stream.of("A", "is", "a", "dog").reduce("", String::concat); //Stream相关使用参考“集合(1)”章节,该语句相当于String str = Stream.of("A", "is", "a", "dog").reduce("", (s1, s2)->s1 + s2)
System.out.println(str); //输出为"Aisadog"
String[] names = {"java", "c++", "c#"};
Arrays.sort(names, String::compareTo); //以字符串大小(字典书序)排序
Arrays.sort(names, String::compareToIgnoreCase); //以字符串大小(字典书序)排序,忽略大小写差异
//3、指向构造方法的引用
//Stream.toArray()无参数版本返回包含此流中元素的数组,返回类型为Object类型的数组
Object[] oAry = Stream.of(1, 2, 3, 4).toArray();
System.out.println(Arrays.toString(oAry)); //输出为 [1, 2, 3, 4]
//Stream.toArray()的另一个版本会返回一个指定类型的数组,该版本方法需要一个参数类型为IntFunction接口对象,toArray()返回的数组类型即为IntFunction的泛型类型
//IntFunction接口中的抽象函数与数组的构造函数一致
/*
public interface IntFunction<R>
{
R apply(int value);
}
int[] a = new int[3]; //定义有3个int值的数组
*/
String[] sAry = Stream.of("A", "is", "a", "dog").toArray(String[]::new);
System.out.println(Arrays.toString(sAry)); //输出为[A, is, a, dog]
}
}
3、Arrays与Lambda表达式的应用
package xu;
import java.util.Arrays;
import java.util.Comparator;
public class Test
{
public static void main(String[] args)
{
//根据元素大小倒叙排序
Integer[] ary1 = new Integer[]{9, 4, 6, -1, -2, 3, 10, 0};
Comparator<Integer> cmp = (o1, o2)->
{
if(o1 < o2)
return 1;
else if(o1 > o2)
return -1;
else
return 0;
};
Arrays.sort(ary1, cmp);
System.out.println(Arrays.toString(ary1));
//根据元素长度排序
String[] ary2 = new String[]{"1234", "123", "12", "12345", "123", "12"};
Arrays.parallelSort(ary2, (o1, o2)->o1.length() - o2.length());
System.out.println(Arrays.toString(ary2));
//给数组元素赋值
int[] ary4 = new int[5];
Arrays.setAll(ary4, index->index * 5); //元素值是: 元素索引 * 5
Arrays.parallelSetAll(ary4, index->ary4[index] * 5); //元素值是: 原元素值 * 5
System.out.println(Arrays.toString(ary4));
//根据当前元素和前一元素给数组元素赋值
int[] ary3 = new int[]{3, -4, 25, 16, 30, 18};
Arrays.parallelPrefix(ary3, (left, right)->left * right);//left为数组中前一索引的元素,right为当前索引元素,right为第一个元素时,left值为1
System.out.println(Arrays.toString(ary3));
}
}