Java类的分类

1. 絮絮叨叨

  • 在学习JDK 8的lambda表达式这一新特性时,发现很多文章都是以匿名类作为切入点,从而体现lambda表达式的简洁,或者帮助我们理解lambda表达式的写法
  • 在对lambda表达式语法不熟悉的情况下,自己总是尝试先用匿名类实现这个接口,然后再跟lambda表达式一一对应 😂
  • 通过学习JDK 8的官方文档,发现原来我所认识的内部类也有这么多讲究:嵌套类、静态嵌套类、非静态嵌套类等

2. 嵌套类

  • 所谓嵌套类(Nested Class),就是在一个类中定义的类

  • 之前自己一直把这种类直接叫做内部类,原来JDK中的专业术语是嵌套类

    class OuterClass { // 外部类
        static class StaticNestedClass { // 这是静态嵌套类
            // 内容省略
        }
        class NonStaticNestedClass { // 这是非静态嵌套类,又称内部类
            // 内容省略
        }
    }
    
  • 根据是否被static关键字修饰,嵌套类分为:静态嵌套类(static nested class)、非静态嵌套类(non-static nested class)

  • 非静态嵌套类,又称内部类(inner class)

2.1 为什么要使用嵌套类?

  • 这是对仅在一个地方使用的类的逻辑分类方法:
    • 类B只在类A中使用,则类B应该被嵌入到类A中。
    • 将类B这样的帮助类(helper class)进行嵌套定义,可以让包更加精简
  • 嵌套类增强了封装性:
    • 两个顶层类(top-level class)A和B,如果B需要访问A中的成员,则A中的成员不能使用private修饰
    • 将类B嵌套在类A中,类B可以访问到类A中的private成员

      类B类本身可以看做类A的成员,因此可以访问类A的private成员

    • 同时,类B本身可以对外界隐藏
  • 增加了代码的易读性和可维护性:将小的类嵌套到顶层类中,一般会在靠近使用嵌套类的地方定义嵌套类

2.2 嵌套类的一些说明

2.2.1 关于访问权限

  • 嵌套类是上层类(enclosing class)的成员变量,可以使用public、protected、private和包权限进行修饰
  • 外部类,只能使用public或包权限进行修饰

2.2.2 关于访问范围

  • 内部类可以访问上层类的所有成员,静态成员或实例成员都可以直接访问,甚至private成员也可以访问
  • 注意: 内部类中,不能定义静态成员(static final的成员变量除外)
  • 静态嵌套类,只能直接访问上层类的静态成员;若想访问实例成员,需要通过上层类的实例对象进行访问

解读1:内部类可以访问上层类的所有成员

  • 内部类访问上层类的静态成员,无需过多解释

  • 内部类是上层类的实例成员,在使用时必须先实例化上层类,再实例化内部类

  • 这时,内部类的实例对象当然可以访问上层类的实例成员

    public class OuterClass {
        private static String staticOuterField = "static field in outer class";
        private String outerField = "instance field in outer class";
    
        public static void staticOuterMethod() {}
    
        public void outerMethod() {}
    
        class InnerClass{
            public void method() {
                System.out.println(staticOuterField);
                System.out.println(outerField);
                staticOuterMethod();
                outerMethod();
            }
        }
    	public static void main(String[] args) {
        	// 内部类的实例化
            OuterClass outerClass = new OuterClass();
            OuterClass.InnerClass innerClass = outerClass.new InnerClass();
            innerClass.method();
        }
    }
    

静态嵌套类无法直接访问上层类的实例成员

  • 静态嵌套类可以看做上层类的静态成员,因此可以直接访问上层类的静态成员

  • 静态嵌套类在使用时无需初始化,除非主动实例化上层类,否则将不存在上层类的实例

  • 因此,静态嵌套类无法直接访问上层类的实例成员

  • 这时,静态嵌套类就像一个顶层类(所谓顶层类,自己的理解:跟上层类同一level的类)

    public class OuterClass {
        private static String staticOuterField = "static field in outer class";
        private String outerField = "instance field in outer class";
    
        public static void staticOuterMethod() {}
    
        public void outerMethod() {}
    
        static class StaticNestedClass {
            public void method() {
                System.out.println(staticOuterField);
                staticOuterMethod();
    
                OuterClass outerClass = new OuterClass();
                System.out.println(outerClass.outerField);
                outerClass.outerMethod();
            }
        }
    }
    // 其他类的main方法
    public static void main(String[] args) {
        // 实例化静态嵌套类,并访问静态嵌套类的实例方法
        OuterClass.StaticNestedClass staticNestedClass = new OuterClass.StaticNestedClass();
        staticNestedClass.method();
    }
    

2.3 Shadowing(阴影)

  • 在学习类的继承时,super和this两个关键字,想必大家都不陌生

  • 尤其是存在同名字段或方法时,可以通过super关键字访问父类的实例成员(实例变量、构造函数、实例方法),可以通过this关键字(或者省略this关键字)访问当前类的实例成员

  • 在内部类中,很容易出现类型声明相同的情况。

  • 例如,下面代码中的x,在上层类ShadowTest中是一个成员变量,在内部类FirstLevel中是一个成员变量,在内部类的方法methodInFirstLevel()中是一个入参

    public class ShadowTest {
        public int x = 0;
    
        class FirstLevel {
            public int x = 1;
            void methodInFirstLevel(int x) {
                System.out.println("x = " + x);
                System.out.println("this.x = " + this.x);
                System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
            }
        }
    
        public static void main(String[] args) {
            ShadowTest st = new ShadowTest();
            ShadowTest.FirstLevel fl = st.new FirstLevel();
            fl.methodInFirstLevel(23);
        }
    }
    
  • 后来者居上,x的作用域将会发生Shadowing(在我看来就是覆盖😂)

  • 被shadowed的x,需要通过添加作用域前缀才能被识别

    • x:表示方法入参x
    • this.x:表示内部类的成员变量x
    • ShadowTest.this.x:表示上层类的成员变量x
  • 最终的执行结果如下:

3. 特殊的内部类

  • 到目前为止,我们看到的内部类都是定义在一个类中的非静态嵌套类
  • 内部类其实还有两种特殊的类型:
    • 定义在方法体中的内部类,叫做局部类(local class)
    • 定义在方法体中的、没有名字的内部类,叫做匿名类(anonymous class)
  • 具体来说,是定义在语句块(block)中的类,语句块可能是方法体、for循环、if语句等

3.1 局部类

  • 下面是一个局部类示例,通过创建局部类PhoneNumberCheck判断一个电话号码是否有效(11位的手机号才有效)

    public class LocalClassTest {
        private static final String regx = "[0-9]+";
    
        public static void checkPhoneNumber(String phoneNumber1, String phoneNumber2) {
            // 定义电话号码的长度,jdk 8之前必须使用final修饰
            int length = 11;
            // 定义局部类,判断电话号码
            class PhoneNumberCheck{
                private String phoneNumber;
                public PhoneNumberCheck(String phoneNumber) {
                    this.phoneNumber = phoneNumber;
                }
    
                public boolean checkPhoneNumber() {
                    // 电话号码要求为11位,只能包含数字;局部类可以直接访问上层类的成员
                    if (phoneNumber.replaceAll(regx, "").length() == 0 && phoneNumber.length() == length) {
                        return true;
                    }
                    return false;
                }
    
                // jdk 8开始,允许访问方法的入参
                public void printParameter() {
                    System.out.println("phoneNumber1: " + phoneNumber1 + ", phoneNumber2: " + phoneNumber2);
                }
            }
    
            // 定义好局部类后,通过局部类判断电话号码是否有效
            PhoneNumberCheck check = new PhoneNumberCheck(phoneNumber1);
            // 打印方法入参中的两个电话号码
            check.printParameter();
            // 校验电话号码是否有效
            if (check.checkPhoneNumber()) {
                System.out.println("phoneNumber1 is valid!");
            } else {
                System.out.println("phoneNumber1 is invalid! ");
            }
    
            check = new PhoneNumberCheck(phoneNumber2);
            if (check.checkPhoneNumber()) {
                System.out.println("phoneNumber2 is valid!");
            } else {
                System.out.println("phoneNumber2 is invalid! ");
            }
        }
    
        public static void main(String[] args) {
            checkPhoneNumber("18380122330", "7283456");
        }
    }
    
  • 执行结果:

关于局部类的特殊说明

  • 如果局部类定义在静态方法中,则局部类只能访问上层类的静态成员
    • 例如,checkPhoneNumber()为静态方法,如果将regx定义为非静态变量,编译时会报错
  • 局部类能访问上层语句块的实例成员,所以局部类必须是非静态的(不太理解😂)
  • 接口可以看做特殊的抽象类,但接口本质上是静态的,所以不能在语句块中定义一个接口。
  • JDK 8以来的变化
    • final 或effectively final
      • JDK 8以前,局部类要想访问上层语句块中的局部变量或参数,要求局部变量或参数必须是final类型(这里的参数,不知道是否为方法的入参)
      • JDK 8开始,要求变量或参数是final或effectively final即可
      • 所谓的effectively final是指,变量或参数的值一旦初始化,就从未改变过(隐式的final类型
    • JDK 8之后,如果局部类定义在方法体中,局部类可以访问方法体的入参

3.2 匿名类

  • 局部类属于类的定义,匿名类则是一个表达式,可以在定义类的同时实例化一个类

  • 匿名类的定义类似于通过构造函数new一个对象,但在构造函数之后有一个代码块,这个代码块是匿名类的具体定义

    interface HelloWorld {
        public void greet(String name);
    }
    // 在方法体中定义匿名类
    HelloWorld frenchGreeting = new HelloWorld() {
        public void greet(String name) {
            System.out.println("Salut " + name);
        }
    };
    

对匿名类的特殊说明

  • 匿名类不能访问上层作用域中的非final或非effectively final的变量
  • 匿名类中可以定义字段、额外的方法(即使没有实现或重写超类的任何方法)、实例初始化、局部类,但是不能定义构造函数
  • 匿名类,在Java的GUI编程中使用非常多
  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值