技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 编程语言 --> 匿名类型的硬伤:围绕this的成员捕获策略

匿名类型的硬伤:围绕this的成员捕获策略

浏览:1675次  出处信息

    时不时听到一些C#程序员说,希望在C#里出现像Java匿名类一样的特性。以前我也觉得Java里的匿名类是个不错的特性,C#应该吸取进来。不过前段时间我仔细地理解了Java语言规范中关于内部类、匿名类的部分之后,一下子就被恶心到了。恶心过后,我忽然也意识到有些问题的确也是硬伤,也不能指责Java设计者的“品位”。例如,现在我想要谈的关于匿名类中this使用的问题――如果C#没法漂亮地实现这个特性,我宁愿它继续保持现状。

Java匿名类中的this

    Java的匿名类特性,在于可以在项目里“内联”地实现一个类型,它可以继承一个现有的具体或抽象类,或是实现接口,并提供完整的成员实现。例如,这里有个抽象类,定义了一个抽象方法:

// Java
abstract class MyAbstractClass {
    String getName() {
        return \"MyAbstractClass\";
    }

    abstract void print();
}

    然后我们在另一个地方使用一个匿名类,继承这个类:

// Java
class MyClass {
    String getName() {
        return \"MyClass\";
    }

    MyAbstractClass someMethod() {
        return new MyAbstractClass() {
            public void print() {
                System.out.println(getName());
            }
        };
    }
}

    好,现在提一个问题,运行下面这行代码会打印出什么结果?

// Java
new MyClass().someMethod().print();

    输出结果是MyAbstractClass而不是MyClass。换句话说,匿名类型中调用的getName方法是定义在MyAbstractClass里的,而不是定义在词法作用域(Lexical Scope)里的getName方法。根据Java规范,匿名类中的this(包括上面代码中“隐式”的this)表示类型本身对象,而与上下文无关。如果要访问词法作用域里的getName方法(即MyClass的方法),则反而必须显式指定MyClass类,例如:

// Java
class MyClass {
    String getName() {
        return \"MyClass\";
    }

    MyAbstractClass someMethod() {
        return new MyAbstractClass() {
            public void print() {
                System.out.println(MyClass.this.getName());
            }
        };
    }
}

可能会造成的问题

    在我看来,Java的这个设计决策很不好,十分容易让人误解代码的意图,但我相信肯定也有人会认为这只是个“品位”区别而已,没有高低。那么现在我们撇开“品位”不谈,谈点这个决策可能会造成的问题吧。例如,程序员A写了一个抽象类:

// Java
abstract class MyAbstractClass {
    abstract void print();
}

    程序员B在另一个类的方法中编写了MyAbstractClass的匿名子类:

// Java
MyAbstractClass someMethod() {
    final String name = \"MyClass\";
    return new MyAbstractClass() {
        public void print() {
            System.out.println(name);
        }
    };
}

    很显然,print方法会打印出name变量的值MyClass。相安无事多日,忽然某一天,程序员A需要为MyAbstractClass添加一些新功能,新增了一个受保护的name字段:

// Java
abstract class MyAbstractClass {
    abstract void print();

    // new field
    protected String name = \"MyAbstractClass\";
}

    于是第二天程序员B惊奇地发现,自己明明没有动过任何一行代码,MyAbstractClass忽然就无法正常工作了。这真让人情何以堪。

Java 8的Lambda表达式

    事实上,关于这种“内联”定义函数的写法,我能想到的语言都是采取“词法作用域”,因此我想Java这方面的“特立独行”的确容易让人误会。当然客观来说,Java设计成这样也是无奈之举,因为它过于强调“类型”,匿名类还是一个类,既然是个类便会有自己的成员,既然有成员就应该让内联的函数有办法调用这些成员。与之相对,虽然C#中也可以定义内联的函数,却完全不会有Java的困扰,因为C#中内联的只是“函数”而不是完整的“类型”。

    说到,底还是多亏.NET中提供了“委托”这种纯粹的,可以让“函数”独立存在的概念。当时在C# 1.0刚出现时,Sun官方还发布文章,认为“委托”破坏了面向对象的纯粹性,“内部类”完全可以作为委托来使用。现在看来,这中观点无疑是一个笑话。追求纯粹的面向对象与盲目套用设计模式类似,都是舍本逐末的做法,我们追求的是“良好的设计”,“面向对象”只是手段而不是目标。如今C#已经发布近十年了,Java社区也在努力向Java7、Java 8里引入部分C#的特性,例如Lambda表达式。

    但是,由于Java中没有“委托”,即便是Lambda表达式依旧无法提供单独函数,还是必须附带一个完整的类型。因此this问题依旧存在,这依然是个硬伤。例如我以前的文章里也提到过Java 7里的SAM类型和Lambda表达式上下文成员的捕获策略。从当时的资料来看,Lambda表达式的策略与匿名类相同,依旧以“匿名类”的成员优先,换句话说Lambda表达式只是匿名类的简单写法而已。不过现在这方面有了些许变化,例如这份幻灯片第18页里提到:Lambda表达式是一个拥有词法作用域的匿名方法(A lambda expression is a lexically scoped anonymous method),它的上下文成员捕获与Java的内部类、匿名类有明显不同。

    当然,如果使用匿名类的语法定义一个SAM类型,this相关的策略还是要与以前保持不变。Java和C#这类工业化语言的一个包袱,便是要保证兼容性――包括类库等其他方面。所以我还是一直认为,像Python,Ruby这般“洒脱”的技术平台及社区,的确很难进入企业开发市场。

硬伤

    this问题可以说是Java匿名类特性的硬伤。C#如果想要引入这个匿名特性,似乎也完全无法躲开这一点。我并不希望C#引入一个“丑陋”的语言特性,幸好也没有任何迹象表明C#有这方面的打算。有趣的是,F#提供了类似Java匿名类的特性,但完全没有这个问题。为什么呢?一看代码便知:

// Java
[]
type MyAbstractClass() =
    member this.Name = \"MyAbstractClass\"
    abstract member Print: unit -> unit

type MyClass() =
    let name = \"Local\"

    member this.Name = \"MyClass\"
    member this.MyMethod () =
        { new MyAbstractClass() with
            override inner.Print () =
                printfn \"%s\" this.Name
                printfn \"%s\" inner.Name
                printfn \"%s\" name }

    在F#中,定义一个类型的成员时,需要指定“该方法中表示自身对象的标识符”,我们可以将标识符取名为this,也可以取名为inner或是任意值。再加上F#中没有“隐式”的this指针存在,一切都是指明的,自然没有任何问题。

建议继续学习:

  1. 揭秘JavaScript中谜一样的this    (阅读:3639)
  2. jQuery之不要滥用$(this)    (阅读:2497)
  3. JavaScript中的this关键字    (阅读:2398)
  4. 深入理解Javascript之this关键字    (阅读:2197)
  5. Javascript中的this    (阅读:2215)
  6. 从同步到异步,从匿名到实名    (阅读:2030)
  7. 正确理解javascript的this关键字    (阅读:1924)
  8. 匿名类型的硬伤:围绕this的成员捕获策略    (阅读:1823)
  9. Javascript的this用法    (阅读:1625)
  10. 理解JavaScript 中的 this    (阅读:1494)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1