深入分析Java的闭包与回调的理解

 

Java 编程语言给我们提供了接口的概念,接口里可以定义抽象的方法。接口定义了 API,并希望用户或者供应商来实现这些方法。很多时候,我们并不为一些接口创建独立的实现类,我们通过写一个匿名内部类来写一个内联的接口实现。

匿名类使用的非常广泛。匿名内部类使用的最常见的场景就是事件处理器了。其次匿名内部类还常被用在多线程的程序中,我们通常写匿名内部类,而不是创建 Runnable/Callable 接口的实现类。

就像我们讨论的一样,一个匿名类就是一个内联的给定的接口的实现。通常我们将这个实现类的对象作为参数传递给一个方法,然后这个方法将在内部调用传递过来的实现类的方法。故这种接口叫做回调接口,这些方法叫做回调方法。

虽然匿名类到处都在使用,但是他们还是有很多问题。第一个主要问题是复杂。这些类让代码的层级看起来很乱很复杂,也称作 Vertical Problem 。第二,他们不能访问封装类的非 final 成员。this 这个关键字将变得很有迷惑性。如果一个匿名类有一个与其封装类相同的成员名称,内部变量将会覆盖外部的成员变量,在这种情况下,外部的成员在匿名类内部将是不可见的,甚至不能通过 this 关键字来访问。因为 this 关键字值得是匿名类对象本身而不是他的封装类的对象。
 

public void anonymousExample() {
  String nonFinalVariable = "Non Final Example";
  String variable = "Outer Method Variable";
  new Thread(new Runnable() {
    String variable = "Runnable Class Member";
    public void run() {
      String variable = "Run Method Variable";
      //Below line gives compilation error.
      //System.out.println("->" + nonFinalVariable);
      System.out.println("->" + variable);
      System.out.println("->" + this.variable);
    }
  }).start();
}
输出是:
 

->Run Method Variable
->Runnable Class Member
这个例子很好的说明了我上面所说的这个问题,而 Lambda 表达式几乎解决了匿名内部类带来的所有问题。在我们进一步探讨 lambda 表达式之前,让我们来看一看 Functional Interfaces。

Functional Interfaces

Functional Interfaces 是一个只有单个方法的接口,这代表了这个方法契约。

上面的定义中的只有一个实际上并没有那么简单。这段有些不懂,请读者查看原文(The ‘Single' method can exist in the form of multiple abstract methods that are inherited from superinterfaces. But in that case the inherited methods should logically represent a single method or it might redundantly declare a method that is provided by classes like Object, e.g. toString.)

下面的例子清楚的展示了怎样理解 Functional Interfaces 的概念。
 

interface Runnable { void run(); }
// Functional
interface Foo { boolean equals(Object obj); }
// Not functional; equals is already an implicit member
interface Bar extends Foo {int compare(String o1, String o2); }
// Functional; Bar has one abstract non-Object method
interface Comparator {
 boolean equals(Object obj);
 int compare(T o1, T o2);
}
// Functional; Comparator has one abstract non-Object method
interface Foo {int m();  Object clone(); }
// Not functional; method Object.clone is not public
interface X { int m(Iterable arg); }
interface Y { int m(Iterable arg); }
interface Z extends X, Y {}
// Functional: two methods, but they have the same signature
大多数回调接口都是 Functional Interfaces。例如 Runnable,Callable,Comparator 等等。以前被称作 SAM(Single Abstract Method)

Lambda 表达式

我们上边说过,匿名类的一个主要问题是是代码的层级看起来很乱,也就是 Vertical Problem 了,Lamdba 表达式实际上就是匿名类,只不过他们的结构更轻量,更短。Lambda 表达式看起来像方法。他们有一个正式的参数列表和这些参数的块体表达。
 

(String s)-> s.lengh;
() -> 43;
(int x, int y) -> x + y;
上面的例子的意思是,第一个表达式接收一个 String 变量作为参数,然后返回字符串的长度。第二个不带任何参数,并返回43。最后,第三个接受两个整数 x 和 y ,并返回其和。

在看了许多文字后,终于,我可以给出第一个 Lambda 表达式的例子了,这个例子运行在 JavaSE8 的预览版下:
 

public class FirstLambdaExpression {
  public String variable = "Class Level Variable";
  public static void main(String[] arg) {
    new FirstLambdaExpression().lambdaExpression();
  }
  public void lambdaExpression(){
    String variable = "Method Local Variable";
    String nonFinalVariable = "This is non final variable";
    new Thread (() -> {
      //Below line gives compilation error
      //String variable = "Run Method Variable"
      System.out.println("->" + variable);
      System.out.println("->" + this.variable);
    }).start();
  }
}
输出是:
 

->Method Local Variable
->Class Level Variable

你可以比较一些使用 Lambda 表达式和使用匿名内部类的区别。我们可以清楚的说,使用 Lambda 表达式的方式写匿名类解决了变量可见性的问题。你可以看一下代码中的注释, Lambda 表达式不允许创建覆盖变量。

通常的 Lambda 表达式的语法包括一个参数列表,箭头关键字"->"最后是主体。主体可以是表达式(单行语句)也可以是多行语句块。如果是表达式,将被计算后返回,如果是多行的语句块,就看起来跟方法的语句块很相似了,可以使用 return 来指定返回值。break 和 continue  只能用在循环内部。

为什么选择这个特殊的语法形式呢,因为目前 C# 和 Scala 中通常都是这种样式,也算是 Lambda 表达式的通用写法。这样的语法设计基本上解决了匿名类的复杂性。但是与此同时他也是非常灵活的,例如,如果方法体是单个表达式,大括号和 return 语句都是不需要的。表达式的结果就是作为他自己的返回值。这种灵活性可以保持代码简洁。

 Lambda 表达式用作匿名类,因此他们可以灵活运用在其他模块或在其他 Lambda 表达式(嵌套的 Lambda 表达式)。
 

//Lambda expression is enclosed within methods parameter block.
//Target interface type is the methods parameter type.
String user = doSomething(() -> list.getProperty(“propName”);
//Lambda expression is enclosed within a thread constructor
//target interface type is contructors paramter i.e. Runnable
new Thread (() -> {
  System.out.println("Running in different thread");
}).start();

如果你仔细看看 lambda 表达式,您将看到,目标接口类型不是一个表达式的一部分。编译器会帮助推断 lambda 表达式的类型与周围环境。

Lambda 表达式必须有一个目标类型,而他们可以适配任意可能的目标类型。当目标类型是一个接口的时候,下面的条件必须满足,才能编译正确:

    接口应该是一个 functional interface
    表达式的参数数量和类型必须与 functional interface 中声明的一致
    返回值类型必须兼容 functional interface 中方法的返回值类型
    抛出的异常表达式必须兼容 functional interface 中方法的抛出异常声明
由于编译器可以通过目标类型的声明中得知参数类型和个数,所以在 Lambda 表达式中,可以省略参数类型声明。
 

Comparator c = (s1, s2) -> s1.compareToIgnoreCase(s2);
而且,如果目标类型中声明的方法只接收一个参数(很多时候都是这样的),那么参数的小括号也是可以不写的,例如:
 

ActionListenr listenr = event -> event.getWhen();

一个很明显的问题来了,为什么 Lambda 表达式不需要一个指定的方法名呢?

答案是:Lambda 表达式只能用于 functional interface ,而 functional interface 只有一个方法。

当我们确定一个 functional interface 来创建 Lambda 表达式的时候,编译器可以感知 functional interface 中方法的签名,并且检查给定的表达式是否匹配。

这种灵活的语法帮助我们避免了使用匿名类的 Vertical Problem ,而且不会带来 Horizontal Problem(单行语句非常长)。

Lambda 表达式的语法是上下文相关的,但是这些并不是第一次出现。Java SE 7添加的diamond operators 也有这个概念,通过上下文推断类型。
 

void invoke(Runnable r) {r.run()}
void Future invoke(Callable r) {return c.compute()}
//above are two methods, both takes parameter of type functional interface
Future s = invoke(() ->"Done"); //Which invoke will be called?

上面问题的答案是调用接收Callable参数的方法。在这种情况下编译器会通过不同参数类型的重载解决。当有不止一个适用的重载方法,编译器也检查lambda表达式与相应的目标类型的兼容性。简单的说,上面的invoke方法期望一个返回,但是只有一个invoke方法具有返回值。

Lambda表达式可以显式的转换为指定的目标类型,只要跟对应的类型兼容。看一下下面的程序,我实现了三种Callable,而且都将其转换为Callable类型。
 

public class FirstSightWithLambdaExpressions {
  public static void main(String[] args) {
    List list = Arrays.asList(
      (Callable)()->"callable 1",
      (Callable) ()->"callable 2",
      (Callable) ()->"callable 3");
    ExecutorService e = Executors.newFixedThreadPool(2);
    List futures = null;
    try {
      futures = e.invokeAll(list);
      new FirstSightWithLambdaExpressions().dumpList(futures);
    } catch (InterruptedException | ExecutionException e1) {
      e1.printStackTrace();
    }
    e.shutdown();
  }
  public void dumpList(List list) throws InterruptedException,
       ExecutionException {
    for (Future future : list) {
      System.out.println(future.get());
    }
  }
}
正如我们前面讨论的一样,匿名类不能访问周围环境中非final的变量。但是Lambda表达式里就没有这个限制。

目前,该定义的 functional interfaces 只适用于接口。我试着对一个只有一个抽象方法的抽象类创建一个 lambda 表达式,但出了一个编译错误。按照 jsr - 335,未来版本的 lambda 表达式可能支持 Functional Classes。

方法引用
方法引用被用作引用一个方法而不调用它。
Lambda 表达式允许我们定义一个匿名的方法,并将它作为 Functional interface 的一个实例。方法引用跟 Lambda 表达式很像,他们都需要一个目标类型,但是不同的是方法引用不提供方法的实现,他们引用一个已经存在的类或者对象的方法。
 

System::getProperty
"abc"::length
String::length
super::toString
ArrayList::new
上面的语句展示了方法和构造函数的引用的通用语法。这里我们看到引入了一个新的操作符“::'(双冒号)。我尚不清楚确切名称为这个操作符,但是 JSR 指它作为分隔符,维基百科页面是指它作为一个范围解析操作符。作为我们的参考,本教程的范围内,我们将简单地将它作为分隔符。
目标引用或者说接收者被放在提供者和分隔符的后面。这形成了一个表达式,它能够引用一个方法。在最后声明上述代码,该方法的名字是“new”。这个表达式引用的是 ArrayList 类的构造方法(下一节再讨论构造方法的引用)

再进一步了解这个之前,我想让你看一看方法引用的强大之处,我创建了一个简单的 Employee 数组的排序程序。
 

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class MethodReference {
  public static void main (String[] ar){
    Employee[] employees = {new Employee("Nick"), new Employee("Robin"), new      Employee("Josh"), new Employee("Andy"), new Employee("Mark")};
    System.out.println("Before Sort:");
    dumpEmployee(employees);
    Arrays.sort(employees, Employee::myCompare);
    System.out.println("After Sort:");
    dumpEmployee(employees);
  }
  public static void dumpEmployee(Employee[] employees){
    for(Employee emp : Arrays.asList(employees)){
      System.out.print(emp.name+", ");
    }
    System.out.println();
  }
}
class Employee {
  String name;
  Employee(String name) {
   this.name = name;
  }
  public static int myCompare(Employee emp1, Employee emp2) {
    return emp1.name.compareTo(emp2.name);
  }
}
输出是:
 

Before Sort: Nick, Robin, Josh, Andy, Mark,
After Sort: Andy, Josh, Mark, Nick, Robin,
输出没什么特别,Employee 是一个非常简单的类,只有一个 name 属性。静态方法 myCompare 接收两个 Employee 对象,返回他们名字的比较。

在 main 方法中我创建了一个不同的 employee 的数组,并且将它连同一个方法引用表达式( Employee::myCompare )传递给了 Arrays.sort 方法。

等一下,如果我们看 Javadoc 你会发现 sort 方法的第二个参数是 Comparator 类型的,但是我们却传递了 Employee 的一个静态方法引用。重要的问题就在这了,我既没有让 Employee 实现 Comparable 接口,也没有写一个独立的 Comparator 类,但是输出确实没有任何问题。

让我们来看一看这是为什么。 Arrays.sort 方法期望一个 Comparator 的实例,而这个 Comparator 是一个 functional interface  ,这就意味着他只有一个方法,就是 compare 了。这里我们同样恶意传一个 Lambda 表达式,在这个表达式中提供 compare 方法的实现。但是在我们的里中,我们的 Employee 类已经有了一个自己的比较方法。只是他们的名字是不一样的,参数的类型、数量,返回值都是相同的,这里我们就可以创建一个方法引用,并将它传递给 sort 作为第二个参数。

当有多个相同的名称的方法的时候,编译器会根据目标类型选择最佳的匹配。为了搞明白,来看一个例子:
 

public static int myCompare(Employee emp1, Employee emp2) {
 return emp1.name.compareTo(emp2.name);
}
//Another method with the same name as of the above.
public static int myCompare(Integer int1, Integer int2) {
 return int1.compareTo(int2);
}
我创建了两个不同的数组,用作排序。
 

Employee[] employees = {new Employee("Nick"), new Employee("Robin"),
     new Employee("Josh"), new Employee("Andy"), new Employee("Mark")};
Integer[] ints = {1 , 4, 8, 2, 3, 8, 6};
现在,我执行下面的两行代码
 

Arrays.sort(employees, Employee::myCompare);
Arrays.sort(ints, Employee::myCompare);
这里,两行代码中的方法引用声明都是相同的(Employee::myCompare),唯一不同的是我们传入的数组,我们不需要传递一个含糊不清的标记用以知名那个方法作为方法引用,编译器会帮助我们检查第一个参数,并且智能的找到合适的方法。
不要被静态方法误导了哦,我们还可以创建实例方法的引用。对于静态方法我们使用类名::方法名来写方法引用,如果是实例方法的引用,则是对象::方法名。

上面的例子已经是相当不错的了,但是我们不必为整型的比较单独写一个方法,因为Integer已经实现了Comparable并且提供了实现方法compareTo。所以我们直接使用下面这一行就行了:
 

Arrays.sort(ints, Integer::compareTo);
看到这里,你是否觉得有点迷惑?没有?那我来让你迷惑一下
这里, Integer 是一个类名(而不是一个像 new Integer() 一样的实例),而 compareTo 方法却是 Integer 类的成员方法(非静态).如果你仔细看了我上面的描述就会知道,成员方法的方法引用::之前应该是对象,但是为什么这里的语句确实合法的。
答案是:这种类型的语句允许使用在一些特定的类型中。Integer是一个数据类型,而对于数据类型来说,这种语句是允许的。
如果我们将 Employee 的方法 myCompare 变成非静态的,然后这样使用:Employee::myCompare,就会出编译错误:No Suitable Method Found。

构造方法引用
构造方法引用被用作引用一个构造方法而不实例化指定的类。
构造方法引用是 JavaSE 8 的一个新的特性。我们可以构造一个构造方法的引用,并且将它作为参数传递给目标类型。
当我们使用方法引用的时候,我们引用一个已有的方法使用他们。同样的,在使用构造方法引用的时候,我们创建一个已有的构造方法的引用。
上一节中我们已经看到了构造方法引用的语法类名::new,这看起来很像方法引用。这种构造方法的引用可以分配给目标 functional interfaces 的实例。一个类可能有多个构造方法,在这种情况下,编译器会检查 functional interfaces 的类型,最终找到最好的匹配。
对我来说写出第一个构造方法引用的程序有些困难,虽然我理解了他的语法,但是我却不知道怎么使用它,以及它有什么用。最后,我花费了很久的努力,终于“啊,找到了...”,看看下面的程序吧。
 

public class ConstructorReference {
  public static void main(String[] ar){
    MyInterface in = MyClass::new;
    System.out.println("->"+in.getMeMyObject());
  }
}
interface MyInterface{
  MyClass getMeMyObject();
}
class MyClass{
  MyClass(){}
}
输出是:
 

->com.MyClass@34e5307e
这看起来有点神奇是吧,这个接口和这个类除了接口中声明的方法的返回值是 MyClass 类型的,没有任何关系。

这个例子又激起了我心中的另一个问题:怎样实例化一个带参数的构造方法引用?看看下面的程序:
 

public class ConstructorReference {
  public static void main(String[] ar){
    EmlpoyeeProvider provider = Employee::new;
    Employee emp = provider.getMeEmployee("John", 30);
    System.out.println("->Employee Name: "+emp.name);
    System.out.println("->Employee Age: "+emp.age);
  }
}
interface EmlpoyeeProvider{
  Employee getMeEmployee(String s, Integer i);
}
class Employee{
  String name;
  Integer age;
  Employee (String name, Integer age){
    this.name = name;
    this.age = age;
  }
}
输出是:
 

->Employee Name: John
->Employee Age: 30
在看完这篇文章之前,让我们再来看一看JavaSE8中的最酷的一个特性--默认方法(Default Methods)

默认方法(Default Methods)
JavaSE8 中将会引入一个叫做默认方法的概念。早起的 Java 版本的接口拥有非常严格的接口,接口包含了一些抽象方法的声明,所有非抽象的实现类必须要提供所有这些抽象方法的实现,甚至是这些方法没有用或者不合适出现在一些特殊的实现类中。在即将到来的Java 版本中,允许我们在接口中定义方法的默认实现。废话不多说,看下面:
 

public class DefaultMethods {
 public static void main(String[] ar){
 NormalInterface instance = new NormalInterfaceImpl();
 instance.myNormalMethod();
 instance.myDefaultMethod();
 }
}
interface NormalInterface{
 void myNormalMethod();
 void myDefaultMethod () default{
 System.out.println("-> myDefaultMethod");
 }
}
class NormalInterfaceImpl implements NormalInterface{
 @Override
 public void myNormalMethod() {
 System.out.println("-> myNormalMethod");
 }
}
输出是:
 

-> myDefaultMethod
上面的接口中声明了两个方法,但是这个接口的实现类只实现了其中一个,因为 myDefaultMethod 使用 default 修饰符标记了,而且提供了一个方法块用作默认实现。通用的重载规则在这里仍然生效。如果实现类实现了接口中的方法,调用的时候将是调用类中的方法,否则,默认实现将被调用。

集成父接口的接口可以增加、改变、移除父接口中的默认实现。
 

interface ParentInterface{
 void initiallyNormal();
 void initiallyDefault () default{
 System.out.println("-> myDefaultMethod");
 }
}
interface ChildInterface extends ParentInterface{
 void initiallyNormal() default{
 System.out.println("now default - > initiallyNormal");
 }
 void initiallyDefault (); //Now a normal method
}
在这个例子中,ParentInterface  定义了两个方法,一个是正常的,一个是有默认实现的,子接口只是简单的反了过来,给第一个方法添加了默认实现,给第二个方法移除了默认实现。
设想一个类继承了类 C ,实现了接口 I ,而且 C 有一个方法,而且跟I中的一个提供默认方法的方法是重载兼容的。在这种情况下,C中的方法会优先于I中的默认方法,甚至C中的方法是抽象的时候,仍然是优先的。
 

public class DefaultMethods {
 public static void main(String[] ar){
 Interfaxe impl = new NormalInterfaceImpl();
 impl.defaultMethod();
 }
}
class ParentClass{
 public void defaultMethod() {
 System.out.println("->ParentClass");
 }
}
interface Interfaxe{
 public void defaultMethod() default{
 System.out.println("->Interfaxe");
 }
}
class NormalInterfaceImpl extends ParentClass implements Interfaxe{}
输出是:
 

->ParentClass
第二个例子是,我的类实现了两个不同的接口,但是两个接口中都提供了相同的具有默认实现的方法的声明。在这种情况下,编译器将会搞不清楚怎么回事,实现类必须选择两个的其中一个实现。这可以通过如下的方式来使用 super 来搞定。
 

public class DefaultMethods {
 public static void main(String[] ar){
 FirstInterface impl = new NormalInterfaceImpl();
 impl.defaultMethod();
 }
}
interface FirstInterface{
 public void defaultMethod() default{
 System.out.println("->FirstInterface");
 }
}
interface SecondInterface{
 public void defaultMethod() default{
 System.out.println("->SecondInterface");
 }
}
class NormalInterfaceImpl implements FirstInterface, SecondInterface{
 public void defaultMethod(){
 SecondInterface.super.defaultMethod();
 }
}
输出是:
 

->SecondInterface

现在,我们已经看完了 Java  闭包的介绍。这个文章中,我们接触到了 Functional Interfaces  和 Java Closure ,理解了 Java 的 Lambda 表达式,方法引用和构造方法引用。而且我们也写出了 Lambda 表达式的 Hello World 例子。

java回调机制:

软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。

同步调用:一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;

回调:一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;

异步调用:一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。

回调和异步调用的关系非常紧密:使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。

看下面的例子:

    packagecallbackexample;

    public interface ICallBack {

    public void postExec();//需要回调的方法

     }

 

另外的一个类:

    packagecallbackexample;

    public class FooBar { //组合聚合原则

    privateICallBackcallBack;

    public void setCallBack(ICallBackcallBack)

    { this.callBack = callBack; doSth(); }

    public void doSth()

    { callBack.postExec(); } }

 

第二个类在测试类里面,是一个匿名类:

    packagecallbackexample;

    public class Test {

    public static void main(String[] args)

    { FooBar foo = new FooBar();

    foo.setCallBack(new ICallBack() {

    public void postExec()

{ System.out.println("在Test类中实现但不能被Test的对象引用,而由FooBar对象调用"); } }); } }

以上代码中:

1.两个类:匿名类和FooBar

2.匿名类实现接口ICallBack(在test测试的main方法中用匿名类的形式实现)

3.FooBar 拥有一个参数为ICallBack接口类型的函数setCallBack(ICallBacko) 

4.匿名类运行时调用FooBar中setCallBack函数,以自身传入参数

5.FooBar已取得匿名类,就可以随时回调匿名类中所实现的ICallBack接口中的方法

首先回调方法的概念与“构造方法”的概念是不一样的,它不是指java中某个具有特殊意义或用途的方法。

称它为方法的“回调”更恰当一些,它是指方法的一种调用方式。任何一个被“回调”的方法,皆可称之为“回调方法”

方法的回调通常发生在“java接口”和“抽象类”的使用过程中。

假设有接口名为ICallBack其中有方法名为postExec()

有类Myclass实现了该接口,也就是一定实现了postExec()这个方法。现在有另一个类FooBar它有个方法setCallBack(ICallBackcallBack) ,并且setCallBack方法调用了callBack的postExec()方法。

如果现在,我们使用一个Myclass的实例myClass,将它作为参数带入到setCallBack(ICallBackcallBack)方法中,我们就说setCallBack(ICallBackcallBack)方法回调了myClass的postExec()方法。

Java最引人争议的问题之一就是,人们认为Java应该包含某种类似指针的机制,以允许回调(callback)。通过回调,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。

如果回调是通过指针实现的,那么就只能寄希望于程序员不会误用该指针。然而,您应该已经了解到,Java更小心仔细,所以没有在语言中包括指针。

通过内部类提供闭包的功能是优良的解决方案,它比指针更灵活、更安全。

再看下面的例子:

// innerclasses/Callbacks.java

// Using inner classes for callbacks

packageinnerclasses

 

interfaceIncrementable {

void increment();

}

   // Very simple to just implement the interface

class Callee1 implements Incrementable {

private int i = 0;

public void increment() {

i++;

System.out.println(i);

 }

 }

 

classMyIncrement {

public void increment() {System.out.println("Other operation");}

static void f(MyIncrement mi) {mi.increment();}

}

 

 

// If your class must implement increment() in

// some other way, you must use an inner class:

class Callee2 extends MyIncrement {

private int i=0;

public void increment() {

super.increment();

i++;

System.out.println(i);

}

private class Closure implements Incrementable {

public void increment() {

 // Specify outer-class method, otherwise

 // you'd get an infinite recursion

Callee2.this.increment();

 }

 }

IncrementablegetCallbackReference() {

return new Closure();

}

 }

 

class Caller {

privateIncrementablecallbackReference;

Caller(Incrementablecbh) {callbackReference = cbh;}

void go() {callbackReference.increment();}

}

 

public class Callbacks {

public static void main(String[] args) {

Callee1 c1 = new Callee1();

 Callee2 c2 = new Callee2();

MyIncrement.f(c2);

 

Caller caller1 = new Caller(c1);

Caller caller2 = new Caller(c2.getCallbackReference());

caller1.go();

caller1.go();

caller2.go();

caller2.go();

 }

}

输出:

 

 Other operation

1

1

2

Other operation

2

Other operation

3

 

这个例子进一步展示了外围类实现一个接口与内部类实现此接口之间的区别。就代码而言,Callee1是简单的解决方式。Callee2继承自MyIncrement,后者已经有了一个不同的increment()方法,并且与Incrementable接口期望的increment()方法完全不相关。

所以如果Callee2继承了MyIncrement,就不能为了Incrementable的用途而覆盖increment()方法,于是只能使用内部类独立地实现Incrementable。还要注意,当创建了一个内部类时,并没有在外围类的接口中添加东西,也没有修改外围类的接口。

注意,在Callee2中除了getCallbackReference()以外,其他成员都是private的。要想建立与外部世界的任何连接,interface Incrementable都是必需的。在这里可以看到,interface是如何允许接口与接口的实现完全独立的。

内部类Closure实现了Incrementable,以提供一个返Callee2的“钩子”(hook)——而且是一个安全的钩子。无论谁获得此Incrementable的引用,都只能调用increment(),除此之外没有其他功能(不像指针那样,允许您做很多事情)。

   Caller的构造器需要一个Incrementable的引用作为参数(虽然可以在任意时刻捕获回调引用),然后在以后的某个时刻,(Caller对象可以使用此引用回调Callee类。

时间: 2024-10-03 03:34:25

深入分析Java的闭包与回调的理解的相关文章

Java内部类之间的闭包和回调详解_java

前言 闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域.通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用城内,内部类有权操作所有的成员,包括private成员. Java最引人争议的问题之一就是,人们认为Java应该包含某种类似指针的机制,以允许回调(callback).通过回调,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象.如果回调是通

详解Java的闭包_java

 在2013年将发布的 JavaSE8 中将包含一个叫做 Lambda Project 的计划,在今年6月份的 JSR-335 草案中有描述.  JSR-335 将闭包引入了 Java .闭包在现在的很多流行的语言中都存在,例如 C++.C# .闭包允许我们创建函数指针,并把它们作为参数传递.在这篇文章中,我们将粗略的看一遍Java8的特性,并介绍Lambda表达式.而且我将试着放一些样例程序来解释一些概念和语法. Java 编程语言给我们提供了接口的概念,接口里可以定义抽象的方法.接口定义了

【深入分析Java Web】几种域名解析方式-详解

转载请注明出处:http://blog.csdn.net/qq_26525215 本文源自[大学之旅_谙忆的博客] 域名解析记录主要分为A记录.MX记录.CNAME记录.NS记录和TXT记录. A记录: A代表的是Address,用来指定域名对应的IP地址,如将item.taobao.com指定到115.238.23.241,将switch.taobao.com指定到121.14.24.241.A记录可以将多个域名解析到一个IP地址,但是不能将一个域名解析到多个IP地址. MX记录: 表示的是M

【深入分析Java Web】HTTP解析-常见请求头/响应头/状态码

转载请注明出处:http://blog.csdn.net/qq_26525215 本文源自[大学之旅_谙忆的博客] B/S网络架构的核心是HTTP协议,掌握HTTP协议对一个从事互联网工作的程序员来说是非常重要的. 要理解HTTP协议,最重要的就是要熟悉HTTP协议中的HTTP Header. HTTP Header控制着互联网上成千上万的用户的数据的传输. 最关键的是,它控制着用户浏览器的渲染行为和服务器的执行逻辑. 例如,当服务器没有用户请求的数据时就会返回一个404状态码,告诉浏览器没有要

接口回调怎么理解????

问题描述 接口回调怎么理解???? 接口回调要怎么理解??要怎么写,具体步骤是什么,求大神解释?? 解决方案 就是你调用了一个接口,然后那个接口又来吊你写的函数 解决方案二: 跟类的上转型对象使用一样,只是接口回调是针对接口而已,我只是渣渣,深一点的就不会了 解决方案三: 跟类的上转型对象使用一样,只是接口回调是针对接口而已,我只是渣渣,深一点的就不会了 解决方案四: 接口回调安卓开发接口回调经典案例网络请求什么是接口回调? 解决方案五: 其实和多态差不多一个道理 解决方案六: 按钮的监听事件都

java多线程问题-有一道Java面试题,没太理解明白,求大神指点啊~

问题描述 有一道Java面试题,没太理解明白,求大神指点啊~ package test2; /** 设计4个线程,其中两个线程每次对i增加1,另外两个线程对i每次减少1 @author liuyu * */ public class Test { private int i ; private synchronized void inc(){ i ++; System. out .println(Thread.currentThread().getName()+ "--inc--" +

java反射中class对象的理解

问题描述 java反射中class对象的理解 看到String.class和int.class,感到感到跟奇怪,点运算符后通常是成员变量或者方法,可这个class却不属于这二者,他们究竟该怎么理解? 解决方案 .class是一个特殊的运算符,java编译器会据此得到类型对应的class对象. 解决方案二: 你也知道是通常,,.class就相当于编译后生成的.class文件,,通过这个文件jvm就能使用这个类 解决方案三: 通过.class可以取得对象的元数据 解决方案四: Java反射(一)Cl

java微信支付后回调方法设置

问题描述 java微信支付后回调方法设置 java 微信支付成功后,回调函数方法怎么设置,和如何获得当前支付订单的信息 解决方案 java 微信支付签名生成方法 解决方案二: 我的是php 当前页面php://input 里面有微信异步返回的信息 返回的是XML 先把他转json json转数组 解决方案三: 应该在开发者中心之类的.当初设置了回调的地址了把. 解决方案四: 把处理支付后业务action全路径设置在微信后台的支付回调地址里. 解决方案五: 我设置了,可是支付完成后没有反应,方法没

JAVA中接口的回调是怎么一回事

问题描述 JAVA中接口的回调JAVA做参数是怎么一回事情书上说的很抽象 解决方案 解决方案二:http://www.blogjava.net/Carter0618/archive/2007/08/19/137936.html解决方案三:知道什么是抽象类不???解决方案四:接口做参数实际上是一种泛型,让程序更通用点跟用Object做参数有点像,你可以传String,Apple类都可以容器里面使用很广泛解决方案五:就是调用接口的实现类解决方案六:看一下马士兵的视频吧,会很有用的,就看他的javas