Java8新特性之lambda表达式

     在了解lambda表达式之前,必须先了解函数式编程、函数式接口和default方法。在Java8出来之前,别的编程语言中已经有了函数式编程这种概念,只不过后来没落了,不过在最近几年又火起来了。在Java8中大力提倡我们使用函数式编程,并且更新了一些类(java.utl.function),让我们根据这些类来编程。

 

目录

一、有关函数式编程、函数式接口和default方法的介绍

1.什么是函数式编程?

2.什么是函数式接口?

3.default方法又是什么?

二、lambda表达式的介绍

1.lambda表达式是啥?

2. forEach方法

3.方法引用


 

一、有关函数式编程、函数式接口和default方法的介绍

1.什么是函数式编程?

    函数式编程是种编程范式,与之相对应的是命令式编程,下面举个例子来对比一下,比如计算(1+1)*2-3的结果:

在命令式编程中可以这样写: int a = (1+1)*2-3;为了体现命令式和函数式的区别,用以下的方式方便理解:

int a = 1 + 1 ;
int b = a * 2 ;
int c = b - 3 ;

函数式编程:

subtract(multiply(add(1,1),2),3);

通过上面这个例子可以看出,函数式编程大量使用的是函数+表达式,不使用语句,因此函数式编程的代码会看起来很精简。

 

2.什么是函数式接口?

    函数式编程是一个很早就提出来的概念,不过函数式接口是Java8中新增的,那什么是函数式接口呢?在一个接口中只有一个抽象方法的接口被称为函数式接口,比如说java.lang.Runnable、java.util.concurrent.Callable。对比看这两个接口可以发现,接口定以前都有一个@FunctionalInterface注解,这是Java8中新增的注解,用来标注一个函数式接口

    

自定义一个函数式接口:

/**
 * 函数式接口
 */
@FunctionalInterface
public interface MyInterface {
    void method01();   //修饰符默认为public abstract,省略不写
}

 

3.default方法又是什么?

      这个方法也是Java8中新增的方法,在jdk8之前,接口中的方法必须都是抽象的,在jdk8中允许接口中定义非抽象方法,在接口中的非抽象方法使用default修饰即可,比如在jdk8中新增一个函数式接口:java.util.function。Java8中打破了接口中的方法必须都是抽象这一规范,有一个好处就是可以提高程序的兼容性,在java.lang.Iterable接口中新增了foreach方法,该方法就是使用default修饰的。

在接口中定义default方法可以变相的让Java支持“多继承”。

  下面代码是jdk8中java.lang.Iterable的源码(一共有3个方法, 除了iterator(),另外两个方法都是被default修饰的):

package java.lang;

import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;

/**
 * Implementing this interface allows an object to be the target of
 * the "for-each loop" statement. See
 * <strong>
 * <a href="{@docRoot}/../technotes/guides/language/foreach.html">For-each Loop</a>
 * </strong>
 *
 * @param <T> the type of elements returned by the iterator
 *
 * @since 1.5
 * @jls 14.14.2 The enhanced for statement
 */
public interface Iterable<T> {
    /**
     * Returns an iterator over elements of type {@code T}.
     *
     * @return an Iterator.
     */
    Iterator<T> iterator();

    /**
     * Performs the given action for each element of the {@code Iterable}
     * until all elements have been processed or the action throws an
     * exception.  Unless otherwise specified by the implementing class,
     * actions are performed in the order of iteration (if an iteration order
     * is specified).  Exceptions thrown by the action are relayed to the
     * caller.
     *
     * @implSpec
     * <p>The default implementation behaves as if:
     * <pre>{@code
     *     for (T t : this)
     *         action.accept(t);
     * }</pre>
     *
     * @param action The action to be performed for each element
     * @throws NullPointerException if the specified action is null
     * @since 1.8
     */
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    /**
     * Creates a {@link Spliterator} over the elements described by this
     * {@code Iterable}.
     *
     * @implSpec
     * The default implementation creates an
     * <em><a href="Spliterator.html#binding">early-binding</a></em>
     * spliterator from the iterable's {@code Iterator}.  The spliterator
     * inherits the <em>fail-fast</em> properties of the iterable's iterator.
     *
     * @implNote
     * The default implementation should usually be overridden.  The
     * spliterator returned by the default implementation has poor splitting
     * capabilities, is unsized, and does not report any spliterator
     * characteristics. Implementing classes can nearly always provide a
     * better implementation.
     *
     * @return a {@code Spliterator} over the elements described by this
     * {@code Iterable}.
     * @since 1.8
     */
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

自定义一个由default方法的接口:

@FunctionalInterface
public interface MyInterface {
    double method01(double a, double b);  //抽象方法

    default String method02() {   //default方法
        return "hello";
    }
    default int method03(int a) {  //default方法
        return a*a;
    }
}

 

二、lambda表达式的介绍

1.lambda表达式是啥?

     可以将lambda表达式看作是一个匿名方法,lambda表达式只能用于函数式接口。主要由()->{}这些符号组成,()里放的是参数(列表),{}里放的是代码块(和返回值)。使用lambda表达式可以编写出比匿名内部类更简洁的代码。

使用lambda表达式的例子(用到了上面的例子中函数式接口MyInterface):

public class TestLambda01 {
    public static void main(String[] args) {
        //不使用lambda表达式,使用匿名内部类创建MyInterface的对象
        MyInterface mi1 = new MyInterface() {
            @Override
            public double method01(double a, double b) {
                System.out.println("no lambda:");
                return a+b;
            }
        };

        //使用lambda表达式(接口一定要是函数式接口)
        MyInterface mi2 = (double a, double b) -> {
            System.out.println("lambda:");
            return a+b;
        };
        //使用lambda表达式,省略参数类型(编译器都可以从上下文环境中推断出lambda表达式的参数类型,因此可以省略参数类型)
        MyInterface mi3 = (a, b) -> {
            System.out.println("lambda:");
            return a+b;
        };
        //使用lambda表达式,省略return关键字(代码块只有一行代码时,{}可以省略)
        MyInterface mi4 = (a, b) -> a+b;
    }
}

使用lambda表达式创建线程的例子:

public class TestLambda02 {
    public static void main(String[] args) {
        //不使用lambda表达式创建线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("no lambda");
            }
        }).start();

        //使用lambda表达式
        //因为Thread类中接收Runnable类型的对象,所以编译器会识别出lambda表达式是Runnable对象
        new Thread(() -> System.out.println("lambda")).start();
    }
}

lambda表达式为什么只能用于函数式接口中?

    lambda表达式实际上就是重写了接口中的抽象方法,在函数式接口中只有一个抽象方法,此时编译器会认定重写的就是该唯一的抽象方法。倘若一个接口中有多个抽象方法,而lambda表达式是没有匿名的,编译器就无法判断其重写的是哪个抽象方法了。

 

2. forEach方法

    在jdk8中的java.lang.Iterable接口中新增了非抽象的forEach方法,可以使用该方法配合lambda表达式来遍历集合。

// 使用forEach方法+lambda表达式遍历集合

public class TestLambda03 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(6);
        list.add(3);
        list.add(8);
        list.add(2);
        list.add(7);
        list.add(5);
        list.add(8);

        //以前遍历集合的一种方式
        for (Integer i:list) {
            System.out.println(i);
        }

        //使用forEach方法+lambda表达式遍历集合
        //list.forEach((i) -> System.out.println(i));
        //只有一个参数时,可以省略()
        list.forEach(i -> System.out.println(i));
    }
}

    以上使用lambda表达式之后已经够简洁,但是上述的最后一条语句还可以简介,当lambda表达式只有一行代码时,可以使用方法的引用,上述可以写成:

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

 

3.方法引用

    方法引用主要是用来简写lambda表达式,有些lambda表达式里面仅仅是执行一个方法调用,这时使用方法引用可以简写lambda表达式。

四种类型的方法引用:
静态方法引用类名::方法名
某个对象的引用对象变量名::方法名
特定类的任意对象的方法引用类名::方法名
构造方法类名::new

前三种比较好理解,就举一个静态方法引用的例子:

public class TestLambda04 {
    public static void main(String[] args) {
        Integer[] arr = {4,8,2,9,6,5,1}; //对该数组排序
        //不使用lambda表达式
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer x, Integer y) {
                return Integer.compare(x,y);
            }
        });

        //lambda表达式
        Arrays.sort(arr,(x,y) -> Integer.compare(x,y));
        //上述只有 Integer.compare(x,y)一条语句,所以可以使用方法的引用,改为:
        Arrays.sort(arr,Integer :: compare);
    }
}

 

构造方法引用例子:

import java.util.function.Supplier;

public class Car {
    public static Car buy(Supplier<Car> s) {
        return s.get(); //通过get方法获取传入的Car类型的对象
    }
}
// 构造方法引用
public class Testlambda05 {
    public static void main(String[] args) {
        Car car = Car.buy(Car::new);
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值