【Java】抽象类和接口 + Object类、内部类 —— 有码有图有真相

目录

1. 抽象类

1.1 抽象类概念

1.2 抽象类语法:abstract关键字

1.3 抽象类特性

1.4 抽象类的作用

2. 接口

2.1 接口的概念

2.2 语法规则:interface关键字

2.3 接口使用与特性:implements关键字

2.5 实现多个接口

2.6 接口间的继承

2.7 抽象类和接口的区别

3. Object 类

3.1 获取对象信息

3.2 对象比较equals方法

3.3 hashcode方法(简单介绍)

4. 内部类

4.1 内部类的分类

4.1.1 实例内部类

4.1.2 静态内部类

4.1.3 局部内部类

4.1.4 匿名内部类 — 日常开发中用的最多


1. 抽象类

1.1 抽象类概念

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

即:抽象类不是一个具体的类,不能描述具体的对象。

比如:

说明:

1、矩形、三角形、圆形都是图形,因此和Shape类的惯性应该是继承关系

2、虽然图形图Shape中也存在draw的方法,但由于Shape类并不是具体的图形,因此其内部的draw方法实际是没有办法实现的

3、由于Shape类没有办法描述一个县体的图形,导致其draw()方法无法具体实现,因此可以将Shape类设计为“抽象类”

在打印图形例子中,我们发现,父类 Shape 中的 draw 方法好像并没有什么实际工作,主要的绘制图形都是由 Shape的各种子类的 draw 方法来完成的。像这种没有实际工作的方法,我们可以把它设计成一个 抽象方法(abstract method)包含抽象方法的类我们称为 抽象类(abstract class)

1.2 抽象类语法:abstract关键字

在Java中,一个类如果被 abstract 修饰称为抽象类抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。

// 抽象类:被abstract修饰的类
public abstract class Shape {
    
    // 抽象方法:被abstract修饰的方法,没有方法体
    public abstract void draw();

    // 抽象类也是类,也可以增加普通方法和属性
    public String name;
    public static int a;

    public void eat() {
    }
}

注意:抽象类也是类,内部可以包含普通方法和属性,构造方法

抽象类的形状就跟棒球一样。

1.3 抽象类特性

  1. 使用 abstract 修饰的方法称为抽象方法
  2. 类中包含抽象方法,这个类也必须由 abstract 修饰,称为抽象类
  3. 抽象类不能直接实例化对象
    Shape shape = new Shape();
    // 编译出错
    Error:(30, 23) java: Shape是抽象的; 无法实例化
  4. 抽象类当中可以和普通类一样,定义成员变量和成员方法
  5. 当一个普通的类继承了抽象类,需要重写抽象类当中的所有的抽象方法。
  6. 抽象类的出现,就是为了被继承。被继承后能发生向上转型
  7. 抽象方法不能被 private 修饰
  8. 抽象方法不能被 finalstatic 修饰,因为抽象方法要被子类重写。
  9. 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
  10. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量

抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰。

出来混迟早是要还的,最终继承的子类要重写前面全部的抽象方法。

abstract class Shape {
    public abstract void draw();
}

abstract class A extends Shape {
    public abstract void testA();
}

class B extends A {
    @Override
    public void draw() {
    }
    @Override
    public void testA() {
    }
}

代码演示:

abstract class Shape {
    public abstract void draw();
    /*public String name;
    public static int a;
    public void eat() {
    }*/
}

class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("●!");
    }
}

public class TestDemo {
    public static void main(String[] args) {
        //Shape shape = new Shape(); //编译报错,Shape是抽象类,不能被实例化
        Shape shape = new Cycle();
        shape.draw();
    }
}
//运行结果
●!

1.4 抽象类的作用

抽象类本身不能被实例化,要想使用,只能创建该抽象类的子类,然后让子类重写抽象类中的抽象方法。

  • 普通的类也可以被继承,普通的方法也可以被重写,为啥非得用抽象类和抽象方法呢?

因为使用抽象类相当于多了一重编译器的校验

  • 使用抽象类的场景就如上面的代码,实际工作不应该由父类完成,而应由子类完成。那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的。但是父类是抽象类就会在实例化的时候提示错误,让我们尽早发现问题。
  • 很多语法存在的意义都是为了 "预防出错",例如我们曾经用过的 final 也是类似。创建的变量用户不去修改,不就相当于常量嘛? 但是加上 final 能够在不小心误修改的时候,让编译器及时提醒我们。
  • 充分利用编译器的校验,在实际开发中是非常有意义的。

2. 接口

2.1 接口的概念

在现实生活中,接口的例子比比皆是,比如:笔记本上的USB口,电源插座等。

电脑的USB口上,可以插:U盘、鼠标、键盘...所有符合USB协议的设备

电源插座插孔上,可以插:电脑、电视机、电饭煲...所有符合规范的设备

通过上述例子可以看出:

  • 接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
  • 在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型

2.2 语法规则:interface关键字

接口的定义格式与定义类的格式基本相同,将 class 关键字换成 interface 关键字,就定义了一个接口。

public interface 接口名称{
    // 抽象方法
    public abstract void method1(); // public abstract 是固定搭配,可以不写
    public void method2();
    abstract void method3();
    void method4();
    // 注意:在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁
}

提示:

  1. 创建接口时,接口的命名一般以大写字母 I 开头
  2. 接口的命名一般使用 "形容词" 词性的单词
  3. 阿里编码规范中约定,接口中的方法和属性不要加任何修饰符号,保持代码的简洁性。

2.3 接口使用与特性:implements关键字

接口不能直接使用,必须要有一个“实现类”来“实现”该接口,实现接口中的所有抽象方法。

public class 类名称 implements 接口名称{
// ...
}

子类和父类之间是 extends 继承关系,类与接口之间是 implements 实现关系。

接口特性:

1、接口是使用 interface 关键字来修饰的

2、接口当中不能有被实现的方法,意味着只能有抽象方法但是两个方法除外(jdk8中引入的):一个是static修饰的方法,一个是被default修饰的方法。 static修饰的方法不能被重写,直接可以通过接口名调用default修饰的方法 可重写也可不重写(下面代码介绍)。接口可以认为比抽象类更抽象。

3、接口当中的抽象方法默认都是public abstract 修饰的(只能是,其他修饰符是错误的;两个关键字可以随意排序)

4、接口当中的成员变量默认都是 public static final 修饰的(三个关键字可以随意排序)

interface Shape {
    //接口当中的成员变量都是 public static final 修饰的
    public int a = 1;
    public static int b = 2;
    public static final int c = 3;
    //意味着为常量,大写
    int A = 1;
    int B = 2;
    int C = 3;

    //接口当中的抽象方法默认都是public abstract修饰的
    void draw();
    //public void draw();
    //public abstract void draw();

    // 不能有方法的实现
    public void test() {    //编译报错
        System.out.println("123");
    }

    //有static 或者 define关键字修饰的方法可以实现,JDK8 新引入的
    default public void test1(){
        System.out.println("test1");
    }
    public static void test2() {
        System.out.println("test2");
    }
}

5、接口不能进行实例化,但在被实现后能发生向上转型

6、类和接口之间的关系,可以使用 implements 来进行关联,叫做这个类实现了接口。

7、如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类。但是出来混迟早是要还的

8、接口中不能有静态代码块和构造代码块,也没有构造方法

9、接口虽然不是类,但是接口编译完成后也有对应的字节码文件,后缀格式也是.class

10、重写接口中方法时,不能使用默认的访问权限。重写的子类的访问权限要大于等于父类。

接口中方法默认被public abstract修饰的,所以子类的这个重写的方法的访问权限只能是public

interface Shape {
    /*
    {}
    static {}
    public Shape () {
    }
    */

    //接口当中的抽象方法默认都是public abstract修饰的
    void draw();
}
class Rect implements Shape {
    public void draw() {
        System.out.println("矩形!");
    }
}

class Flower implements Shape {
    @Override
    public void draw() {
        System.out.println("❀!");
    }
}

接口可以去引用实现了该接口的具体的类型。 接口也能发生向上转型和动态绑定,来实现多态。

一个类实现了一个接口,通过这个接口引用可以指向这个类的具体对象。

public class Test {
    public static void drawMap(Shape shape) {
        shape.draw();
    }
    public static void main(String[] args) {
        //Shape shape = new Shape();//不能被实例化
        Shape shape1 = new Rect();
        Shape shape2 = new Flower();
        drawMap(shape1);
        drawMap(shape2);
        System.out.println("====================");

        Shape[] shapes = {new Rect(),new Flower(),new Rect(),new Flower()};

        for (Shape shape: shapes) { 
            shape.draw();
        }
    }
}
//运行结果
矩形!
❀!
====================
矩形!
❀!
矩形!
❀!

代码示例:

实现笔记本电脑使用USB鼠标、USB键盘的例子
1. USB接口:包含打开设备、关闭设备功能
2. 笔记本类:包含开机功能、关机功能、使用USB设备功能
3. 鼠标类:实现USB接口,并具备点击功能
4. 键盘类:实现USB接口,并具备输入功能

//USB接口
public interface USB {
    void openDevice();
    void closeDevice();
}
//鼠标类:实现USB接口
public class Mouse implements USB {
    @Override
    public void openDevice() {
        System.out.println("鼠标开始工作!");
    }
    public void click() {
        System.out.println("疯狂点击鼠标!");
    }
    @Override
    public void closeDevice() {
        System.out.println("鼠标关闭工作!");
    }
}
//键盘类:实现USB接口
public class KeyBoard implements USB {
    public void openDevice() {
        System.out.println("键盘开始工作!");
    }
    public void input() {
        System.out.println("疯狂敲击键盘!");
    }
    public void closeDevice() {
        System.out.println("键盘结束工作!");
    }
}
//笔记本类:使用USB设备
public class Computer {
    public void powerOn() {
        System.out.println("打开电脑!");
    }
    public void powerOff() {
        System.out.println("关闭电脑");
    }
    public void useService(USB usb) {
        usb.openDevice();
        if (usb instanceof Mouse) {
            Mouse mouse = (Mouse) usb;//向下转型,调用子类特有的方法
            mouse.click();
        } else if (usb instanceof KeyBoard) {
            KeyBoard keyBoard = (KeyBoard) usb;
        }
        usb.closeDevice();
    }
}

测试类: 

public class Test {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.powerOn();

        Mouse mouse = new Mouse();
        computer.useService(mouse);//向上转型
        System.out.println("=====================");

        KeyBoard keyBoard = new KeyBoard();
        computer.useService(keyBoard);//向上转型
        computer.powerOff();
    }
}
//运行结果
打开电脑!
鼠标开始工作!
疯狂点击鼠标!
鼠标关闭工作!
=====================
键盘开始工作!
键盘结束工作!
关闭电脑

接口中被static修饰的方法不能被重写,直接可以通过接口名调用。

被default修饰的方法,可重写也可不重写

不重写默认调用的是接口的方法;重写调用重写后的方法。

代码演示:

public interface ITest {
    //必须被重写
    void testA();
    //一定不能重写
    public static void staticMethod () {
        System.out.println("这是staticMathe()");
    }
    //可重写可不重写
    default public void defaultMethod() {
        System.out.println("这是defaultMethod()");
    }
}

测试类:

public class TestDemo implements ITest{
    @Override
    public void testA() {
        System.out.println("重写test()");
    }
    @Override
    public void defaultMethod() {
        System.out.println("重写的defaultMethod()");
    }
}
class Test {
    public static void main(String[] args) {
        ITest.staticMethod();
        //TestDemo testDemo = new TestDemo();
        //testDemo.defaultMethod();//被继承下来的部分,子类访问父类成员方法
        ITest iTest = new TestDemo();//发生向上转型
        iTest.defaultMethod();//没有重写default修饰的方法,父类引用调用自己独有的方法
                              //重写后调用重写的方法
    }
}
//运行结果
这是staticMathe()
重写的defaultMethod()

2.5 实现多个接口

在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。下面通过类来表示一组动物。

bstract class Animal {
    public String name;
    public int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public abstract void eat();

    /*public void fly(){}
    //不建议在抽象类中写,不是所有动物都会飞,会跑,会游泳的
    public void run() {}
    public void swim() {}*/
}

另外我们再提供一组接口, 分别表示 "会飞的", "会跑的", "会游泳的"

interface IFly {
    void fly();
}

interface IRun {
    void run();
}

interface ISwim {
    void swim();
}

接下来我们创建几个具体的动物

狗会跑;青蛙会跑,游泳;鸭子会跑,游泳,飞。

注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。

提示, IDEA 中使用 ctrl + i 快速实现接口

class Dog extends Animal implements IRun {

    public Dog(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        System.out.println(this.name + " 正在吃狗粮!");
    }
    @Override
    public void run() {
        System.out.println(this.name + " 四条腿猛猛跑!");
    }
}

class Frog extends Animal implements IRun, ISwim {
    public Frog(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        System.out.println(this.name + " 正在吃蛙粮!");
    }
    @Override
    public void run() {
        System.out.println(this.name + " 两条腿跳着跑!");
    }
    @Override
    public void swim() {
        System.out.println(this.name + " 蛙泳唰唰游!");
    }
}

class Duck extends Animal implements IRun, ISwim, IFly {
    public Duck(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        System.out.println(this.name + " 正在吃鸭粮!");
    }
    @Override
    public void fly() {
        System.out.println(this.name + " 酷酷飞!");
    }
    @Override
    public void run() {
        System.out.println(this.name + " 两条腿迈着跑!");
    }
    @Override
    public void swim() {
        System.out.println(this.name + " 滑着游泳!");
    }
}

上面的代码展示了 Java 面向对象编程中最常见的用法:一个类继承一个父类,同时实现多种接口。

继承表达的含义是 is - a 语义,而接口表达的含义是 具有 xxx 特性

  • 狗是一种动物,具有会跑的特性(功能)
  • 青蛙也是一种动物,具有跑,游泳的特性
  • 鸭子也是一种动物,具有跑,游泳和飞的特性

测试类:

public class Test {
    public static void func1(Animal animal) {
        animal.eat();
    }
    public static void running (IRun iRun) {
        iRun.run();
    }
    public static void swimming (ISwim iSwim) {
        iSwim.swim();
    }
    public static void flying (IFly iFly) {
        iFly.fly();
    }
    public static void main(String[] args) {
        func1(new Dog("六六", 3));
        func1(new Frog("蛙蛙", 2));
        func1(new Duck("唐老鸭", 5));

        System.out.println("=========================");
        running(new Dog("六六", 3));
        running(new Frog("蛙蛙", 2));
        running(new Duck("唐老鸭", 5));

        System.out.println("=========================");
        swimming(new Frog("蛙蛙", 2));
        swimming(new Duck("唐老鸭", 5));

        System.out.println("=========================");
        flying(new Duck("唐老鸭", 5));
    }
}
//运行结果
六六 正在吃狗粮!
蛙蛙 正在吃蛙粮!
唐老鸭 正在吃鸭粮!
=========================
六六 四条腿猛猛跑!
蛙蛙 两条腿跳着跑!
唐老鸭 两条腿迈着跑!
=========================
蛙蛙 蛙泳唰唰游!
唐老鸭 滑着游泳!
=========================
唐老鸭 酷酷飞!
  • 这样设计有什么好处呢? 时刻牢记多态的好处,让程序猿忘记类型有了接口之后,类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力。
  • 在这个 runing 方法内部,我们并不关注到底是不是动物,只要参数是会跑的就行。例如,写一个机器人类,具备跑的特性。
class Robot implements IRun {
    public String name;
    public Robot(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        System.out.println(this.name + "机器人会跑!");
    }
}

running(new Robot("大黄蜂"));
//执行结果
大黄蜂机器人会跑!

为什么说接口解决了Java的多继承问题:

  • 不是所有的动物都具备这些功能,需要把这些功能(行为分开),让这个动物具有指定的特性不能当甩手掌柜。
  • 接口是行为的规范,标准的规范;
  • 让程序员忘记类型有了接口之后,类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力。

2.6 接口间的继承

在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。
接口可以继承一个接口,达到复用的效果。使用 extends 关键字。

  • 类 与 类 之间的关系 -》 extends 继承
  • 类 和 接口 之间的关系 -》implements 实现
  • 接口 与 接口 之间的关系 -》 extends 拓展,接口间的继承相当于把多个接口合并在一起。
interface A {
    void testA();
}
interface B {
    void testB();
}

//接口 extends 接口 —》 拓展
interface C extends A, B {
    void testC();
}

// 类 implements 接口 -》 实现
class TestDemo implements C {
    @Override
    public void testA() {
    }
    @Override
    public void testB() {
    }
    @Override
    public void testC() {
    }
}

2.7 抽象类和接口的区别

抽象类和接口都是 Java 中多态的常见使用方式。都需要重点掌握,同时又要认清两者的区别(重要!!!常见面试题)

核心区别:

  • 抽象类中可以包含普通方法和普通属性,这样的普通方法和属性可以被子类直接使用(不必重写)
  • 接口中不能包含普通方法,子类必须重写所有的抽象方法。因此接口比抽象类更加抽象。

比如之前写的 Animal 例子。此处的 Animal 中包含两个属性name和age,这个两个属性在任何子类中都是存在的,因此此处的 Animal 只能作为一个抽象类, 而不应该成为一个接口

class Animal {
    public String name;
    public int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

再次提醒:

抽象类存在的意义是为了让编译器更好的校验,像Animal这样的类我们并不会直接使用,而是使用它的子类。万一不小心创建了Animal的实例,编译器会及时提醒我们。

3. Object 类

Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。

即所有类的对象都可以使用Object的引用进行接收。

  • 现阶段先学习使用:clone(),equals(),hashCode(),toString(),这四种方法。
  • 线程阶段学习使用:notify(),notifyAll(),wait(),finalize(),这几种方法。
  • 反射阶段学习使用:getClass()。

范例:使用Object接收所有类的对象

class Person{}
class Student{}
public class Test {
    public static void main(String[] args) {
        function(new Person());
        function(new Student());
    }
    public static void function(Object obj) {
        System.out.println(obj);
    }
}
//执行结果:
Person@1b6d3586 
Student@4554617c

所以在开发之中,Object类是参数的最高统一类型但是Object类也存在有定义好的一些方法。如下:

对于整个Object类中的方法需要实现全部掌握。

本篇博客当中,我们主要来熟悉这几个方法:toString()方法,equals()方法,hashcode()方法。clone()方法会在下一篇博客中介绍到。
 

3.1 获取对象信息

如果要打印对象中的内容,可以直接重写Object类中的toString()方法,之前已经讲过了,此处不再累赘。

Object类中的toString()方法实现:

3.2 对象比较equals方法

在Java中,== 进行比较时:

  1. 如果 == 左右两侧是基本类型变量,比较的是变量中值是否相同
  2. 如果 == 左右两侧是引用类型变量,比较的是引用变量地址是否相同
  3. 如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的.

Object类中的equals方法:

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

Person类重写equals方法后,然后比较:

class Person implements Cloneable {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //重写 equals
    @Override
    public boolean equals(Object obj) {
        Person tmp = (Person) obj;
        return this.name.equals(tmp.name)
                && this.age == tmp.age;
    }
}

也可以不自己重写equals方法,由编译器生成(Alt + Insert )

这里的hashCode()方法,定义对象在内存中的位置。一般与equals方法配合使用,后续会讲到。

结论:比较对象中内容是否相同的时候,一定要重写equals方法。
 

3.3 hashcode方法(简单介绍)

hashCode()方法,帮我算了一个具体的对象位置。后续在数据结构中会详细讲解,这里了解即可。

hashcode方法源码:

该方法是一个native方法,底层是由C/C++代码写的,特点是:快。我们看不到。

4. 内部类

当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么这个内部的完整结构最好使用内部类。

在 Java 中,可以将一个类定义在另一个类或者一个方法的内部,前者称为内部类,后者称为外部类。内部类也是封装的一种体现。

比如:一串糖葫芦,是由一个个山楂构成的,而这里的内部类就是山楂,一串糖葫芦就是外部类。

public class OutClass {
class InnerClass{


        }
}

// OutClass是外部类
// InnerClass是内部类

1、定义在class 类名{}花括号外部的,即使是在一个文件里,都不能称为内部类

public class A{
}
class B{
} 
// A 和 B是两个独立的类,彼此之前没有关系

2、内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件
 

4.1 内部类的分类

先来看下,内部类都可以在一个类的哪些位置进行定义:

public class OutClass {
    // 成员位置定义:未被static修饰 --->实例内部类
    public class InnerClass1{
    } 
    // 成员位置定义:被static修饰 ---> 静态内部类
    static class InnerClass2{
    }
    public void method(){
    // 方法中也可以定义内部类 ---> 局部内部类:几乎不用
        class InnerClass5{
        }
    }
}

根据内部类定义的位置不同,一般可以分为以下几种形式:

  1. 实例内部类:未被static修饰的成员内部类
  2. 静态内部类:被static修饰的成员内部类
  3. 匿名内部类:没有名字的内部类。
  4. 局部内部类:定义在方法体内部。不谈修饰符,几乎不用。

注意:内部类其实日常开发中使用并不是非常多,大家在看一些库中的代码时候可能会遇到的比较多,日常开发中使用最多的是匿名内部类。

  • 在外部类中,内部类定义位置与外部类成员所处的位置相同,因此称为成员内部类。成员内部类包含:普通(实例)内部类 和 静态内部类。
  • 说内部类一定要加上前缀,每种内部类特点不一样。

4.1.1 实例内部类

即未被static修饰的成员内部类。

实例内部类在创建对象时需要先创建外部类的对象,通过外部类对象的引用来实例化成员。

  • 实例内部类是定义在另一个类的实例内部的类。它依赖于外部类的实例存在,因此每次创建外部类的实例时,实例内部类也会被创建。
  • 要访问实例内部类中成员,必须要创建实例内部类的对象。而普通内部类定义与外部类成员定义位置相同,因此创建实例内部类对象时必须借助外部类。

【注意事项】

  1. 实例内部类对象必须在先有外部类对象前提下才能创建
  2. 内部类中不能定义被static修饰的成员加final 关键字修饰的成员变量可以定义(改为常量,编译时确认的),成员方法不行。(下方有详细讲解)
  3. 实例内部类的方法中可以直接访问外部类中的任何成员。
  4. 实例内部类包含两个this:一个是自己的this,另一个是外部类的this‌。在实例内部类中,可以通过外部类类名.this来访问外部类的this‌
  5. 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名称.this.同名成员 来访问
  6. 实例内部类的非静态方法中包含了一个指向外部类对象的引用,this
  7. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。
  8. 实例内部类所处的位置与外部类成员变量位置相同,因此也受public、private等访问限定符的约束

代码演示:

class OuterClass {
    public int data1 = 1;
    public static int data2 = 2;
    private int data3 = 3;

    //实例内部类
    class InnerClass {
        public int data1 = 111;
        public int data4 = 4;
        public static final int data5 = 5;//编译的时候就确认
        private int data6 = 6;
        public void test() {
            System.out.println("这是实例内部类InnerClass::test()" + data5);//"InnerClass::test()" + 5
            //如果外部类和实例内部类中具有相同名称成员时,优先访问的是内部类自己的
            System.out.println(data1);
            System.out.println(this.data1);
            //如果要访问外部类同名成员时候,必须:外部类名称.this.同名成员名字
            System.out.println(OuterClass.this.data1);
            System.out.println(data2);
            System.out.println(data3);
            //在实例内部类中可以直接访问外部类中:任意访问限定符修饰的成员
            System.out.println(data4);
            System.out.println(data5);
            System.out.println(data6);
            System.out.println("=====================================");
        }
    }
    public void test() {
        System.out.println("这是外部类OuterClass::test()");
        // 要访问实例内部类中成员,必须要创建实例内部类的对象
        InnerClass innerClass = new InnerClass();
        System.out.println(data1);
        System.out.println(innerClass.data1);
        System.out.println(data2);
        System.out.println(data3);
        System.out.println(innerClass.data4);
        System.out.println(innerClass.data5);
        System.out.println(innerClass.data6);
    }
}

测试类:

public class Test2 {
    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        OuterClass.InnerClass innerClass = outerClass.new InnerClass();
        //OuterClass.InnerClass innerClass = new OuterClass().new InnerClass();
        innerClass.test();
        outerClass.test();
    }
}
//执行结果
这是实例内部类InnerClass::test()5
111
111
1
2
3
4
5
6
=====================================
这是外部类OuterClass::test()
1
111
2
3
4
5
6

注意内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件

内部类生成的字节码文件,命名格式: 外部类类名+ $ + 内部类类名.class  

【详细讲解】:内部类中不能定义被static修饰的成员。

  • 原因是,实例内部类在创建对象时,依赖外部类的对象引用。(需要外部类先实例化对象,然后通过这个对象引用调用去创建对象)而static修饰的成员在使用的时候不需要依赖于实例化对象。所以二者矛盾。
  • 解决方法:在static修饰的变量上,加一个关键字 final,把该成员变量定义为不可修改的变量,也就是常量(在编译时就确定了值)。此方法谨对内部类static修饰的成员变量有效,对static修饰的成员方法无效,所以内部类中一定不能定义被static修饰的成员方法。

4.1.2 静态内部类

被static修饰的内部成员类称为静态内部类。

实例静态内部类对象。new 外部类类名.静态内部类

【注意事项】

  1. 在静态内部类中只能访问外部类中的静态成员,不能直接访问外部类普通成员。因为普通成员依赖于对象。
  2. 非要访问,在静态内部类中,实例一个外部类对象。通过这个引用,来获得外部类的普通成员。
  3. 创建静态内部类对象时,不需要先创建外部类对象

代码演示:

class OuterClass {
    public int data1 = 1;
    public static int data2 = 2;
    private int data3 = 3;

    //静态内部类:被static修饰的成员内部类
    static class InnerClass {
        public int data4 = 4;
        public static int data5 = 5;
        private int data6 = 6;
        public void test() {
            System.out.println("这是静态内部类InnerClass::test()");
            // 在内部类中只能访问外部类的静态成员,不能直接访问外部类普通成员
            // 静态内部类对象创建 & 成员访问
            OuterClass outerClass = new OuterClass();
            System.out.println(outerClass.data1);
            System.out.println(data2);
            System.out.println(outerClass.data3);
            System.out.println(data4);
            System.out.println(data5);
            System.out.println(data6);
        }
    }
    public void test() {
        System.out.println("这是外部类InnerClass::test()");
    }
}

测试类:

public class Test2 {
    public static void main(String[] args) {
        OuterClass.InnerClass innerClass = new OuterClass.InnerClass();
        innerClass.test();
    }
}
//执行结果
这是静态内部类InnerClass::test()
1
2
3
4
5
6

静态内部类相较于实例内部类的区别在于,不需要外部类对象引用。

4.1.3 局部内部类

定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用,一般使用的非常少,此处简单了解下语法格式。

class OuterClass {
    public int data1 = 1;
    public static int data2 = 2;
    private int data3 = 3;
    public void func() {
        int data4 = 4;

        //局部内部类:定义在方法体内部
        //不能被public、static等访问修饰限定符修饰
        class InnerClass {
            int data5 = 5;
            public void test() {
                System.out.println(data1);
                System.out.println(data2);
                System.out.println(data3);
                System.out.println(data4);
                System.out.println(data5);
            }
        }
        //只能在该方法体内使用,其他位置都不能用
        InnerClass innerClass = new InnerClass();
        innerClass.test();
        System.out.println(innerClass.data5);
    }
}

public class Test2 {
    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        outerClass.func();
    }
}
//执行结果
1
2
3
4
5
5

【注意事项】

  1. 局部内部类只能在所定义的方法体内部使用
  2. 不能被public、static等修饰符修饰
  3. 几乎不会使用
  4. 编译器也有自己独立的字节码文件,命名格式:外部类名字 +  $ + 数字 + 内部类名字.class

4.1.4 匿名内部类 — 日常开发中用的最多

匿名内部类就是没有名字的内部类 。

上面这个过程相当于 有一个类,实现了IA接口,重写了test方法,然后new了一下,就变成了匿名内部类对象。这个类叫什么,不清楚,也不用了解,知道有这个类就可以了。

通过两种方式可以调用匿名内部类的方法:

1、. 内部类重写的方法

2、接收一下匿名内部类对象(已经new过了),通过对象的引用去调用。


好啦Y(^o^)Y,本节内容就到此结束了,感谢大家的阅读浏览,期望大家的一键三连哟!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值