函数式表达式
序言
java方法参数都是基本类型、对象(数组和引用)。当我们使用这些参数时,他们所带的属性,方法(行为)必须是固定已知,否则程序不可能运行出正确的结果。所以当我们方法传递一个引用类型参数前必须实列化它,如果属性依赖于它的方法,还要运行它相关方法,确保属性的正确,才可以使用。如果对象参数行为依赖调用者的内部变量,则这就出现了回调,那么就得使用匿名内部类。监控行为很常见。
public class ZooKeeperConnection {
// declare zookeeper instance to access ZooKeeper ensemble
private ZooKeeper zoo;
final CountDownLatch connectedSignal = new CountDownLatch(1);
// Method to connect zookeeper ensemble.
public ZooKeeper connect(String host) throws IOException,InterruptedException {
zoo = new ZooKeeper(host,5000,new Watcher() {
public void process(WatchedEvent we) {
if (we.getState() == KeeperState.SyncConnected) {
//初始值为1,所以一旦成功建立连接,就会放行connet
connectedSignal.countDown();
}
}
});
//阻塞主线程,直到成功建立连接,返回ZooKeeper对象
connectedSignal.await();
return zoo;
}
// Method to disconnect from zookeeper server
public void close() throws InterruptedException {
zoo.close();
}
}
那么能不能不要匿名内部类的复杂,而可以在使用相关属性前,正确的运行了它的方法呢。这也是大多数顺序流运行的代码逻辑。
public class XyzWatcher implements Watcher {
@Override
public void process(WatchedEvent watchedEvent) {
final CountDownLatch connectedSignal = new CountDownLatch(1);
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
connectedSignal.countDown();
}
try {
connectedSignal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ZooKeeperConnection {
// declare zookeeper instance to access ZooKeeper ensemble
private ZooKeeper zoo;
//public final CountDownLatch connectedSignal = new CountDownLatch(1);
// Method to connect zookeeper ensemble.
XyzWatcher xyz = new XyzWatcher();
public ZooKeeper connect(String host) throws IOException,InterruptedException {
zoo = new ZooKeeper(host,5000,xyz);
return zoo;
}
// Method to disconnect from zookeeper server
public void close() throws InterruptedException {
zoo.close();
}
}
上面代码又复杂了,其实我们的目的就是创建一个ZooKeeper
对象,创建过程中有条件(一旦成功建立连接才返回对象),这一行为整体作为参数传递才是我们的目的,但面向对象编程是抽象级别不够,它只能把数据抽象为对象,但对行为无能为力。函数式编程是对行为进行抽象,λ表达式产生了。
public class ZooKeeperConnection {
// declare zookeeper instance to access ZooKeeper ensemble
private ZooKeeper zoo;
final CountDownLatch connectedSignal = new CountDownLatch(1);
// Method to connect zookeeper ensemble.
public ZooKeeper connect(String host) throws IOException,InterruptedException {
zoo = new ZooKeeper(host,5000,(Watcher watcher,WatchedEvent we) ->watcher.process(we) );
return zoo;
}
// Method to disconnect from zookeeper server
public void close() throws InterruptedException {
zoo.close();
}
}
函数式接口
函数式接口是未声明sealed
且只有一个abstract方法
(除了Object之外的方法),因此表示单个函数契约。它可以用@FunctionalInterface
标识,用来提醒编译器检查接口定义是否满足函数接口定义,当违反这一约定时,编译器会报错。正确的函数式接口也可以不标。
//是
interface Runnable {
void run();
}
//不是,它只有一个Object的equals方法,未声明任何其他抽象方法
interface NonFunc {
boolean equals(Object obj);
}
//是,虽然它是上一个的子接口,但有一个非Object抽象方法
interface Func extends NonFunc {
int compare(String o1, String o2);
}
//是它只有一个非Object抽象方法
interface Comparator<T> {
boolean equals(Object obj);
int compare(T o1, T o2);
}
//不是,它声明了两个非Object的抽象方法
interface Foo {
int m();
Object clone();
}
只有一个抽象方法的接口才是函数接口,函数接口目的就是将函数运行过程作为参数传递,如果接口中有多个方法,调用时就无法得知哪一个方法,编译器也无法通过。
函数接口运用——构造方法
这就是一个事件工厂,可以生产出任何类的实例。java8之前,我称之为抽象工厂。
public interface EventFactory<T>
{
/**
* Implementations should instantiate an event object, with all memory already allocated where possible.
*
* @return T newly constructed event instance.
*/
T newInstance();
}
每一个具体工厂继承它,然后实现newInstance()方法,生产对象。
public class LongEventFactory implements EventFactory {
@Override
public Object newInstance() {
return new LongEvent();
}
}
其实就是new LongEvent()的调用,那有了方法引用之后,就一句。不用具体的工厂实现。但只接口中方法只有无参的,所以调用的是无参构造器,要想调用有参构造器,函数接口中的方法加上参数。但是这是Disruptor框架的,改不了,所以我们在创建无参事件时,可以直接通过函数接口。有参时,还是需要工厂实现类,而且可以添加创建事件对象逻辑。
EventFactory<LongEvent> eventFactory = LongEvent::new;
λ原理
main
方法需要一个匿名内部类,来输出Integer.valueOf(1)
的结果。现在用Factory
接口来实现。Factory<Integer> f
就相当于匿名内部类的对象。make
调用时正真触发λ表达式相当于调用匿名内部类中的方法。
//函数式接口
public interface Factory<T> {
T make(String s);
}
public class LamdaTest {
public static void main(String[] args) {
//lamda表达式
Factory<Integer> f = (Factory<Integer>) a -> Integer.valueOf(1);
System.out.println(f.make(null));
}
}
调用方法
λ表达式外部方法字节码。这时的字节码make参数为null,静态合成方法时,我们把参数改为100,不要产生歧义了
R大的lamda
从字节码看Lambda表达式本质
Java底层原理专题系列之带你看透Lambda表达式的本质
方法调用指令之invokevirtual
Java Lambda 表达式实现原理<长文 - 慎重>
MethodHandle标记final影响内联问题
一线开发大牛带你深度解析探讨模板解释器,解释器的生成
java Methodhandle效率
方法引用和invokedynamic重点
杨易博客