Java8-Lambda

Java8-Lambda

Java 8 的最大变化是引入了 Lambda 表达式——一种紧凑的、传递行为的方式

引出Lambda表达式

例子:

使用匿名内部类将行为和按钮单击进行关联



button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent event) {
        System.out.println("button clicked");
    }
});

我们创建了一个新对象,它实现了 ActionListener 接口。这个接口只有一个方法 actionPerformed ,当用户点击屏幕上的按钮时, button 就会调用这个方法。匿名内部类实现了该方法

这实际上是一个代码即数据的例子——我们给按钮传递了一个代表某种行为的对象。

设计匿名内部类的目的,就是为了方便 Java 程序员将代码作为数据传递。不过,匿名内部类还是不够简便。为了调用一行重要的逻辑代码,不得不加上 4 行冗繁的样板代码

使用 Lambda 表达式将行为和按钮单击进行关联


button.addActionListener(event -> System.out.println("button clicked"));

和传入一个实现某接口的对象不同,我们传入了一段代码块——一个没有名字的函数

event 是参数名,和上面匿名内部类示例中的是同一个参数。 -> 将参数和 Lambda 表达式的主体分开,而主体是用户点击按钮时会运行的一些代码

和使用匿名内部类的另一处不同在于声明 event 参数的方式。使用匿名内部类时需要显式地声明参数类型 ActionEvent event ,而在 Lambda 表达式中无需指定类型,程序依然以
编译.这是因为 javac 根据程序的上下文( addActionListener 方法的签名)在后台推断出了参数 event 的类型。这意味着如果参数类型不言而明,则无需显式指定

看几种 Lambda 表达式的不同形式:

  • 1:

Runnable runnable =
                () -> System.out.println("hello world");

  • 2:

ActionListener actionListener = 
                
                event -> System.out.println("button clicked");

  • 3:

Runnable runnable1 =
                () ->
                {
                    System.out.println("hello");
                    System.out.println("world");
                };


  • 4:

BinaryOperator<Long> add = 
                (x,y) -> x+y;

  • 5:

BinaryOperator<Long> addExplicit = 
                (Long x,Long y) -> x + y;

1中所示的 Lambda 表达式不包含参数,使用空括号 () 表示没有参数。该 Lambda 表达式实现了 Runnable 接口,该接口也只有一个 run 方法,没有参数,且返回类型为 void

2中所示的 Lambda 表达式包含且只包含一个参数,可省略参数的括号

如3所示Lambda 表达式的主体不仅可以是一个表达式,而且也可以是一段代码块,使用大括号( {} )将代码块括起来,该代码块和普通方法遵循的规则别无二致,可以用返回或抛出异常来退出。只有一行代码的 Lambda 表达式也可使用大括号,用以明确 Lambda表达式从何处开始、到哪里结束

Lambda 表达式也可以表示包含多个参数的方法,如4所示。这时就有必要思考怎样去阅读该 Lambda 表达式。这行代码并不是将两个数字相加,而是创建了一个函数,用来计算两个数字相加的结果。变量 add 的类型是 BinaryOperator ,它不是两个数字的和,而是将两个数字相加的那行代码

所有 Lambda 表达式中的参数类型都是由编译器推断得出的。这当然不错,但有时最好也可以显式声明参数类型,此时就需要使用小括号将参数括起来,多个参数的情况也是如此。如5所示

目标类型是指 Lambda 表达式所在上下文环境的类型。比如,将 Lambda 表达式赋值给一个局部变量,或传递给一个方法作为参数,局部变量或方法参数的类型就是 Lambda 表达式的目标类型

Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。目标类型也不是一个全新的概念

lambda 表达式的语法格式如下:


(parameters) -> expression
或
(parameters) ->{ statements; }

以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。

  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。

  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。

  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值

简单测试:


public class M2 {


    interface MathOperation{
        int operation(int a,int b);
    }

    interface GreetingService{
        void sayMessage(String message);
    }

    private int operate(int a, int b, MathOperation mathOperation){
        return mathOperation.operation(a, b);
    }

    public static void main(String[] args) {

        M2 m2 = new M2();

        // 类型声明
        MathOperation addition  = (int a,int b) -> a + b;

              // 不用类型声明
        MathOperation subtraction = (a, b) -> a - b;

        // 大括号中的返回语句
        MathOperation multiplication = (int a, int b) -> { return a * b; };

        // 没有大括号及返回语句
        MathOperation division = (int a, int b) -> a / b;

        // 不用括号
        GreetingService greetService1 = message ->
                System.out.println("Hello " + message);


        // 用括号
        GreetingService greetService2 = (message) ->
                System.out.println("Hello " + message);

        greetService1.sayMessage("Runoob");
        greetService2.sayMessage("Google");

        System.out.println("10 + 5 = " + m2.operate(10, 5, addition));
        System.out.println("10 - 5 = " + m2.operate(10, 5, subtraction));
        System.out.println("10 x 5 = " + m2.operate(10, 5, multiplication));
        System.out.println("10 / 5 = " + m2.operate(10, 5, division));



    }
}


引用值,而不是变量

使用匿名内部类,需要引用它所在方法里的变量。这时,需要将变量声明为 final

将变量声明为 final ,意味着不能为其重复赋值。同时也意味着在使用 final 变量时,实际上是在使用赋给该变量的一个特定的值

匿名内部类中使用 final 局部变量
final String name = getUserName();
button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent event) {
            System.out.println("hi " + name);
        }
});

Java 8 虽然放松了这一限制,可以引用非 final 变量,但是该变量在既成事实上必须是final 。虽然无需将变量声明为 final ,但在 Lambda 表达式中,也无法用作非终态变量。如果坚持用作非终态变量,编译器就会报错

既成事实上的 final 是指只能给该变量赋值一次。换句话说,Lambda 表达式引用的是值,
而不是变量

Lambda 表达式中引用既成事实上的 final 变量

String name = getUserName();
button.addActionListener(event -> System.out.println("hi " + name));

如果你试图给该变量多次赋值,然后在 Lambda 表达式中引用它,编译器就会报错

未使用既成事实上的 final 变量,导致无法通过编译


String name = getUserName();
name = formatUserName(name);
button.addActionListener(event -> System.out.println("hi " + name));

例子:


public class M3 {

    final static String STRING = "hello";

    interface Greet{
        void say(String Msg);
    }

    public static void main(String[] args) {

        Greet greet = Msg ->
        {
            System.out.println(STRING + Msg);
        };

        greet.say("lolo");

    }
}

可以直接在 lambda 表达式中访问外层的局部变量


public class M4 {


    interface Converter<T1,T2>{
        void convert(int i);
    }
    public static void main(String[] args) {

        final int num = 1;

        Converter<Integer,String> stringConverter =

        (param) ->

        {
            System.out.println(String.valueOf(param + num));
        };

        stringConverter.convert(6);

    }
}


在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。


String first = "";  
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());  //编译会出错 

lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)


int num = 1;  
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
num = 5;  
//报错信息:Local variable num defined in an enclosing scope must be final or effectively 
 final

Lambda 表达式本身的类型:函数接口

函数接口是只有一个抽象方法的接口,用作 Lambda 表达式的类型。

使用只有一个方法的接口来表示某特定方法并反复使用,是很早就有的习惯。使用 Swing编写过用户界面的人对这种方式都不陌生,这里无需再标新立异,Lambda 表达式也使用同样的技巧,并将这种接口称为函数接口

ActionListener 接口:接受 ActionEvent 类型的参数,返回空
public interface ActionListener extends EventListener {
        public void actionPerformed(ActionEvent event);
}

ActionListener 只有一个抽象方法: actionPerformed ,被用来表示行为:接受一个参数,返回空。记住,由于 actionPerformed 定义在一个接口里,因此 abstract 关键字不是必需的。该接口也继承自一个不具有任何方法的父接口: EventListener

这就是函数接口,接口中单一方法的命名并不重要,只要方法签名和 Lambda 表达式的类型匹配即可。可在函数接口中为参数起一个有意义的名字,增加代码易读性,便于更透彻
地理解参数的用途

如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。同时,引入了一个新的注解:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。

这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。在接口中添加了@FunctionalInterface 的接口,只允许有一个抽象方法,否则编译器也会报错,可以拥有若干个默认方法

Java中的lambda无法单独出现,它需要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现

类型推断

Lambda 表达式中的类型推断,实际上是 Java 7 就引入的目标类型推断的扩展。比如Java 7 中的菱形操作符,它可使 javac 推断出泛型参数的类型

使用菱形操作符,根据变量类型做推断
Map<String, Integer> oldWordCounts = new HashMap<String, Integer>(); //1
Map<String, Integer> diamondWordCounts = new HashMap<>(); //2

为变量 oldWordCounts 1明确指定了泛型的类型,而变量 diamondWordCounts 2则使用了菱形操作符。不用明确声明泛型类型,编译器就可以自己推断出来

使用菱形操作符,根据方法签名做推断

private void useHashmap(Map<String, String> values);

useHashmap(new HashMap<>());

Java 7 中程序员可省略构造函数的泛型类型,Java 8 更进一步,程序员可省略 Lambda 表达式中的所有参数类型。再强调一次,这并不是魔法, javac 根据 Lambda 表达式上下文信息就能推断出参数的正确类型。程序依然要经过类型检查来保证运行的安全性,但不用再显式声明类型罢了。这就是所谓的类型推断

Java8中重要的函数接口

Function<T, R>

Fcuntion接口是对接受一个T类型参数,返回R类型的结果的方法的抽象,通过调用apply方法执行内容。

接口方法


// 将此参数应用到函数中
R apply(T t)	

//返回一个组合函数,该函数结果应用到after函数中
Function<T, R> andThen(Function<? super R,? extends V> after)

//返回一个组合函数,首先将入参应用到before函数,再将before函数结果应用到该函数中
Function<T, R> compose(Function<? super V,? extends T> before)	

例子:


public class M1 {


    public static final int addOne(int a){
        return a+1;
    }

    public static int operation(int a,Function<Integer,Integer> function){
        return function.apply(a);
    }


    public static void main(String[] args) {

        int x = 1;

        int y = operation(x,x1 -> addOne(x1));

        System.out.printf("x= %d, y = %d\n", x, y);

        System.out.println("=====================");

//        当然你也可以使用lambda表达式来表示这段行为,只要保证一个参数,一个返回值就能匹配
        int z = operation(x,h->h+10);

        System.out.println(z);

    }
}


计算给定字符串的长度:


public class M2 {


    public static int computeLen(String source, Function<String,Integer> function){

        int len = function.apply(source);

        return len;

    }

    public static void main(String[] args) {

//        使用lambda
        System.out.println(computeLen("jdjsajd",(string)->string.length()));

        //使用了方法引用
        System.out.println(computeLen("helo",String::length));




    }
}

Function<T, R>还有的两个方法:

  • compose方法接收一个Function参数before,该方法说明是返回一个组合的函数,首先会应用before,然后应用当前对象,换句话说就是先执行before对象的apply,再执行当前对象的apply,将两个执行逻辑串起来。

  • andThen方法接收一个Function参数after,与compose方法相反,它是先执行当前对象的apply方法,再执行after对象的方法



public class M3 {

    public static int compute_1(int i,
                                Function<Integer,Integer> after,
                                Function<Integer,Integer> before){


        return after.compose(before).apply(i);
    }

    public static int compute_2(int i,
                                Function<Integer,Integer> before,
                                Function<Integer,Integer> after){

        return before.andThen(after).apply(i);

    }

    public static void main(String[] args) {


//        当调用compute1(5,i -> i 2,i -> i i)时,先平方再乘以2所以结果是50。而compute2方法对两个Function的调用正好相反,所以结果是100
        System.out.println(compute_1(
                5,
                i -> i * 2,
                i -> i * i
        ));

        System.out.println(compute_2(
                5,
                i -> i * 2,
                i -> i * i
        ));


    }
}


Consumer

Consumer 接口翻译过来就是消费者,顾名思义,该接口对应的方法类型为接收一个参数,没有返回值,可以通俗的理解成将这个参数’消费掉了’,一般来说使用Consumer接口往往伴随着一些期望状态的改变或者事件的发生,例如最典型的forEach就是使用的Consumer接口,虽然没有任何的返回值,但是却向控制台输出了语句

接口方法


//对给定的参数执行操作
void accept(T t)	

//返回一个组合函数,after将会在该函数执行之后应用
default Consumer andThen(Consumer<? super T> after)	

几个应用例子:


public class M1 {

    public static void main(String[] args) {

        Consumer<String> consumer = s -> System.out.println(s);

        consumer.accept("jfjfj");
    }
}


public class M2 {

    public static void consumer_1(double num){

        method(num,
                (m) ->
                        System.out.println("花了 " + m + "  RMB"));
    }

    public static void method(double number, Consumer<Double> consumer){
        consumer.accept(number);
    }

    public static void main(String[] args) {

        consumer_1(555);

    }
}


public class M3 {

    public static void main(String[] args) {

        StringBuilder builder = new StringBuilder("hello");

        Consumer<StringBuilder> consumer = (s) ->
                s.append("  world");

        consumer.accept(builder);

        System.out.println(builder.toString());
    }
}

public class M4 {


    public static void main(String[] args) {

        StringBuilder builder = new StringBuilder("hello  ");

        Consumer<StringBuilder> consumer_1 =
                (s) ->
                        s.append("LOL");



        Consumer<StringBuilder> consumer_2 =

                (s) ->
                        s.append("Dota");


        consumer_1.andThen(consumer_2).accept(builder);

        System.out.println(builder.toString());
    }
}


Supplier

Supplier 接口翻译过来就是提供者,和上面的消费者相反,该接口对应的方法类型为不接受参数,但是提供一个返回值,通俗的理解为这种接口是无私的奉献者,不仅不要参数,还返回一个值,使用get()方法获得这个返回值

接口方法


//获取结果值
T get()	


几个例子:


public class M1 {


    public static void main(String[] args) {

        Supplier<String> stringSupplier = () ->
                "djjdjdjd";

        System.out.println(stringSupplier.get());

    }
}



public class M2 {

    static Random random = new Random(55);

    public static List<Integer> getNumList(Supplier<Integer> supplier){
        List<Integer> list = new ArrayList<>();

        for (int i = 0; i < 20; i++) {
            list.add(supplier.get());
        }
        return list;
    }

    public static void supplier(){
        List<Integer> list = getNumList(() ->
                random.nextInt(555));

        list.forEach(System.out::println);
    }

    public static void main(String[] args) {

        supplier();
    }
}


Predicate

predicate 谓语接口,顾名思义,中文中的‘是’与‘不是’是中文语法的谓语,同样的该接口对应的方法为接收一个参数,返回一个Boolean类型值,多用于判断与过滤,当然你可以把他理解成特殊的Funcation<T,R>,但是为了便于区分语义,还是单独的划了一个接口

接口方法


// 根据给定的参数进行判断
boolean test(T t)	

//返回一个组合判断,将other以短路与的方式加入到函数的判断中
Predicate and(Predicate<? super T> other)	

//返回一个组合判断,将other以短路或的方式加入到函数的判断中
Predicate or(Predicate<? super T> other)	

//将函数的判断取反
Predicate negate()	

使用:


public class M1 {

    public static void main(String[] args) {

        Predicate<Integer> predicate = n ->
                n != 0;

        System.out.println(predicate.test(22));


        System.out.println("====================");

        Predicate<Integer> predicate_1 = n ->

                n != 0;

        predicate_1 = predicate_1.and(n ->
                n >= 100);

        System.out.println(predicate_1.test(555));


        System.out.println("====================");


        
        Predicate<Integer> predicate_2 = n ->

                n != 0;

        predicate_2 = predicate_2.or( n ->
                n < 10);

        System.out.println(predicate_2.test(0));

        System.out.println("====================");


        Predicate<Integer> predicate_3  = number -> number != 0;

        predicate_3 = predicate_3.negate();

        System.out.println(predicate_3.test(10));


    }
}



public class M2 {


    public static List<String> filterStr(List<String> list,
                                         Predicate<String> predicate){

        List<String> strings = new ArrayList<>();

        list.forEach(str ->
        {
            if (predicate.test(str)){
                strings.add(str);
            }
        });

        return strings;
    }


    public static void predicate(){

        List<String> list = Arrays.asList("Hello", "atguigu", "Lambda", "www", "ok");

        List<String> res = filterStr(list,
                s -> s.length() > 3
                );

        res.forEach(System.out::println);


    }




    public static void main(String[] args) {

            predicate();

    }
}


BiFunction<T, U, R>

使用前面Function以后,发现Function只能接收一个参数,如果我要传递两个参数呢,这一点Java8也替我们考虑到了,就是我们截下来要讲到的 BiFunction

方法:


//将此函数应用于给定的参数。
R apply(T t, U u);

//返回一个组合函数,首先将此函数应用于其输入,然后将 after函数应用于结果。
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after)

使用例子:



public class M1 {


    public static int compute(int a, int b, BiFunction<Integer,Integer,Integer>
                              function){

        return function.apply(a,b);
    }


    public static int compute_1(int a, int b,
                                BiFunction<Integer,Integer,Integer> function_1,
                                Function<Integer,Integer> function_2){

        return function_1.andThen(function_2).apply(a,b);

    }

    public static void main(String[] args) {

        System.out.println(compute(2,5,
                (x,y) ->
                x + y));

        System.out.println(compute(2,5,
                (x,y) ->
                        x - y));

        System.out.println(compute(2,5,
                (x,y) ->
                        x * y));


        System.out.println("===============================");


//        首先执行(v1, v2) -> v1 + v2,然后执行 v1 -> v1 * v1。
        System.out.println(compute_1(100,200,
                (x,y) -> (x + y),x->x*x));


    }



}

BinaryOperator

public interface BinaryOperator<T> extends BiFunction<T,T,T> 

BiFunction的一个特殊例子,接收两个参数,产生一个结果,只是它的三个参数都是同一个数据类型

接口方法


//根据给定参数执行函数
T apply(T t, T u)	

//返回一个组合函数,after应用于该函数之后
BiFunction<T,T,T> andThen(Function<? super T,? extends T> after)	

//返回二元操作本身,通过特殊比较器返回最大的元素
BinaryOperator maxBy(Comparator<? super T> comparator)


//返回二元操作本身,通过特殊比较器返回最小的元素
BinaryOperator minBy(Comparator<? super T> comparator)		

使用:


public class M2 {


    public static void test_1(Integer integer1,Integer integer2,
                              BinaryOperator<Integer> binaryOperator){

        System.out.println(binaryOperator.apply(integer1,integer2));
    }

    //     * 返回两者里面较小的一个
    public static void test_2(String s1, String s2, Comparator<String> comparator){

        System.out.println(BinaryOperator.minBy(comparator).apply(s1,s2));
    }

//     * 返回两者里面较大的一个
    public static void test_3(String s1,String s2,Comparator<String> comparator){

        System.out.println(BinaryOperator.maxBy(comparator).apply(s1,s2));
    }



    public static void main(String[] args) {

        test_1(1,2,(x,y) -> x + y);

        System.out.println("==============================");

        test_1(1,55,(x,y) -> x - y);

        System.out.println("==============================");

        test_2("hello","wonders",(str1,str2)->str1.length()-str2.length());
        
    }
}

BinaryOperator中有两个静态方法,是用于比较两个数字或字符串的大小。


//获取更小的值
static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator)
//获取更大的值
static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator)


    public static void main(String[] args) {

        BinaryOperator<Integer> binaryOperator = BinaryOperator.minBy(
                Comparator.naturalOrder()
        );

        System.out.println(binaryOperator.apply(100,200));
        
    }

interface DoubleUnaryOperator

接口方法


// 根据给定参数执行函数
double applyAsDouble(double operand);

// 返回一个组合函数,after应用于该函数之后
DoubleUnaryOperator andThen(DoubleUnaryOperator after)	

//返回一个组合函数,before应用于该函数之前
DoubleUnaryOperator compose(DoubleUnaryOperator before)	

使用:


public class M4 {

    public static void main(String[] args) {

        DoubleUnaryOperator doubleUnaryOperator = d -> d + 2.50;

        System.out.println(doubleUnaryOperator.applyAsDouble(2.3));

        System.out.println("=================================================");

        DoubleUnaryOperator doubleUnaryOperator1 = doub -> doub + 2.5;

        DoubleUnaryOperator doubleUnaryOperator2 = doub -> doub * 3;

        double result = doubleUnaryOperator1.andThen(doubleUnaryOperator2)
                .applyAsDouble(10);

        System.out.println(result);

        System.out.println("=================================================");


        DoubleUnaryOperator doubleUnaryOperator_3 = doub -> doub + 2.5;
        DoubleUnaryOperator doubleUnaryOperator_4 = doub -> doub * 3;
        double result_1 = doubleUnaryOperator_3.compose(doubleUnaryOperator_4)
                .applyAsDouble(10);
        System.out.println(result_1);


    }
}

UnaryOperator


public interface UnaryOperator<T> extends Function<T, T> 

接口方法


//将给定参数应用到函数中
T apply(T t)	


//返回一个组合函数,该函数结果应用到after函数中
Function<T, R> andThen(Function<? super R,? extends V> after)	

//返回一个组合函数,首先将入参应用到before函数,再将before函数结果应用到该函数中
Function<T, R> compose(Function<? super V,? extends T> before)	

使用:


public class M5 {

    public static void main(String[] args) {

        UnaryOperator<String> unaryOperator = greet -> greet + "hello ";

        System.out.println(unaryOperator.apply("world"));

        System.out.println(UnaryOperator.identity());
    }
}


interface DoubleToIntFunction

接口方法


//根据给定的参数执行函数
int applyAsInt(double value)	

使用:


public class M6 {

    public static void main(String[] args) {

        DoubleToIntFunction doubleToIntFunction =  d -> Double.valueOf(d).intValue();

        System.out.println(doubleToIntFunction.applyAsInt(1.2));

    }
}


BiConsumer<T,U>

接口方法


//对给定的参数执行操作
void accept(T t, U u)	


//返回一个组合函数,after将会在该函数执行之后应用
default BiConsumer<T,U> andThen(BiConsumer<? super T,? super U> after)	


public class M8 {

    public static void main(String[] args) {

        StringBuilder builder = new StringBuilder();

        BiConsumer<String,String> biConsumer =
                (a,b) ->
                {
                  builder.append(a);
                  builder.append(b);
                };


        biConsumer.accept("hello  ","  world");

        System.out.println(builder.toString());
    }
}



public class M9 {


    public static void main(String[] args) {

        StringBuilder sb = new StringBuilder();
        BiConsumer<String, String> biConsumer = (a, b) -> {
            sb.append(a);
            sb.append(b);
        };
        BiConsumer<String, String> biConsumer1 = (a, b) -> {
            System.out.println(a + b);
        };
        biConsumer.andThen(biConsumer1).accept("Hello", " Jack!");

    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值