• Java学习笔记-嵌套类


    嵌套类

    嵌套类有两种类别:static and non-static,分别对应为静态嵌套类和内部类。

    1 class OuterClass {
    2     ...
    3     static class StaticNestedClass {
    4         ...
    5     }
    6     class InnerClass {
    7         ...
    8     }
    9 }

    其中静态嵌套类只能访问外部类的静态成员,内部类可以访问外部类的任意成员;它们可以被声明为privatepublicprotected, 或 package private。

    • 静态嵌套类实例化方式为: OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
    • 内部类实例化方式:OuterClass.InnerClass innerObject = outerObject.new InnerClass(); 即通过外部类实例才能访问内部类。

    有两个比较特殊的内部类,分别为局部内部类和匿名类。

    局部内部类

    • 局部内部类(Local CLasses)可声明在类中任意块(block)中,如方法、for或if块中
    • 局部内部类可以访问外部类的成员,若局部内部类声明在静态块中,则可访问外部类的静态成员;若声明在非静态块中,则可访问外部类所有成员;
    • 局部内部类可以访问所在块的局部变量,但该局部变量必须声明为final;在JDK8中进行了改进,局部变量可以声明为final或effectively final;
    • 其他特性类似于普通内部类

    其中effectively final与final局部变量的区别在于,前者可以不显式声明变量为final,只要在整个过程中,该变量不会被修改(编译器默认该情况为final)。具体为什么局部内部类为什么必须引用final变量,可参考

    java为什么匿名内部类的参数引用时final? 。大致意思是局部内部类引用局部变量,其实是进行的值引用(或者说是值拷贝)。可以认为避免外部代码块在内部类运行结束前结束,导致局部变量回收而出错。

    匿名类

    匿名类与局部内部类相似,只是没有命名,并且同时进行声明和实例化。如下:

     1 HelloWorld frenchGreeting = new HelloWorld() {
     2             String name = "tout le monde";
     3             public void greet() {
     4                 greetSomeone("tout le monde");
     5             }
     6             public void greetSomeone(String someone) {
     7                 name = someone;
     8                 System.out.println("Salut " + name);
     9             }
    10         };

     匿名内部类适用于只用一次的情况。其他的特性与局部内部类相同。

    Lambda表达式

     在使用匿名内部类的时候,无需提供类名。对于只有一个方法的接口,使用Lambda显然比匿名类的实现简单明了。如下所示,定义一个LambdaTest接口,该接口只包含一个opt方法:
    1 interface LambdaTest {
    2     int opt(int a , int b);
    3 }
    4 
    5 LambdaTest sumTest = (a,b) -> a+b;

    第5行即为Lambda表达式声明,其中(a,b)为方法的参数,a+b为方法体,->表示将参数传递给方法体。 

    • Lambda表达式的方法体中,可以是一个表达式,也可以是代码块。若为表达式,Java运行期会计算表达式,并返回结果;若为代码块,可以添加return语句,将结果返回。
    • Lambda表达式其实是一个方法的声明,可以认为Lambda表达式是匿名方法
    • Lambda表达式与局部内部类和匿名类相似,可以访问外部类和外部代码块的变量;但与后两者不同,其不存在变量覆盖的问题,可以认为没有引入新的代码块,其与外部代码块中的局部变量同级
    • 由于第三条,所以在表达式的参数中,不能声明与同级作用域相同的变量名,否则会出现重复定义的异常。
    • Lambda表达式是匿名内部类实现形式的一种,其访问的外部变量必须是final或effectively final。

    举例如下:

     1 public class Lambda {
     2     
     3     private int var = 100;
     4     private String x = "hello";
     5     
     6     interface Cal{
     7         int op(int a, int b);
     8     }
     9     
    10     interface Print{
    11         void print(String msg);
    12     }
    13     
    14     public int operator(int a, int b, Cal cal) {
    15         return cal.op(a, b);
    16     }
    17 
    18     public void operator1(String msg, Print print) {
    19         print.print(msg);
    20     }
    21     
    22     public void operator2(String x) {
    23         
    24 //        x = "";
    25         
    26         Print print = (msg) -> {
    27             System.out.println("Lambda访问外部变量:");
    28             System.out.println(x);
    29             System.out.println(msg);
    30             System.out.println(Lambda.this.x);
    31         };
    32         
    33         print.print(x);
    34     }
    35     
    36     public static void main(String[] args) {
    37         Cal add = (a,b) -> {return a+b;};
    38         Cal mul = (a,b) -> a*b;
    39         
    40         Lambda lambda = new Lambda();
    41         System.out.println("2+3="+lambda.operator(2, 3, add));
    42         System.out.println("2*3="+lambda.operator(2, 3, mul));
    43         
    44         lambda.var = 200;
    45         Print print = (msg) -> {
    46             System.out.println(msg);
    47             System.out.println(lambda.var);
    48         };
    49         lambda.operator1("Hello World", print);
    50 
    51         lambda.operator2("Hello Lambda");
    52     }
    53 
    54 }

    运行结果:

    1 2+3=5
    2 2*3=6
    3 Hello World
    4 200
    5 Lambda访问外部变量:
    6 Hello Lambda
    7 Hello Lambda
    8 hello

     其中operator2方法可以验证后三条,如果将24行的注释取消,28行就会报“local variables referenced from a lambda expression must be final or effectively final”的异常。

    目标类型(Target Type)

    目标类型为外部类方法期望调用的类型,如上例中operator期望调用的目标方法为Cal。Java会根据Lambda表达式所处的语境和上下文信息判断目标类型,并实现调用。

    举例如下:
     1 public class TargetType {
     2     
     3     interface Cal{
     4         String op();
     5     }
     6     
     7     interface Cal1{
     8         int op1();
     9     }
    10     
    11     interface Cal2{
    12         void op1();
    13     }
    14     
    15     public static String invoke(Cal cal) {
    16         return cal.op();
    17     }
    18     
    19     public static void invoke(Cal1 cal1) {
    20         cal1.op1();
    21     }
    22     
    23     public static void invoke(Cal2 cal2) {
    24         cal2.op1();
    25     }
    26 
    27     public static void main(String[] args) {
    28         invoke(() -> "done");
    29         invoke(() -> 100);
    30         invoke(() -> {return;});
    31     }
    32 }

    声明三个接口(Cal Cal1 Cal2),具有相同名称的方法,但他们的返回值不同。另声明了3个invoke方法,分别接收3个类,即期望的目标类型不同。然后进行测试:

    main方法中的三个语句都通过编译,并且eclipse提示28行调用目标类型为Cal的invoke,29行调用目标类型为Cal1的invoke,30行调用目标类型为Cal2的invoke,目标类型如下图所示:

    (1)如果再添加一句如:invoke(() -> 100.0);  则编译器会报错,Type mismatch: cannot convert from double to String;

    (2)如果将Cal接口方法的返回值改为int,则除了28行报错,29行也报错:The method invoke(TargetType.Cal) is ambiguous for the type TargetType,即编译器无法确定调用哪个目标类型。

     官网文档中举的例子为Runnable和Callable,原理一样,如下:
    1 public interface Runnable {
    2     void run();
    3 }
    4 
    5 public interface Callable<V> {
    6     V call();
    7 }

     方法声明:

    1 void invoke(Runnable r) {
    2     r.run();
    3 }
    4 
    5 <T> T invoke(Callable<T> c) {
    6     return c.call();
    7 }
     根据上下文确定目标类型,由于有返回值,所以会调用参数为Callable的invoke方法:
    1 String s = invoke(() -> "done");

     总结:

    • 静态嵌套类与内部类区别
    • 两类特殊的内部类,局部内部类和匿名内部类;
    • 匿名内部类的特殊实现:Lambda表达式,可认为匿名方法的实现;
    • Lambda表达式会根据上下文环境确定目标类型

    参考:

     
  • 相关阅读:
    订单的处理原理及代码实现.
    购物车的原理及实现.(仿京东实现原理)
    集群下session共享问题的解决方案.
    页面静态化技术Freemarker技术的介绍及使用实例.
    ActiveMQ的介绍及使用实例.
    获取Android运行apk的packagename 和activityname
    linux extundelete 删除文件恢复
    jenkins 批量修改配置文件
    jenkins构建自动执行jmeter 发送http请求,中间有替换参数路径
    jenkins 执行ssh 远程linux执行命令
  • 原文地址:https://www.cnblogs.com/shuimuzhushui/p/8207041.html
Copyright © 2020-2023  润新知