Java学习 day11_内部类

接口

引例

回想Animal继承体系的案例,现在我们把Animal设计成抽象类

产品经理给了新需求

  • 现在有一部分猫和狗,经过了特殊训练

    • 都可以直立行走(Wlak Upright)和骑自行车(Ride Bike)
  • 那么该怎么去描述这群特殊的猫狗呢?

显然,如果直接将这些特殊的行为加入到猫、狗的类定义中是不合适的

因为,不是所有的猫、狗都会这些特殊技能

可以考虑重新定义特殊的猫狗类(SpecialCat)然后将这些特殊的行为加入到新定义的类中

这么做不是不行,但是没有对代码进行复用,如果又有一批特殊训练的猴子、狮子等动物呢?

可能你会想到定义一个装着特殊行为的类,然后让SpecialCat继承它

但是很可惜Java不支持多继承,我们以往的知识已经不能处理这种情况了

总结:

  • 一方面,我们有时需要从多个类中派生出一个类,继承它们所有的成员,Java语法不适用
    • Java不支持多继承
  • 另一方面,我们有时候需要从几个类中抽取出共同的行为特征
    • 而它们之间并没有“is-a”关系,继承思想显得不适用

于是:

  • 为了复用这些“特殊行为”,我们迫切需要一种新的数据格式
    • 和类相似,可以抽取出共性,定义成员
    • 不受Java单继承限制
  • 这种新的数据格式就是接口
public class Demo {
    public static void main(String[] args) {
        //测试一下接口的使用
        //有接口后,接口也可以作为"父类",用来实现多态
        IA ia = new SuperDog();
        //ia.test()

        ISkills iss = new SuperDog();
        iss.rideBike();
        iss.walkUpright();
    }
}

abstract class Animal {
    public abstract void shout();
}

class Cat extends Animal {
    @Override
    public void shout() {
        System.out.println("猫叫");
    }
}

class Dog extends Animal {
    @Override
    public void shout() {
        System.out.println("狗叫");
    }
}

class SuperCat extends Cat implements ISkills {
    //增加额外功能,扩展父类
    public void walkUpright() {
        System.out.println("直立行走");
    }

    public void rideBike() {
        System.out.println("骑自行车");
    }
}

class SuperDog extends Dog implements ISkills, IA{
    //增加额外功能,扩展父类
    public void walkUpright() {
        System.out.println("直立行走");
    }
    public void rideBike() {
        System.out.println("骑自行车");
    }
    public void test(){
    }
}
abstract class AbstractSkills {
    public abstract void walkUpright();
    public abstract void rideBike();
}
interface ISkills{
    public abstract void walkUpright();
    public abstract void rideBike();
}
interface IA{

}

接口的定义

  • 语法

    [访问权限修饰符] interface 接口名{}
    
  • 接口不是类,而是一种独立的数据类型,和class并列

    • 一个类实现的接口,也称之为接口的子类
  • 一个类继承接口,称之为实现接口,使用关键字implements

    • 语法

      class 类名 implements 接口名 {}
      
    • 当一个类继承另一个类的同时,又实现接口

      • 必须要将实现接口放在继承类后面

接口概述

  • interface表示一种数据类型,和class同级别
    • 是一种引用数据类型
    • 区别是:
      • 类定义的是一个数据集合基于这个数据集的一组操作(行为)
        • 类所描述的这一组行为,它们是有关系的(间接),都可以访问同一个数据集合
    • 类实现接口本质上也是一种继承,接口的实现类是接口的子类
    • 接口不能实例化
    • 接口不受多继承限制,接口可以多实现

接口的声明特征

  • 接口的声明中默认存在一个abstract,所以接口必然是抽象的
  • 接口普遍声明为public 鼓励继承(实现)

接口的成员特征:

  • 成员变量
  • 成员方法
  • 构造方法
  • 接口中的所有成员变量都默认是由public static final修饰的

    • 无法使用除public外的访问权限修饰符,修饰成员变量
    • 成员变量可省略public static final,比如直接int a
    • 但是接口内部不支持用static代码块给成员变量初始化,必须要提供显式的初始化

  • 接口中的所有方法都默认是由public abstract修饰的,所以你可以省略修饰,比如直接写void test();

  • (了解即可)在JDK8中引入了默认方法

    • 语法

      default 返回值类型 方法名{
      }
      
    • 默认是public修饰

    • 不能用static、abstract修饰

    • 和抽象类中的具体方法一样,接口中的方法是给子类提供了一个默认实现

      • 子类可以选择直接继承使用该方法,也可以重写(这里写只是自己回顾一下,重写必须要求参数列表相同)
      • 可以用子类对象调用,包括多态形式

    • 抽象类中有普通方法是很正常的,但是接口中不应该有具体方法,除非

      • 为了代码的兼容考虑
      • 例如class A、B、C很多类都实现了接口ITest,现在给接口新增了一个方法
        • 但这个方法不是所有类都需要重写的
        • 如果这个方法是一个抽象方法,所有类都必须实现该方法,否则会报错
        • 为了省事,可以选择default方法
        • 实际上Java8之所以引入default方法就是为了做这种事情


  • (了解即可)在JDK8中引入了静态方法

    • 语法

      static 返回值类型 方法名{
      }
      
    • 默认是public修饰

    • 不能用abstract、default修饰

    • 可以使用接口名点方法名访问

  • 从技术角度来说,以上实现方法是完全合法的

    • 只是它看起来违反了接口作为一个抽象定义的理念
    • 除非特别有必要,不要使用

关于构造方法

  • 接口根本没有构造方法,接口中的成员变量都是常量,不需要子类调用构造方法来初始化


接口的子类特征:

  • 1,普通类
  •  这个类必须实现所有的接口当中的抽象方法,不然会报错
    
  • 2,抽象类
  •  这个抽象子类可以不实现接口当中的抽象方法,也可以选择实现某几个 ,类去实现接口,用的是implements关键字,不能用继承extends
    
  • 3,接口
    接口是可以继承另一个接口的,这样它就得到了另一个接口的成员(抽象方法)
    接口的继承不受java单继承限制,一个接口可以继承多个接口
现在做一个接口和类之间的继承与实现的关系总结:
  1,同种族之间只能extends
      类之间单继承
      接口之间多继承
  1,类可以实现接口用implements
  3,接口不能对类做继承或者实现操作

接口使用注意事项:

  • 定义Java类的语法格式:先写extends,后写implements

    • class SubClass extends SuperClass implements InterfaceA{
      } 
      

接口和抽象类的异同

编号 区别点 抽象类 接口
1 定义 包含抽象方法的类 抽象方法和全局常量的集合
2 组成 构造方法、抽象方法、普通方法、常量、变量 常量、抽象方法、(jdk8:默认方法、静态方法)
3 使用 子类继承抽象类(extends) 子类实现接口(implements)
4 关系 抽象类可以实现多个接口 接口不能继承抽象类,但允许继承多个接口
5 对象 不能创建对象,但是有构造方法 不能创建对象,也没有构造方法
6 局限 抽象类不能被多继承 接口之间能多继承,能被多实现
7 思想 作为模板或对共性抽象,is-a 作为标准或对共性能力抽象,like-a
8 选择 如果抽象类和接口都可以使用的话,优先使用接口,因为避免单继承的局限

Java提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:

  • 接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。
  • 类可以实现很多个接口,但是只能继承一个抽象类
  • 类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
  • 抽象类可以在不提供接口方法实现的情况下实现接口。
  • Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。
  • Java接口中的成员方法默认是public的。抽象类的成员方法可以是private,protected或者是public。

接口就是这样的一种规范,它抽取了事物的相似行为,而不关心事物有什么关系

  • 相比于继承抽象类的“is-a”关系,体现的是“是XX”思想,而实现接口体现的是“能够XX”的思想

  • 接口允许多实现。实现多个接口,继承它们所有的属性和方法

  • 接口的本质是契约、规范、标准

小试牛刀

请用所学知识分析:

这个案例中有哪些抽象类,哪些接口,哪些具体类。

  • 教练(Coach)和运动员(Sportsman)案例

    • 乒乓球(TableTennis)运动员和篮球(basketball)运动员

    • 乒乓球教练和篮球教练

    • 为了出国交流,跟乒乓球相关的人员都需要学习英语

  • 做一个高中学生管理系统,高一,高二,高三

    • 需要做一个抽象的学生类,具体类:高一学生
    • 并且要对学生信息进行增删改查,这就是接口里面的功能,crud工程师
public class Demo {
    public static void main(String[] args) {
        //简单测试一下
        AbstractSportsman as = new TableTennisSportsman();
        as.play();
        //((TableTennisSportsman) as).sing();
        ILearning il = new TableTennisSportsman();
        il.LearningEnglish();
        //((TableTennisSportsman) il).play();
    }
}
interface ILearning{
    //接口定义都很简单,直接写抽象方法
    void LearningEnglish();
}
//定义抽象类
abstract class AbstractCoach{
    String name;
    int age;
    double salary;

    public abstract void teach();
}
abstract class AbstractSportsman{
    String name;
    int age;
    double prizeMoney;

    public abstract void play();
}
//定义具体类
class BasketballCoach extends AbstractCoach{
    @Override
    public void teach() {
        System.out.println("我是篮球教练,我就教篮球");
    }
}
class TableTennisSportsman extends AbstractSportsman implements ILearning{
    @Override
    public void LearningEnglish() {
        System.out.println("我出国了,需要学习嘤语");
    }
    @Override
    public void play() {
        System.out.println("我打乒乓球天下无敌");
    }

    //乒乓球运动员额外的行为
    public void sing(){
        System.out.println("rap");
    }
}

任何时候,先理解记忆,等到后面忘记了,很正常

等到碰到了 再翻笔记,查百度 查博客

没碰到 当不知道

学习不要犹豫 一往无前,学就完事了



接口和抽象类形式参数和返回值问题


方法的形式参数(formal)在传值的时候

1.基本类型: 对于基本数据类型的方法传参,存在自动类型提升

2.引用类型: 对于引用数据类型的方法传参,存在自动向上转型

  • 类:调用方法时需要传入的是该类的对象或者该类的子类对象
  • 抽象类:调用方法时需要传入的是该抽象类的子类对象
  • 接口:调用方法时需要传入的是该接口的子类对象
public class Demo {
    public static void main(String[] args) {
        test(0.1F);
        test(new A());
        test(new ASon());
        test(new BSon());
        test(new IAImpl());
    }

    public static void test(double a) {
        System.out.println(a);
    }

    public static void test(A a) {
    }

    public static void test(B b) {

    }

    public static void test(IA ia) {

    }
}

class A {
}

class ASon extends A {
}

abstract class B {

}

class BSon extends B {

}

interface IA {
}

class IAImpl implements IA {
}

父子类方法重写中,方法声明中返回值类型的书写

1.基本类型: 必须保持一模一样,不存在类型提升

2.引用类型: 不必保持一模一样,存在自动向上转型,

  • 类:父类中的方法返回一个普通类类型
    • 子类方法中可以返回该类型
    • 也可以返回该类的子类类型
  • 抽象类:父类中的方法返回一个抽象类类型
    • 子类方法可以返回该类型
    • 也可以返回抽象类的实现类类型
  • 接口:父类中的方法返回一个接口类型
    • 子类方法可以返回该类型
    • 也可以返回接口的实现类类型

在方法中书写一个具体的返回值

1.基本类型: 方法体中,返回一个具体的值的时候,存在自动类型提升

2.引用类型: 方法体中,返回一个具体对象的时候,存在自动向上转型

  • 类:返回该类的对象或者该类的子类对象
  • 抽象类:返回抽象类的(具体)子类对象
  • 接口:返回接口的(具体)子类对象

C.链式调用(chain calls)

StudentDemo sd = new StudentDemo();
Student s = sd.getStudent();
s.show();
//类似这种形式的代码,可以写成链式调用的形式,就变成了
new StudentDemo().getStudent().show();
//结果是一样的
class Student{

    public Student getStudent(){
        return new Student();
    }

    public Teacher getTeacher(){
       return new Teacher();
    }
}
class Teacher{
    public void show(){
        System.out.println("秀一波~~~");
    }
}


内部类

引入

我们现在做一个应用程序,需要描述一台电脑(Computer)中的CPU,对于电脑而言

该怎么去描述这个CPU呢?

  • 它有存储设备,IO设备等等很多硬件资源组件
  • CPU是电脑中最重要的组件

这个CPU具有以下特点:

  • CPU可以调控计算中的所有硬件资源
    • CPU应该设计成一个类,并且是作为一个私有成员存在于计算机内部
  • CPU需要被隐藏起来,不能直接暴露在外部,也不能独立于计算机存在
    • 根据我们现在的知识,无法实现这一点
    • 因为声明一个普通类,无法声明为private修饰
  • 这个时候就想到能不能直接把CPU类丢到Computer类的内部呢?
    • 这样,有一层壳子,就能够很好的保护CPU
    • 这种嵌套定义的类,就是内部类

内部类的定义

  • 在Java语言中类可以嵌套定义,内部类(inner class)是定义在另一类当中的类

内部类的概述

  • 按照内部类在类中定义的位置不同:

    • 定义在成员位置的内部类,称之为成员内部类,普通成员内部类,静态内部类
    • 定义在局部位置的内部类,局部内部类,匿名内部类

  • 为了上课方便,我们需要统一口径,在内部类课程当中,我们统一规定

    • 像CPU这种类我们称之为内部类(inner class)
    • Computer这种类我们称之为外围类(enclosed class)
    • Demo这种类我们称之为外部类(outside class)

成员内部类

成员内部类概述

  • 成员内部类是最普通的内部类,它定义在另一个类的成员位置, 可以看成该类的一个成员

  • 语法

  • [访问权限修饰符] class EnclosedClazz{ //外围(普通)类的访问权限修饰符,只有两个,public和缺省
    	[访问权限修饰符] class InnerClazz{//内部类访问权限修饰符,有四个,和普通成员一样
        }
    }
    

成员内部类自身特点

成员内部类自身的特点

1,访问权限修饰符

2,成员特点

3,继承和实现

权限修饰符

成员内部类的访问权限修饰符可以是

  • public
  • protected
  • (default)缺省
  • private
成员特点

成员内部类的成员特点

成员变量

成员方法

构造器

  • 可以定义普通成员变量,成员方法
    • 不能定义静态方法、静态成员、静态代码块, 只要触发类加载的就不行,内部类当中就没有static声明,除了全局常量
    • 可以定义全局常量(因为全局常量是编译时就加入了常量池,不触发类加载)
  • 可以定义构造方法,和普通类并无差别

成员内部类类加载机制(重要)

类加载,什么时候成员内部类会类加载?
类加载: 类加载是懒加载,只有到迫不得已了才会加载一个类,类加载的时机:

  •      1,运行main方法
         2,new对象
         3,访问静态成员
         4,触发子类类加载会先触发父类类加载
    

成员内部类的直接类加载时机只有一种:

  •      就是创建成员内部类对象
    
  • 成员内部类的类加载要靠创建内部类对象触发,因为成员内部类没有静态成员
    • 一个外围类中定义了成员内部类后,并不代表每个外围类对象中都会自动初始化一个内部类对象
    • 只有在外围类对象的基础上,创建内部类的对象,才会触发成员内部类的类加载
    • 也就是说,成员内部类对象依赖于外围类的对象(即成员内部类实例化是在外围类实例的基础上)
  • 成员内部类对象依赖于外围类对象而存在
继承和实现

(了解)成员内部类的继承和实现

  • 内部类可以继承和实现外部的类和接口
  • 甚至可以在类中定义多个普通类、抽象内部类和接口用来自己继承和实现

成员内部类的访问特点

成员内部类的访问特点

  • 成员内部类内部访问外围类
  • 外围类访问成员内部类成员
  • 外部类访问成员内部类成员
  • 成员内部类访问外部类成员(了解)
成员内部类内部访问外围类

需要理解的是,成员内部类相当于外围类的成员

并且内部类对象依赖外围类,在成员内部类的成员方法中一定存在一个外围类对象(重要)

理解这一点,就好理解下面的现象了

  • 成员内部类可以无条件访问外围类的所有成员属性和成员方法(包括private成员和静态成员)

    • 需要注意的是,当外围类中有同名的属性或者方法时,都会发生类似“隐藏”的现象

      • 即默认情况下访问的都是成员内部类的成员
    • 如果要访问外围类的同名成员,需要以下面的形式进行访问:

      • EnclosedClazz.this.成员变量
        EnclosedClazz.this.成员方法
        
  • 这里有一个类似this、super一样的隐式引用,默认传给成员内部类的所有成员方法

    • 即EnclosedClazz.this
    • 该引用指向了外围类的对象
外围类访问成员内部类成员

虽然成员内部类可以无条件地访问外围类的成员,而外围类想访问成员内部类的成员却不是这么随心所欲了

原因在于内部类访问外围类时,内部类对象和外围类对象一定都已经存在;

但外围类访问内部类时,内部类对象还不存在,所以必须手动创建内部类对象

当然如果想要访问内部类中的全局常量,直接内部类名点常量名即可

  • 在外围类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象

  • 再通过这个内部类的引用去访问内部类的成员

  • 在外围类中创建内部类语法:最容易理解,最万能的形式如下:

    • EnclosedClazz oz = new EnclosedClazz();
      InnerClazz ic = oz.new InnerClazz();//该形式适合在外围类中创建内部类
      //或者
      InnerClazz ic2 = new EnclosedClazz().new InnerClazz();//该形式适合在外围类中创建内部类
      
    • 如果访问内部类的方法是静态的,则只能使用上述格式,因为静态方法中外围类对象可能不存在

    • 如果访问内部类的方法是普通成员方法,则可以省略创建外围类对象的过程,其语法如下

    • InnerClazz ic = new InnerClazz();
      
  • 在外围类中一旦创建内部类对象,使用该对象可以访问内部类的所有成员,包括私有

外部类访问成员内部类成员

外部类要访问内部类成员,条件要苛刻的多

由于内部类属于外围类的一个成员,所以内部类受访问权限的限制

  • 如果该内部类是private修饰,那么显然在任何外部类中都无法访问该内部类成员

  • 如果外部类拥有内部类的访问权限,可以创建该内部类对象,来访问该内部类的成员

  • 语法

  • //该方式最全面,适合任何位置
    EnclosedClazz.InnerClazz ic3 = new EnclosedClazz().new InnerClazz();
    
  • 和外围类创建内部类对象不同,在外部类中创建内部类对象,不能够访问内部类的私有成员

成员内部类访问外部类成员(了解)

在成员内部类中访问外部类成员,和在普通类中访问其它类成员别无二致

  • 静态成员直接类名点访问

  • 普通成员需创建外部类对象

  • 受访问权限控制

牛刀小试

试着说一说下述访问,在成员内部类的情况下,能否进行,怎么进行

  • 内部类的成员方法中,去访问外围类的成员(普通或静态、私有成员)

能访问,可以直接访问,外围类的对象已经存在了,也不受访问权限限制

this表示内部类自身对象

外围类类名.this表示外围类对象

  • 内部类的成员方法中,去访问外部类的成员(普通或静态、私有成员)

能访问,需要创建对象,受访问权限限制

  • 外围类的普通成员方法,访问内部类的成员(普通和全局常量、私有成员)

直接创建内部类对象即可,不受访问权限限制

this表示外围类对象

内部类对象是我们创建出来

  • 外围类的静态成员方法,访问内部类的成员(普通和全局常量、私有成员)

不能直接创建内部类对象,因为没有外围类对象支撑,需要先创建外围类对象,不受访问权限限制

  • 外部类的普通成员方法,访问内部类的成员(普通和全局常量、私有成员)

先创建外围类对象,然后在此基础上创建内部类对象,注意这整个过程都受访问权限限制

对象创建完之后,访问受权限限制

  • 外部类的静态成员方法,访问内部类的成员(普通和全局常量、私有成员)

和普通方法是一样的

静态内部类

思考:怎么把一个普通成员变成一个静态成员?

  • 静态内部类也是处在外围类成员位置的内部类,不同的是它需要使用static修饰

  • 语法:

  • [访问权限修饰符] class EnclosedClazz{ //外围(普通)类的访问权限修饰符,只有两个,public和缺省
    	[访问权限修饰符] static class InnerClazz{//内部类访问权限修饰符,有四个,和普通成员一样
        }
    }
    

静态内部类自身特点

静态内部类自身的特点

1,访问权限修饰符

2,成员特点

3,继承和实现

  • 静态内部类和成员内部类的最大区别就是static关键字

  • Oracle公司官网有一段文字解释静态内部类和成员内部类的区别

    • Nested classes that are declared static are called static nested classes. Non-static nested classes are called inner classes.
      
  • 嵌套的static类就是静态内部类 然后不用static修饰的嵌套类 就是成员内部类

  • 什么意思呢?解释一下

    • 字面意思上看,它在说静态内部类叫做嵌套类,非静态内部类(成员内部类)叫做内部类
    • 什么叫nested呢?
      • 就是直接把一个类丢到另一个类中,本来我和你没有太多的关系,我完全有能力独立的自己做一个类
      • 但是我偏做一个”寄居蟹、啃老族“,借外围类的壳子用一用,来隐藏和保护自己(猥琐)
    • 什么叫inner呢?
      • 就是我处在你的内部,是你的一部分,我了解你,我知道你的全部,没有你就没有我
      • 比如心脏和人,CPU和计算机
  • 静态内部类的类加载机制(重要)

    • 静态内部类是独立存在于外围类中的
      • 创建外围类对象,不会触发静态内部类加载
      • 创建静态内部类对象,也不会触发外围类的类加载
      • 静态内部类创建对象不依赖于外围类
    • 类加载的过程对于内部类和外围类是完全独立的
访问权限修饰符

静态内部类的访问权限修饰符可以是

  • public
  • protected
  • (default)缺省
  • private
成员特点

静态内部类的成员特点

成员变量

成员方法

构造器

  • 静态内部类可以创建普通类可以创建的所有成员,包括静态
  • 体现了静态内部类的独立性
继承和实现(了解)
  • 静态内部类的继承与实现和成员内部类并没有太大不同
  • 区别在于,静态内部类只能继承一个静态内部类,而不能继承普通类

静态内部类的访问特点

静态内部类的访问特点

  • 静态内部类内部访问外围类
  • 外围类访问静态内部类成员
  • 外部类访问静态内部类成员(了解)
  • 静态内部类访问外部类成员(了解)
静态内部类内部访问外围类

静态内部类创建对象的时候,完全可能没有外围类对象

理解这一点和成员内部类的不同,就好理解下述现象了

  • 静态内部类只能直接访问外围类的静态成员,包括私有
  • 静态内部类如果想要访问外围类的普通成员,需要创建对象
    • 和一般类创建对象访问成员不同的是,静态内部类中创建外围类不受访问权限限制
  • 静态内部类中不存在一个外围类对象的引用,该对象完全可能不存在
  • 如果想要调用外围类中,和静态内部类同名的静态成员,只需要外围类名点成员即可
  • 如果想要调用外围类中,和静态内部类同名的普通成员,只需要创建外围类对象,对象点即可
外围类访问静态内部类成员

静态内部类是相对外围类独立的类,在外围类中访问静态内部类成员

除了不受访问权限限制外,和访问其他一般类并无差别

  • 如果访问静态内部类中的静态成员,可以直接内部类名点,不受访问权限限制

  • 如果访问静态内部类中的普通成员,需要创建内部类对象,不受访问权限限制

    • 在外围类的任何地方创建静态内部类对象,都可以用以下语法

    • 最大的区别是不需要创建外围类对象,因为是相互独立的

    • InnerClazz ic = new InnerClazz();
      
外部类访问静态内部类成员

静态内部类在外部创建对象,可以完全独立于外围类对象,不会触发外围类的类加载

  • 创建静态内部类对象

    • 语法

    • EnclosedClazz.InnerStaticClazz ecisc = new EnclosedClazz.InnerStaticClazz();
      
    • 和普通类对象访问成员一样,受访问权限限制

  • 访问静态成员,无需创建对象,直接

    • EnclosedClazz.InnerStaticClazz.静态成员名
      
    • 受访问权限限制

静态内部类访问外部类成员(了解)

在静态内部类中,访问外部类成员,和在普通类中访问其他类成员别无二致

  • 静态成员,类名点直接访问
  • 普通成员需创建对象访问
  • 受访问权限控制

牛刀小试

试着说一说下述访问,在静态内部类的情况下,能否进行,怎么进行

  • 内部类的成员方法中,去访问外围类的普通成员(包括私有)

创建外围类对象,然后随便访问

  • 内部类的成员方法中,去访问外围类的静态成员(包括私有)

直接类名点访问,不受访问权限限制

  • 外围类的普通成员方法,访问内部类的成员(属性和方法包括私有成员)

创建对象然后随便访问

  • 外围类的静态成员方法,访问内部类的成员(属性和方法包括私有成员)

创建对象然后随便访问


课上代码

public class Demo {
    public static void main(String[] args) {
        //创建静态内部类对象
        EnclosedClazz.StaticInnerClazz ecsic = new EnclosedClazz.StaticInnerClazz();
        //System.out.println(ecsic.b);
    }
}

class EnclosedClazz {
    //定义外围类成员
    int a;
    private int b = 100;
    static int c = 200;
    static final int D = 300;

    public void testEnclosed() {
        StaticInnerClazz sic = new StaticInnerClazz();
    }

    public static void testStatic() {
        StaticInnerClazz sic = new StaticInnerClazz();
    }

    //定义静态内部类
    static class StaticInnerClazz {
        //成员变量
        int a;
        private int b = 10;
        static int c = 20;
        static final int D = 30;

        public void testStaticM() {
            //反正没有外围类对象,需要创建对象
            //直接创建外围类对象即可
            EnclosedClazz ec = new EnclosedClazz();
            System.out.println(ec.a);
            System.out.println(ec.b);
            System.out.println(a);
            System.out.println(this.b);
        }

        public static void testStaticS() {
            //直接创建外围类对象
            EnclosedClazz ec = new EnclosedClazz();
        }
    }
}

局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类

它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内

将局部内部类看成是局部变量即可

局部内部类自身特点

局部内部类自身的特点

1,访问权限修饰符

2,成员特点

3,定义位置

4,继承和实现

访问权限修饰符
  • 局部内部类和局部变量一样,没有访问修饰权限,因为毫无意义,大括号已经限制了它的访问范围

  • 局部内部类不能用static关键字修饰,原因和局部变量一样

成员特点
  • 局部内部类内部无法定义静态成员,局部内部类中也没有静态的概念
    • 其余变量和方法都是可以创建的
  • 构造方法和普通类一致,用来给自身成员赋值
  • 和成员内部类的成员特点是一致的
定义位置

几乎所有的局部位置都可以使用局部内部类

包括但不限于

  • 方法

  • 代码块

  • if分支

  • for循环内部

  • 局部内部类的作用:当我们在局部位置,碰到了一个麻烦的问题,需要使用类来解决

    • 但是又不希望这个类被外界知道,这种情况需要使用局部内部类
  • 局部内部类在使用前要有合适的理由,不然会自找麻烦

局部内部类的访问特点

前提:

  • 局部内部类相当于方法的局部变量,只在方法内部生效

  • 要想触发局部内部类的类加载,必须在该方法内部创建该内部类对象才可以

  • 若该方法是普通成员方法

    • 该局部内部类,可以无条件访问,外围类的所有成员 (原因是普通成员方法隐含this 已经有外围类对象了)
    • 局部内部类和局部变量一样,出了作用域就失效了
    • 在外围类中无法创建对象,外部类更不行,只能在方法内部创建实例
  • 若该方法时静态成员方法

    • 该局部内部类,可以无条件访问,外围类的所有成员,但是需要创建外围类对象(原因是没有this)
    • 局部内部类和局部变量一样,出了作用域就失效了
    • 在外围类中无法创建对象,外部类更不行,只能在方法内部创建实例
  • 要想使用局部内部类的功能,必须在该方法内部创建内部类对象,然后调用方法

局部内部类注意事项

  • 值得注意的是:

    • Java8之前的版本中,局部内部类只能访问方法中加了final的局部变量
      • 局部内部类对象的生命周期和局部变量的生命周期是有冲突的(life cycle)
        • 局部内部类对象的有可能比局部变量存活更久
      • 为了解决冲突,JVM偷偷的帮我们把局部变量塞到了局部内部类对象的成员中了
      • 但是问题仍然存在,将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的
        • 也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变
        • 最终Java开发者选择了妥协,使用final标记局部变量,这样就不能够改变它的值了
    • Java8以后的版本,仍然也是这么做的,但是将final关键字隐藏在底层代码中了
      • 也就是说,底层代码仍然会给局部变量加final
      • 拥有局部内部类的方法的局部变量仍然是final修饰的
      • 只不过从显式变成了隐式
  • 局部内部类最大的优势是对方法外部完全隐藏,除了方法本身,即便是当前类也不知道它,不能访问它

    • 这是一种极致的封装思想的体现
  • 匿名内部类和lambda本质依然是局部内部类,这一条注意事项仍然生效

局部内部类的经典使用

写方法返回接口或者抽象类的具体子类

class Enclosed3Clazz{
    public A getA(){
        class AImpl implements A{
            @Override
            public void test() {
            }
        }
        return new AImpl();
    }
}
interface A{
    void test();
}

鸡刀小试

补全程序,使得可以输出三个num

class Outer {
    public int num = 10;
    class Inner {
        public int num = 20;
        public void show() {
            int num = 30;
            System.out.println();
            System.out.println();
            System.out.println();
        }
    }
}

内部类的优缺点和使用场景

写在后面的话

Java 1.1版本就引入了内部类(原属于C++)的概念,虽然很多人觉得内部类的引入让Java程序的扩展性提升了

但也有很多人觉得内部类的引入违背了Java简化C++的初衷,因为内部类的语法过于复杂

以至于让人提不起兴趣去使用,那么这样看似精致巧秒、实则复杂没必要的设计,到底有什么意义呢?

这个问题大家可以自己去思考,接下来我们讲几个内部类的使用场景以及内部类的缺点

  • 场景一:无条件地访问外围类的所有元素(优点)
    • 无论是成员内部类、静态内部类、局部内部类还是匿名内部类都可以无条件访问
  • 场景二:隐藏类
    • 可以用private、protected修饰类
    • private修饰成员内部类、提供public的创建对象方法
  • 场景三:实现多继承
    • 可以创建多个成员内部类继承外部多个类
    • 然后创建内部类对象,实际上就是外围类继承了多个类的成员
  • 场景四:通过匿名内部类来优化简单的接口实现/lambda表达式更简洁
    • 重点:内部类要说使用频率 最高的肯定是匿名内部类

内部类的缺点

  • 内部类的缺点也是显而易见,语法很复杂,在类中定义内部类也会导致类的结构变复杂,影响代码可读性
    • 除此之外,不合理使用内部类还可能导致内存泄漏(了解)
      • 如果当内部类的对象被外围类以外的其他类引用时,就会造成内部类和外围类无法被GC回收的情况
    • 内部类就应该给外围类用,不需要给外部类用,给它用是有风险的

你可能感兴趣的:(Java学习,java)