理解Java的匿名类


在实际的项目中看到一个很奇怪的现象,Java可以直接new一个接口,然后在new里面粗暴的加入实现代码。就像下面这样。那么问题来了,new出来的对象没有实际的类作为载体,这不是很奇怪吗?

思考以下代码的输出是什么?

1 Runnable x = new Runnable() {  
2     @Override  
3     public void run() {  
4         System.out.println(this.getClass());  
5     }  
6 };  
7 x.run();  

实际答案是出现xxxx$1这样一个类名,它是编译器给定的名称。

匿名类

匿名类相当于在定义类的同时再新建这个类的实例。我们来看看匿名类的编译结果。

这个类的代码如下:

 1 public class Test {  
 2   public void test() {  
 3     Runnable r = new Runnable(){  
 4       @Override  
 5       public void run(){  
 6         System.out.println("hello");  
 7       }  
 8     };  
 9   }  
10 }  

来看看它的编译结果,通过javap反向编译Test.class,得到的结果如下:

1 SourceFile: "Test.java"  
2 EnclosingMethod: #20.#21                // Test.test  
3 InnerClasses:  
4      #6; //class Test$1  

发现了一个字段叫EnclosingMethod,说明这个类是定义在Test.test方法下的。那现在有个问题,如果有两个test方法,会出现什么呢?

原来是旁边这个注释不太准确,实际上会包含函数签名的。请看Constant Pool部分,这里的#21指向了这个函数签名。

1 #21 = NameAndType        #34:#16        // test:()V  

匿名类的语法

这里举一个简单的例子:

1 Runnable hello = new Runnable() {  
2     public void run() {  
3         System.out.println("hello");  
4     }  
5 };  

一个匿名类由以下几个部分组成:

  1. new操作符
  2. Runnable:接口名称。这里还可以填写抽象类、普通类的名称。
  3. ():这个括号表示构造函数的参数列表。由于Runnable是一个接口,没有构造函数,所以这里填一个空的括号表示没有参数。
  4. {...}:大括号中间的代码表示这个类内部的一些结构。在这里可以定义变量名称、方法。跟普通的类一样。

访问权限

那么匿名内部类能访问哪些东西呢?按照规则,可以访问如下内容:

  1. 访问外层Class里面的字段。
  2. 不能访问外层方法中的本地变量。除非变量是final。
  3. 如果内部类的名称和外面能访问的名称相同,则会把名称覆盖掉。
1 public class A {  
2     private int foo;  
3     public void test() {  
4         Runnable r = new Runnable() {  
5             System.out.println(foo);  
6         };  
7     }  
8 }  

匿名类里面不可以有的东西:
1.不能定义静态初始化代码块(Static Initializer)。比如下面的代码是不符合语法的:

1 public class A {  
2     public void test() {  
3         Runnable r = new Runnable() {  
4             static { System.out.println("hello"); }  
5         };  
6     }  
7 }  

2.不能在匿名类里面定义接口。

比如:

1 public class A {  
2     public void test() {  
3         Runnable r = new Runnable() {  
4             public interface Hello { };  
5         };  
6     }  
7 }  

和上面一样,也是为了语义的清晰。interface只能定义静态的。

3.不能在匿名类中定义构造函数。

1 public class A {  
2     public void test() {  
3         Runnable r = new Runnable() {  
4             public Runnable() { }  
5         };  
6     }  
7 } 

因为匿名类没有名字,而构造函数需要把类名作为方法名才能看成构造函数。
匿名类中可以包含的东西有:

    1. 字段
    2. 方法
    3. 实例初始化代码
    4. 本地类

为什么不能定义静态初始化代码

事实上,内部类中不能定义任何静态的东西。

关键字:Inner class cannot have static declarations

参考资料:http://stackoverflow.com/questions/975134/why-cant-we-have-static-method-in-a-non-static-inner-class

StackOverFlow上看起来有一种解释如下。

首先来看一个内部类。

1 public class A {  
2   public class B {  
3   }  
4 }  

它编译之后,会变成下面这种含义:

1 public class A {  
2   public static class B {  
3     private final A parent;  
4     public B(A parent) {  
5       this.parent = parent;  
6     }  
7   }  
8 } 

所以,按照这么说,内部类就是一种语法糖。当我们定义静态变量时,就会产生下面这种歧义。下面的代码看起来没什么问题。

1 public class A {  
2   private int a;  
3   public class B {  
4     public static void test() {  
5       a = 1;  
6     }  
7   }  
8 }  

但是编译之后,问题就来了。

 1 public class A {  
 2   private int a;  
 3   public static class B {  
 4     private final A parent;  
 5     public B (A parent) { this.parent = parent; }  
 6     public static void test() {  
 7       parent.a = 1; // 这里有语法错误  
 8     }  
 9   }  
10 }  

所以,归根结底,Java为了保持清晰的语法,不允许这种有歧义的语法存在。

 

Java 函数式编程(lambda) 之匿名函数

函数是一个数学上的概念,表示一个集合和另一个集合的映射关系,这种关系我们在编程的时候通过箭头函数((input)->{expression})来表示,就是函数式编程。箭头的左边表示输入集合,右边表示这种映射关系,最终整个函数的结果代表函数表达式的输出。

关于函数表达式的一些概念(纯函数、函数副作用、合成柯里化)这里就不做详细介绍了,因为我觉得在java中使用lambda表达式,理解上一段落就可以了,以个人在目前实践中的经验来看不可避免会有函数副作用,主要是指在对集合的迭代操作的时候。

在java中函数式编程在两个地方使用起来很方便,一个是匿名函数,一个是集合的stream操作。

本文主要介绍匿名函数,匿名函数。

函数式接口:只有一个方法的接口。只要是函数式接口,都可以通过匿名函数来实现。例如java多线程的接口:

1 package java.lang;
2 @FunctionalInterface
3 public interface Runnable {
4     public abstract void run();
5 }

Runable接口只有一个方法,那就是run,像这样的接口都可以通过匿名函数实现,下面分别展示了通过匿名类和匿名函数来实现多线程的例子:
Thread

 1          //匿名类
 2         Thread anonymousClassThread=new Thread(new Runnable() {
 3             @Override
 4             public void run()  {
 5                 int count=1;
 6                 while (count<5){
 7                     System.out.println("anonymousClassThread:"+count);
 8                     count++;
 9                     try {
10                         Thread.sleep(1000);
11                     }catch (InterruptedException e){
12                     }
13                 }
14             }
15         });
16         //匿名函数
17         Thread anonymousFunctionThread=new Thread(()->{
18             int count=1;
19             while (count<5){
20                 System.out.println("anonymousFunction:"+count);
21                 count++;
22                 try {
23                     Thread.sleep(100);
24                 }catch (InterruptedException e){
25                 }
26             }
27         });
28         anonymousClassThread.start();
29         anonymousFunctionThread.start();

可以看出相对于匿名类的使用,匿名函数在使用上更加直观、简洁、也很易用。从这个例子上也可以看出最好在实现匿名函数的时候养成把函数写成纯函数的好处,那就是栈信息全部在函数内部,如果在不是纯函数,就会在多线程中引发同步的相关的问题。

也可以在平时的实践中,定义一些函数式接口,通过匿名函数轻便实现该接口:

 1 interface IFunctionCase{
 2     void func(String arg);
 3 }
 4 
 5 class FunctionCaseClass{
 6 
 7     public void funcTest(IFunctionCase f){
 8           System.out.println("before");
 9           f.func("test");
10         System.out.println("after");
11     }
12 }

通过匿名函数轻松实现接口:

1         FunctionCaseClass functionCaseClass=new FunctionCaseClass();
2         functionCaseClass.funcTest((arg)->System.out.println("a   "+arg));
3         functionCaseClass.funcTest((arg)->System.out.println("b  "+arg));

java.util.function中,也有一些内置的函数式接口,这些函数式接口在jdk内置的lambda表示中被广泛的使用,主要包含下面四类:

1.Function 返回一个新值
2.Consumer 不返回值
3.Supplier 没有输入,返回一个
4.Predicate 返回一个布尔值
带Bi前缀的表示有多个输入的场景

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM