Java小技巧:20行代码实现惰性求值

惰性求值/延时求值并非什么新技术,大概三四十年前就有了,SICP还专门有一节来讲述如何给语言解释器添加惰性求值的功能,不过这门技术在工程领域并没有大范围使用,大部分流行语言,例如C、C++、Java、JavaScript等,仍然使用严格求值。相比于严格求值,惰性求值在变量被访问的时候才进行求值,这可以避免一部分不会被访问的变量的计算。另外,可以将计算延迟到合适的时候,也给代码的组织和表达提供了更多的空间。

近几年,有些语言开始为惰性求值提供支持,例如Kotlin,可以编写如下代码来实现p在第一次访问时才求值,而非定义的时候,也不会在访问的时候每次都求值:

val p: String by lazy {
    // 计算字符串的值
    "str value"
}

Java语言并不支持惰性求值,但是可以利用一些函数式的特性来实现惰性求值的效果。本文就用简短的代码通过一个Lazy类来实现惰性求值的效果。

1. 示例

首先来看一个简单例子

public void testLazy(){
    Supplier<Integer> value = Lazy.from(() -> {
        System.out.println("eval value");
        return 1;
    });
    System.out.println("start...");
    System.out.println("value:" + value.get());
    System.out.println("value:" + value.get());
}

打印结果如下,可以看到代表求值的eval value信息只有在第一次访问的时候才打印:

start...
eval value
value:1
value:1

在来看另一种场景,在Android项目中,由于视图组件只有在onCreate后才能通过findViewById取到,因此有些字段不能表示为final,例如:

public class TestActivity extends Activity {

    private TextView tvName;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        tvName = findViewById(R.id.tv_name)
        tvName.get().setText("test");
    }
}  

这样可能导致其他人对这些字段重新赋值。我们也可以这样写来使这个字段仍然可以被表示为final

public class TestActivity extends Activity {

    private final Supplier<TextView> tvName = Lazy.from(() -> findViewById(R.id.tv_name));

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        
        tvName.get().setText("test");
    }
}    

2. 使用说明

这个类的使用非常简单,一共两个方法:

  1. from静态方法:这个方法接受一个Supplier并返回新的只求值一次的Supplier对象,用于构建惰性求值的对象。
  2. get方法:这个方法实现了Supplierget方法,不过只进行一次求值,用于获取变量的值。

3. 实现

完整代码如下

import java.util.function.Supplier;

public class Lazy<T> implements Supplier<T> {
    private final Supplier<T> supplier;
    private T value;
    private boolean isEvaluated = false;

    public static <T> Supplier<T> from(Supplier<T> supplier) {
        return new Lazy<>(supplier);
    }

    private Lazy(Supplier<T> supplier) {
        this.supplier = supplier;
    }

    @Override
    public T get() {
        if (!isEvaluated) {
            value = supplier.get();
            isEvaluated = true;
        }
        return value;
    }
}

4. 注意

这个类中的get方法并非线程安全,在多线程场景下,如果多个线程一起调用get方法可能导致重复求值。如果需要在多线程场景下使用,建议用synchronized修饰get方法,或使用其他同步手段。如果提供的Supplier参数可以看作纯函数且不耗时,则不需要考虑多线程问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值