Lambda表达式与方法引用

Lambda表达式允许我们将一个匿名函数作为方法的参数,试想一下这样一个场景,一个方法的参数是一个接口的引用,那么如果我们想使用这个方法就必须给这个方法一个该接口的实现的引用,我们一般会先建一个类来实现这个接口,然后将这个实现类的引用传递给该方法,在这个过程中我们可以发现,为了给这个方法一个参数,我们多建了一个类同时过程也变烦琐了很多,那么有没有办法可以不用建这个实现类也能达到同样的效果呢?有,那就是匿名内部类,我们试想一下,如果这个接口参数可以直接new一个实例就好了,但接口不能直接new,所以,如果我们在new接口的同时将接口的抽象方法都实现这不就相当于我们建一个类,然后实现接口方法一样吗?只不过这个实现类没有具体的类名也就是匿名类。示例如下:

public class TestThreead implements Runnable{
    @Override
    public void run() {
        System.out.println("线程启动成功");
    }
}


Thread thread = new Thread(new TestThreead());
thread.start();


// start和run的区别在去,start可以理解为是启动器,用于启动创建的线程,而run表示的是一个方法或一段执行逻辑,start启动线程就是为了去运行这个run方法
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("线程启动成功");
    }
});
thread.start();

 从结果可以看出以上两段代码具有同样的效果,是等价的,所以我们可以说new Runnable() {...}等价new TestThreead()

扩展:为什么创建一个线程要实现Runnable或是Callable接口,因为这两个接口中的run方法和call方法对应着线程的具体执行内容。如果纯粹的new Thread()而不给任何target(执行逻辑)参数的话,那么这个线程啥事不干将没有任何意义。Runnable和Callable的区别在于,Runnable中的run方法没有返回值,不可以向上抛出异常,出现异常必须自己try...catch捕获处理;Callable中的call方法拥有返回值,可以将异常上抛。

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}



@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

start和run、call的区别在于,start可以理解为是启动器,用于启动创建的线程,而run和call表示的是线程具体要执行的方法,start启动线程就是为了去运行这个run或call方法。

使用Callable需要注意的是,Thread类中的构造函数并没有以Callable引用为参数的构造函数,只有Runnable的,所以,如果想通过Callable来创建线程需要依靠FutureTask类,该类实现RunnableFuture接口,而RunnableFuture接口继承Runnable和Future接口,所以,FutureTask类间接是Runnable接口的实现类,同时FutureTask类中有以Callable引用为参数的构造函数。所以可以如下实现:

public class CallableTest implements Callable<String> {
    private final String description;

    public CallableTest(String description) {
        this.description = description;
    }

    @Override
    public String call() throws Exception {
        return this.description;
    }
}

CallableTest callableTest = new CallableTest("happy every day");
FutureTask<String> futureTask = new FutureTask<>(callableTest);
new Thread(futureTask).start();
Thread.sleep(5000);
System.out.println("线程执行完call方法后:"+futureTask.get());

// FutureTask类关键点分析
public class FutureTask<V> implements RunnableFuture<V> {

     private Callable<V> callable;

    private Object outcome;   // callable中call方法调用后的返回值  

     // 以Callable引用为入参,初始化成员属性callable的值
     public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;    // 给对应属性赋值
        this.state = NEW;       // ensure visibility of callable
    }

    // 线程启动调用的方法
    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            // 将成员属性callable赋值给c
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    // 通过c去调用call方法,并设置ran为true
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                // 如果ran为true即c.call()调用成功,进行返回值设置
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

    // 返回值设置,将call()方法调用后的赋值给成员属性outcome 
    protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }
}

// FutureTask的实现接口RunnableFuture
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

所以,如果以FutureTask类的引用作为Thread的入参近似等价于以Runnable的实现类作为入参,所以将FutureTask引用作为入参也将会调用Runnable的run方法,在上面的FutureTask源码的run方法中,我们可以看到run方法实际是调用了FutureTask构造函数中参数Callable的call方法,并将该方法的返回值赋值给类FutureTask的成员属性outcome,之后我们就可以通过FutureTask实现的Future中的get方法获取该返回值。

方法引用

方法引用是Lambda表达式特定场景下的简写,条件是Lambda表达式的表达式内容为类或引用对象调自身一个方法。参考如下:

Student student = new Student();
List<Student> list  = new ArrayList<>();
list.add(student);
// 通过filter使用消费性函数式接口  Consumer<T>
list.stream().filter(s -> Objects.nonNull(s));    // s表示list数据源中的元素
list.stream().filter(Objects::nonNull);    // 和上面等价,list中的每个元素会自动传入nonNull
// 通过map使用生产型函数式接口    Supplier<T>
list.stream().map(s -> s.getAge());
list.stream().map(Student::getAge);    // 和上面等价

总结如下:

方法引用的格式为<ClassName | instance>::<MethodName>。也就是被引用的方法所属的类名和方法名用双冒号::隔开,构造器方法是个例外,引用会用到new关键字。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值