Java8新特性
1.接口的默认方法
Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用default关键字即可,这个又叫做扩展方法
//Formula表示一个设计 计算公式 的接口
public interface Formula {
//计算
public double calculate(int a);
//开方
default double sqrt(int a){
return Math.sqrt(a);
}
}
main:
Formula f = new Formula() {
@Override
public double calculate(int a) {
return a+1;
}
};
System.out.println(f.calculate(4));
System.out.println(f.sqrt(8));
注意:现在接口还可以存在静态方法,可以使用 接口名.静态方法名 的形式直接调用
2.Lambda 表达式
2.1 认识Lambda表达式
例如:
public class LambdaTest1 {
public static void main(String[] args) {
//假如一个list机会中的元素要排序
List<String> list = Arrays.asList("hello","tom","apple","bbc");
//之前的排序我们可以这样写
Collections.sort(list, new Comparator<String>(){
@Override
public int compare(String o1, String o2) {
return -o1.compareTo(o2);
}
});
//使用Lambda表达式
Collections.sort(list,(String s1,String s2)->{
return s1.compareTo(s2);
});
//可以简写为
//1.大括号里面就一句代码
//2.编译器可以自动推导出参数类型
Collections.sort(list,(s1,s2)->s1.compareTo(s2));
System.out.println(list);
}
}
2.2 Functional接口
"函数式接口"是指仅仅只包含一个抽象方法的接口,每一个函数式接口类型的lambda表达式都会自动被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,为了确保你的接口确实是达到这个要求的,可以接口上添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。
例如:
public class LambdaTest2 {
public static void main(String[] args) {
//原来的内部类实现方式
test(new Action(){
@Override
public void run() {
System.out.println("run..");
}
});
//lambda表达式方法
test(()->System.out.println("run"));
//也可以先创建对象
Action a = ()->System.out.println("run...");
System.out.println(a.getClass());
test(a);
//接口中有泛型也可以,只关注方法的参数和返回值
Work<String,Integer> w = (v)->v.length();
run(w);
run((v)->v.length());
//如果参数只有一个,那么还可以这样简写: 去掉小括号
//注意代码就一句,作为返回值的话不用写return
run(v->v.length());
//有多句代码,就需要写{}了,并且需要写return
run(v->{
System.out.println("doWork..");
return v.length();
});
//观察下面代码是什么意思
run(v->1);
}
public static void test(Action a){
a.run();
a.doSomething();
}
public static void run(Work<String,Integer> a){
int i = a.doWork("hello");
System.out.println(i);
}
}
//这个注解不加也可以,加上只是为了让编译器检查
@FunctionalInterface
interface Action{
public void run();
default void doSomething(){
System.out.println("doSomething..");
}
}
//这个注解不加也可以,加上只是为了让编译器检查
@FunctionalInterface
interface Work<T,V>{
public V doWork(T t);
}
注意:lambda表达式无法访问接口的默认方法,lambda表达式只能去匹配对应接口中的唯一抽象方法。相当于lambda表达式只是对抽象方法的实现,并没有创建接口的实现类对象,因为我们只是想使用这个抽象方法的实现。
2.3 方法与构造函数引用
Java 8 允许你使用 :: 关键字来传递/引用方法(静态方法和非静态方法)
例如:
public class LambdaTest3 {
public static void main(String[] args) {
//正常是这样使用的
Action3 a1 = v->"接收的数字为:"+v;
System.out.println(a1.run(5));
//使用Lambda引用Integer类中的静态方法
Action3 a2 = Integer::toBinaryString;
System.out.println(a2.run(10));
//使用Lambda引用LambdaTest3类的对象中的非静态方法
LambdaTest3 t = new LambdaTest3();
Action3 a3 = t::test;
System.out.println(a3.run(20));
}
public String test(int i){
return "i="+i;
}
}
@FunctionalInterface
interface Action3{
public String run(int i);
}
注:相当于使用lambda表达式引用另一个类中方法的实现来作为Action3接口中run方法的实现,前提是俩个方法的参数列表和返回类型必须一致
能引用Integer类中的静态方法toBinaryString的原因是:Action3接口中只有一个方法且方法的参数类型和返回值类型与Integer类中的静态方法toBinaryString的参数类型、返回类型是一致的.那么能引用非静态方法的原因也是这样
下面是一个接口中带泛型的时候特殊例子: 可以使用 类名::非静态方法 的形式引用方法
public class LambdaTest3Pro {
public static void main(String[] args) {
Model m = new Model();
//这些写法都可以
//相当于变量v是run方法中接收的形参,然后使用v调用它的方法,v的类型是Model,因为这里使用了泛型
Action3Pro<Model> a1 = v->v.test1();
Action3Pro<Model> a1 = v->v.test2("hello");
Action3Pro<Model> a1 = v->v.test3();
a1.run(m);
//在这种情况下,还可以使用Model引用它的内部的方法
//但是必须满足以下要求:
//1.泛型的类型必须是Model
//2.引用的方法必须是无参的
//将来run方法中就自动会调用所传的对象m这个被引用的方法
Action3Pro<Model> a2 = Model::test1;
a2.run(m);
或者
Action3Pro<Model> a2 = Model::test3;
a2.run(m);
//编译报错,因为test2不是无参的
Action3Pro<Model> a2 = Model::test2;
a2.run(m);
}
}
interface Action3Pro<T>{
public void run(T t);
}
class Model{
public void test1(){
System.out.println("test1");
}
public void test2(String s){
System.out.println("test2");
}
public int test3(){
System.out.println("test3");
return 1;
}
}
注:
interface Action4Pro{
void run(Model m);
}
main:
//这样也可以,因为run方法的参数类型已经确定为Model了
Action4Pro ap = Model::test1;
Java 8 允许你使用 :: 关键字来引用构造函数
public class LambdaTest4 {
public static void main(String[] args) {
//Lambda表达式引用构造函数
//根据构造器的参数来自动匹配使用哪一个构造器
//这里执行create方法的时候会自动调用Action4类中的有参构造器
Action4Creater a = Action4::new;
Action4 a4 = a.create("zhangsan");
a4.say();
}
}
class Action4{
private String name;
public Action4() {
}
public Action4(String name) {
this.name = name;
}
public void say(){
System.out.println("name = "+name);
}
}
interface Action4Creater{
public Action4 create(String name);
}
2.4 lambda表达式中的变量访问
public class LambdaTest5 {
private static int j;
private int k;
public static void main(String[] args) {
LambdaTest5 t = new LambdaTest5();
t.test();
}
public void test(){
int num = 10;
j = 20;
k = 30;
//lambda表达式中可以访问成员变量也可以方法局部变量
Action5 a5 = (i)->System.out.println("操作后:i="+(i+num+j+k));
a5.run(1);
//但是这个被访问的变量默认变为final修饰的 不可再改变 否则编译不通过
//num = 60;
j = 50;
k = 70;
}
}
interface Action5{
public void run(int i);
}
2.5 java.util.function.Predicate接口
Predicate接口是用来支持java函数式编程新增的一个接口,使用这个接口和lambda表达式就可以用更少的代码给API中的方法添加更多的动态行为。
public class PredicateTest {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "html5","JavaScript", "C++", "hibernate", "PHP");
//全都显示
filter(list, name->true);
//全都不显示
filter(list, name->false);
//开头是J的语言
filter(list,name->name.startsWith("J"));
//5结尾的
filter(list,name->name.endsWith("5"));
//显示名字长度大于4
filter(list,name->name.length()>4);
System.out.println("-----------------------");
//名字以J开头并且长度大于4的
Predicate<String> c1 = name->name.startsWith("J");
Predicate<String> c2 = name->name.length()>4;
filter(list,c1.and(c2));
//名字不是以J开头
Predicate<String> c3 = (name)->name.startsWith("J");
filter(list,c3.negate());
//名字以J开头或者长度小于4的
Predicate<String> c4 = name->name.startsWith("J");
Predicate<String> c5 = name->name.length()<4;
filter(list,c4.or(c5));
//也可以直接使用Predicate接口中的静态方法
//名字为Java的
filter(list,Predicate.isEqual("Java"));
}
public static void filter(List<String> list, Predicate<String> condition) {
for(String name: list) {
if(condition.test(name)) {
System.out.println(name + " ");
}
}
}
}
2.6 java.util.function.Function<T, R> 接口
Function接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法
compose方法表示在某个方法之前执行
andThen方法表示在某个方法之后执行
注意:compose和andThen方法调用之后都会把对象自己本身返回,这可以方便链式编程
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
//注意: t->t是(t)->t的简写
//t->t是作为方法identity的返回值的,也就是Function类型对象
//类似于这样的写法:Function<Object, Object> f = t->t;
//那么f.apply("test") 返回字符串"test"
//传入什么则返回什么
static <T> Function<T, T> identity() {
return t -> t;
}
}
例如:
public class FunctionTest {
//静态内部类
private static class Student{
private String name;
public Student(String name){
this.name = name;
}
public String getName() {
return name;
}
}
public static void main(String[] args) {
/*用户注册输入一个名字tom*/
String name = "tom";
/*使用用户的输入的名字创建一个对象*/
Function<String, Student> f1 =(s)->new Student(s);
//注意上面的代码也可以写出这样,引用类中的构造器
//Function<String, Student> f1 =Student::new;
Student stu1 = f1.apply(name);
System.out.println(stu1.getName());
/*需求改变,使用name创建Student对象之前需要给name加一个前缀*/
Function<String,String> before = (s)->"briup_"+s;
//表示f1调用之前先执行before对象的方法,把before对象的方法返回结果作为f1对象方法的参数
Student stu2 = f1.compose(before).apply(name);
System.out.println(stu2.getName());
/*获得创建好的对象中的名字的长度*/
Function<Student,Integer> after = (stu)->stu.getName().length();
//before先调用方法,结果作为参数传给f1来调用方法,结果再作为参数传给after,结果就是我们接收的数据
int len = f1.compose(before).andThen(after).apply(name);
System.out.println(len);
}
}