详解Java的闭包

在2013年将发布的 JavaSE8 中将包含一个叫做 Lambda Project 的计划,在今年6月份的 JSR-335 草案中有描述。

JSR-335 将闭包引入了 Java 。闭包在现在的很多流行的语言中都存在,例如 C++、C# 。闭包允许我们创建函数指针,并把它们作为参数传递。在这篇文章中,我们将粗略的看一遍Java8的特性,并介绍Lambda表达式。而且我将试着放一些样例程序来解释一些概念和语法。

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 例子。
JavaSE8 很快就要到来了,我将很高兴的拥抱这些新特性,也许这些新特性还是有些迷惑不清,但是我相信,随着时间的推移,会变得越来越好。

(0)

相关推荐

  • java 实现回调代码实例

    JAVA实现回调 熟悉MS-Windows和X Windows事件驱动设计模式的开发人员,通常是把一个方法的指针传递给事件源,当某一事件发生时来调用这个方法(也称为"回调").Java的面向对象的模型目前不支持方法指针,似乎不能使用这种方便的机制. Java支持interface,通过interface可以实现相同的回调.其诀窍就在于定义一个简单的interface,申明一个被希望回调的方法. 例如,假定当某一事件发生时会得到通知,我们可以定义一个interface: public i

  • JavaSript中变量的作用域闭包的深入理解

    复制代码 代码如下: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> <script type="text/javascript"> /* * 1.js中的变量都是公用的. 2.js中没有静态变量 3.闭包:函数内部可以调用函数外部的变量;反之,则不行 */ v

  • 详解java模板和回调机制

    最近看spring的JDBCTemplete的模板方式调用时,对模板和回调产生了浓厚兴趣,查询了一些资料,做一些总结. 回调函数: 所谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数.回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数.回调函数是一个工作流的一部分,由工作流来决定函数的调用(回调)时机.一般说来,C不会自己调用B,C提供B的目的就是让S来调用它,而且是C不得不提供.由于S并不知道C

  • 妙解Java中的回调机制(CallBack)

    前言 最近学习java,接触到了回调机制(CallBack).初识时感觉比较混乱,而且在网上搜索到的相关的讲解,要么一言带过,要么说的比较单纯的像是给CallBack做了一个定义.当然了,我在理解了回调之后,再去看网上的各种讲解,确实没什么问题.但是,对于初学的我来说,缺了一个循序渐进的过程. 回调是一种双向调用模式,什么意思呢,就是说,被调用方在被调用时也会调用对方,这就叫回调."If you call me, i will call back". 不理解?没关系,先看看这个可以说比

  • Java函数式编程(五):闭包

    使用词法作用域和闭包 很多开发人员都存在这种误解,认为使用lambda表达式会导致代码冗余,降低代码质量.恰恰相反,就算代码变得再复杂,我们也不会为了代码的简洁性而在代码质量上做任何妥协,下面我们就会看到. 在前面一个例子中我们已经可以重用lambda表达式了;然而,如果再匹配另外一个字母,代码冗余的问题很快又卷土重来了.我们先来进一步分析下这个问题,然后再用词法作用域和闭包来把它解决掉. lambda表达式带来的冗余 我们来从friends中过滤出那些以N或者B开头的字母.继续延用上面的那个例

  • 再谈java回调函数

    又遇到了回调函数,这次打算写下来分享一下. 所谓回调函数,或者在面向对象语言里叫回调方法,简单点讲,就是回头在某个时间(事件发生)被调用的函数. 再详细点:就是一个函数A,作为参数,传入了另一个函数B,然后被B在某个时间调用. 这里可以有疑问了,既然是一个函数调用另一个函数,可以在函数体里面调用啊,为什么还要把函数作为参数传到另一个函数里被调用?何况还有一些语言(比如java)不支持把函数作为参数. 对的,确实可以在函数体里调用另一个函数,功能上好像是没差别的,但是这里有一个问题,就是你要调用的

  • 详解 JAVA的回调机制CallBack

    序言 CallBack是回调的意思,熟悉Windows编程的人对"回调函数"这四个字一定不会陌生,但是Java程序员对它可能就不太了解了."回调函数"或者"回调方法"是软件设计与开发中一个非常重要的概念,掌握"回调函数"的思想对程序员来说(不管用哪种语言)是非常必要的. 最近学习java,接触到了回调机制(CallBack).初识时感觉比较混乱,而且在网上搜索到的相关的讲解,要么一言带过,要么说的比较单纯的像是给CallBac

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

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

  • 说明Java的传递与回调机制的代码示例分享

    java传值还是传引用 1.原始类型参数传递 public void badSwap(int var1, int var2) { int temp = var1; var1 = var2; var2 = temp; } 2.引用类型参数传递 public void tricky(Point arg1, Point arg2) { arg1.x = 100; arg1.y = 100; Point temp = arg1; arg1 = arg2; arg2 = temp; } public st

  • 理解Java当中的回调机制(翻译)

    你好,今天我要和大家分享一些东西,举例来说这个在JavaScript中用的很多.我要讲讲回调(callbacks).你知道什么时候用,怎么用这个吗?你真的理解了它在java环境中的用法了吗?当我也问我自己这些问题,这也是我开始研究这些的原因.这个背后的思想是控制反转( PS:维基百科的解释是控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度.)这个范例描述了框架(framework)的工作方式,也以"好莱坞原则:

随机推荐