目 录CONTENT

文章目录

从0开始学Java——面向对象下(9)

Eric
2022-01-23 / 0 评论 / 0 点赞 / 145 阅读 / 11,902 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2023-12-12,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1 抽象类

1.1 概述

  • 随着继承层次一个个新的子类的定义,类变得越来越具体,而父类则更一般,更通用。

  • 类的设计应该保证父类和子类能够共享特征。

  • 这些公共特征应该抽取到一个公共的父类中,而这些方法在父类中又无法给出具体的实现,而是应该交给子类各自去实现。

  • 在父类中声明这些方法的时候,就只有方法签名,没有方法体,我们将这些没有方法体的方法称为抽象方法

  • 在Java中,将包含抽象方法类称为抽象类

1.2 语法格式

  • 抽象类

    • 定义:使用abstract关键字修饰的类。
    • 语法:
权限修饰符 abstract class 类名 {}
权限修饰符 abstract class 类名 extends 父类 {}
  • 抽象方法

    • 定义:没有方法体的方法。
    • 语法:
权限修饰符 abstract 返回值类型 方法名(参数列表);
  • 示例:
package com.github.demo;

/**
 * 抽象类 Animal
 * 
 * @author open1024
 * @version 1.0
 * @since 2021-09-09 15:44
 */
public abstract class Animal {

    /**
     * 吃饭的方法
     */
    public abstract void eat();

}
package com.github.demo;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-09 15:46
 */
public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃老鼠");
    }
}
package com.github.demo;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-09 15:45
 */
public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃肉");
    }
}
package com.github.demo;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-09 15:48
 */
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat(); // 狗吃肉

        Cat cat = new Cat();
        cat.eat(); // 猫吃老鼠
    }
}

1.3 注意事项

  • ① 抽闲类不能创建对象,如果创建,编译将无法通过。只能创建其非抽象子类的对象。

理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  • ② 抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。。

理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类的构造方法。

  • ③ 抽象类中,不一定包含抽象方法,但是有抽象方法的类一定属于抽象类。

理解:没有包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

  • ④ 抽象类的子类,必须重写抽象父类的所有抽象方法;否则,编译无法通过。除非,该子类也是抽象类。

理解:假设不重写所有的抽象方法,则类中可能包含抽象方法,那么在创建对象后,调用抽象的方法,没有意义。

  • ⑤ 不能用abstract修饰变量、代码块、构造器。

  • ⑥ 不能用abstract修饰私有方法、静态方法、final修饰的方法和final修饰的类。

1.4 练习

1.4.1 练习1

  • 定义一个几何图形父类Graphic。所有几何图形都应该具备一个计算面积的方法。但是不同的几何图形计算面积的方式完全不同。

  • 示例:

package com.github.demo2;

/**
 * 图形
 * 
 * @author open1024
 * @version 1.0
 * @since 2021-09-09 16:56
 */
public abstract class Graphic {

    /**
     * 计算面积
     *
     * @return double
     */
    public abstract double getArea();
}
package com.github.demo2;

/**
 * 圆
 * 
 * @author open1024
 * @version 1.0
 * @since 2021-09-09 16:58
 */
public class Circle extends Graphic {

    private double radius;

    public Circle() {}

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return this.radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI * Math.pow(this.radius, 2);
    }
}
package com.github.demo2;

/**
 * 矩形
 * 
 * @author open1024
 * @version 1.0
 * @since 2021-09-09 17:00
 */
public class Rectangle extends Graphic {

    private double width;

    private double height;

    public Rectangle() {}

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double getWidth() {
        return this.width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double getHeight() {
        return this.height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    @Override
    public double getArea() {
        return this.width * this.height;
    }
}
package com.github.demo2;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-09 17:01
 */
public class Test {
    public static void main(String[] args) {
        Circle circle = new Circle(2);
        double area = circle.getArea();
        System.out.println("circle:" + area);

        Rectangle rectangle = new Rectangle(2, 2);
        area = rectangle.getArea();
        System.out.println("rectangle:" + area);
    }
}

1.4.2 练习2

  • ① 声明抽象父类:Person,包含抽象方法:
public abstract void walk();
public abstract void eat();
  • ② 声明子类Man,继承Person:

    • 重写walk():大步流星走路。
    • 重写eat():狼吞虎咽吃饭。
    • 新增方法:public void smoke()实现为吞云吐雾
  • ③ 声明子类Woman,继承Person:

    • 重写walk():婀娜多姿走路。
    • 重写eat():细嚼慢咽吃饭。
    • 新增方法:public void buy()实现为买买买...
  • ④ 在测试类中创建子类对象,调用方法测试。

  • 示例:

package com.github.demo3;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-09 17:13
 */
public abstract class Person {
    /**
     * 走路
     */
    public abstract void walk();

    /**
     * 吃饭
     */
    public abstract void eat();
}
package com.github.demo3;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-09 17:14
 */
public class Man extends Person {
    @Override
    public void walk() {
        System.out.println("大步流星走路");
    }

    @Override
    public void eat() {
        System.out.println("狼吞虎咽吃饭");
    }

    /**
     * 抽烟
     */
    public void smoke() {
        System.out.println("吞云吐雾");
    }
}
package com.github.demo3;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-09 17:15
 */
public class Woman extends Person {
    @Override
    public void walk() {
        System.out.println("婀娜多姿走路");
    }

    @Override
    public void eat() {
        System.out.println("细嚼慢咽吃饭");
    }

    /**
     * 购物
     */
    public void buy() {
        System.out.println("买买买...");
    }
}
package com.github.demo3;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-09 17:16
 */
public class Test {
    public static void main(String[] args) {
        Man man = new Man();
        man.eat();
        man.walk();
        man.smoke();

        System.out.println("----------------------");

        Woman woman = new Woman();
        woman.buy();
        woman.eat();
        woman.walk();
    }
}

2 接口

2.1 概述

  • 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。

  • 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打 印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB接口连接。

  • 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的is-a关系,而接口实现则是 "能不能"的has-a关系。

    • 例如:我们能不能用USB进行连接,或是否具备USB通信功能,就看我们是否遵循USB接口规范。

img

    • 例如:Java程序是否能够连接使用某种数据库产品,那么要看该数据库产品有没有实现Java设计的JDBC规范。

img

2.2 语法

2.2.1 概述

  • 接口的定义,和定义类的语法类似,但是使用的是interface关键字。它也会编译成.class文件,但是一定要明确的是它不是累,而是另外一种引用数据类型。

引用数据类型:数组、类、接口。

2.2.2 接口的语法格式

  • 语法:
权限修饰符 interface 接口名 {
    // 接口的成员列表:
    // ① 静态常量
    // ② 抽象方法
    // ③ 默认方法
    // ④ 静态方法
    // ⑤ 私有方法
}
  • 示例:
package com.github.demo4;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 08:12
 */
public interface Fly {

    void fly();
}
package com.github.demo4;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 08:13
 */
public class Bird implements Fly {
    @Override
    public void fly() {
        System.out.println("小鸟在飞");
    }
}
package com.github.demo4;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 08:13
 */
public class SuperMan implements Fly {
    @Override
    public void fly() {
        System.out.println("超人在飞");
    }
}
package com.github.demo4;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 08:09
 */
public class Test {
    public static void main(String[] args) {
        Fly bird = new Bird();
        bird.fly();

        System.out.println("-----------------");

        Fly superman = new SuperMan();
        superman.fly();
    }
}

2.2.3 接口中的成员说明

  • 接口定义的是多个类共同的公共行为规范,这些行为规范是和外部交流的通道,这就意味着接口里通常定义一组公共方法。

  • 在JDK8之前,接口中只允许出现:

    • ① 公共的静态常量:其中public static final可以省略。
    • ② 公共的抽象方法:其中public abstract可以省略。

理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现。

  • 在JDK8时,接口中允许声明默认方法和静态方法:

    • ① 公共的默认方法:其中public可以省略,但是建议保留,但是default不能省略。
    • ② 公共的静态方法:其中public可以省略,但是建议保留,但是static不能省略。
  • 在JDK9的时候,接口允许私有方法。

目前而言,接口中除了静态常量、抽象方法、默认方法、静态方法、私有方法外,接口中不能有其他成员(构造器、初始化块),接口中没有成员变量需要初始化。

2.2.4 接口中成员变量的思考

  • 问:为什么接口中只能声明公共的静态常量?

  • 答:因为接口是标准规范,那么在规范中需要声明一些底线边界值,当实现者在实现这些规范的时候,不能随意的去修改和触碰这些底线边界值,否则就有危险。比如:USB1.0规范中规定最大传输速率是1.5Mbps,最大输出电流是5V/500mA。 USB3.0规范中规定最大传输速率是5Gbps(500MB/s),最大输出电流是5V/900mA。

  • 问:为什么JDB8之后,允许接口中定义静态方法和默认方法?

  • 答:

    • 静态方法:在之前的标准类库设计中,有很多Collection/Collections、Path/Paths这样的成对的接口和类,后面的类中都是静态方法,而这样静态方法都是为了前面的接口服务的,那么在设计这样一对API的时候,还不如将静态方法直接定义到接口中,这样使用和维护更为方便。
    • 默认方法:
      • ① 如果要在老版本的接口中提供方法,如果添加抽象方法,就会涉及到原来使用这些接口的实现类都需要重写抽象方法,为了保持和旧版本的代码的兼容性,只能允许接口中定义默认方法,比如:JDK8中对Collection、List、Comparator等接口都提供了丰富的默认方法。
      • ② 当我们接口中的某个抽象方法,在很多类中的实现代码都是一样的,那么此时就可以将这个抽象方法设计为默认方法更为合适,那么实现类可以选择重写,也可以选择不重写。
  • 问:为什么JDK9要允许接口中定义私有方法?

  • 答:因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方法由相同的代码抽取,而这些相同的代码抽取出来的代码又希望只在接口内部使用,所以增加了私有方法。

2.3 实现接口

2.3.1 实现接口的语法

  • 语法:
权限访问修饰符 class implements 接口{
    // 重写接口中的抽象方法【必须】,如果实现类是抽象类,可以不重写
    // 重写接口中的默认方法【可选】
}
权限访问修饰符 class extends 父类 implements 接口{
    // 重写接口中的抽象方法【必须】,如果实现类是抽象类,可以不重写
    // 重写接口中的默认方法【可选】
}

注意:

  • ① 如果接口的实现类是非抽象类,那么必须重写接口中所有的抽象方法。

  • ② 默认方法可以选择重写,也可以选择不重写。重写的时候,在子类中,default单词就不需要写了,就如果重写abstract抽象方法一样。

  • ③ 不能重写静态方法。

  • 示例:

package com.github.demo4;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 08:12
 */
public interface Fly {

    void fly();

    default void addOil() {
        System.out.println("加油");
    }
}
package com.github.demo4;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 08:13
 */
public class Bird implements Fly {
    @Override
    public void fly() {
        System.out.println("小鸟在飞");
    }
}
package com.github.demo4;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 08:54
 */
public class Plane implements Fly {
    @Override
    public void fly() {
        System.out.println("飞机在飞");
    }

    @Override
    public void addOil() {
        System.out.println("飞机加航空煤油");
    }
}
package com.github.demo4;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 08:13
 */
public class SuperMan implements Fly {
    @Override
    public void fly() {
        System.out.println("超人在飞");
    }
}
package com.github.demo4;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 08:09
 */
public class Test {
    public static void main(String[] args) {
        Bird bird = new Bird();
        bird.fly();

        System.out.println("-----------------");

        SuperMan superman = new SuperMan();
        superman.fly();

        System.out.println("-----------------");

        Plane plane = new Plane();
        plane.fly();
        plane.addOil();
    }
}

2.3.2 如果调用对应的方法?

  • ① 对于接口中的静态方法,只能通过接口名.方法名()调用。

  • ② 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用。

2.3.3 练习

  • 示例:
package com.github.demo5;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 09:39
 */
public interface LiveAble {
    /**
     * 喝水
     */
    static void drink() {
        System.out.println("喝水");
    }

    /**
     * 呼吸
     */
    void breathe();

    /**
     * 吃饭
     */
    void eat();

    /**
     * 睡觉
     */
    default void sleep() {
        System.out.println("静止不动");
    }
}
package com.github.demo5;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 09:40
 */
public class Animal implements LiveAble {
    @Override
    public void breathe() {
        System.out.println("吸入氧气呼出二氧化碳");
    }

    @Override
    public void eat() {
        System.out.println("吃东西");
    }

    @Override
    public void sleep() {
        System.out.println("闭上眼睛睡觉");
    }
}
package com.github.demo5;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 09:41
 */
public class Plant implements LiveAble {
    @Override
    public void breathe() {
        System.out.println("吸入二氧化碳呼出氧气");
    }

    @Override
    public void eat() {
        System.out.println("吸收营养");
    }
}
package com.github.demo5;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 09:42
 */
public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.breathe();
        animal.eat();
        animal.sleep();

        System.out.println("-------------------");

        Plant plant = new Plant();
        plant.breathe();
        plant.sleep();
        plant.eat();

        System.out.println("-------------------");

        LiveAble.drink();
    }
}

2.4 接口的多实现

  • 在之前学过,在继承体系中,一个类只能继承一个接口。但是,对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。与此同时,一个类能继承一个父类,同时实现多个接口。

  • 语法:

权限修饰符 class 实现类 implements 接口1,接口2,接口3,...{
    // 重写接口中的抽象方法【必须】,如果实现类是抽象类,可以不重写
    // 重写接口中的默认方法【可选】
}
权限访问修饰符 class extends 父类 implements 接口1,接口2,接口3,...{{
    // 重写接口中的抽象方法【必须】,如果实现类是抽象类,可以不重写
    // 重写接口中的默认方法【可选】
}

注意:接口中多个抽象方法的时候,实现类必须重写所有的抽象方法。如果抽象方法有重名的,只需要重写一次。

  • 示例:
package com.github.demo6;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 09:48
 */
public interface A {
    void showA();

    void show();
}
package com.github.demo6;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 09:48
 */
public interface B {
    void showB();

    void show();
}
package com.github.demo6;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 09:49
 */
public class C implements A, B {
    @Override
    public void showA() {
        System.out.println("showA");
    }

    @Override
    public void showB() {
        System.out.println("showB");
    }

    @Override
    public void show() {
        System.out.println("show");
    }
}
package com.github.demo6;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 09:49
 */
public class Test {
    public static void main(String[] args) {
        C c = new C();
        c.show();
        c.showA();
        c.showB();
    }
}

2.5 默认方法冲突问题

2.5.1 亲爹优先原则

  • 当一个类,既继承了一个父类,又实现了若干个接口的时候,父类中的成员方法和接口中的默认方法重名,子类就近选择执行父类的成员方法。

  • 示例:

package com.github.demo7;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:39
 */
public interface A {

    default void method() {
        System.out.println("接口A中的method方法");
    }
}
package com.github.demo7;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:39
 */
public class B {
    public void method() {
        System.out.println("B类中的method方法");
    }
}
package com.github.demo7;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:40
 */
public class C extends B implements A {
    // 不重写method方法
}
package com.github.demo7;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:40
 */
public class D extends B implements A {

    @Override
    public void method() {
        System.out.println("D中的method方法");
    }
}
package com.github.demo7;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:41
 */
public class Test {
    public static void main(String[] args) {
        C c = new C();
        c.method(); // B类中的method方法

        System.out.println("------------");

        D d = new D();
        d.method(); // D中的method方法
    }
}

2.5.2 必须做出选择

  • 当一个类同时实现多个接口,而多个接口中包含方法签名相同的默认方法时,必须进行重写,否则编译报错。在重写的方法中,可以选中使用接口名.super.方法名的方法选择保留哪个接口中的默认方法,也可以选择完全自己重写。

  • 示例:

package com.github.demo8;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:47
 */
public interface A {

    default void method() {
        System.out.println("今天晚上陪我吃饭");
    }
}
package com.github.demo8;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:48
 */
public interface B {

    default void method() {
        System.out.println("今天晚上陪我逛街");
    }
}
package com.github.demo8;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:48
 */
public class C implements A, B {
    @Override
    public void method() {
        // 选择保留其中一个,通过“接口名.super.方法名"的方法选择保留哪个接口的默认方法。
        A.super.method();
    }
}
package com.github.demo8;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:49
 */
public class D implements A, B {
    @Override
    public void method() {
        System.out.println("滚,写代码,它不香吗?");
    }
}
package com.github.demo8;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:49
 */
public class Test {
    public static void main(String[] args) {
        C c = new C();
        c.method(); // 今天晚上陪我吃饭

        System.out.println("----------");

        D d = new D();
        d.method(); // 滚,写代码,它不香吗?
    }
}

2.6 接口的多继承

  • 一个接口能继承另一个接口或者多个接口,接口的继承也使用extends关键字,子接口继承父接口的方法。

温馨提示:

  • ① 子接口重写默认方法的时候,default关键字可以保留。

  • ② 子类重写默认方法的时候,default关键字不可以保留。

  • 示例:

package com.github.demo9;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:54
 */
public interface A {
    void a();

    default void methodA() {
        System.out.println("A接口的default修饰的methodA方法");
    }
}
package com.github.demo9;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:54
 */
public interface B {
    void b();

    default void methodB() {
        System.out.println("A接口的default修饰的methodB方法");
    }
}
package com.github.demo9;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:55
 */
public interface C extends A, B {
    @Override
    default void methodB() {
        System.out.println("C接口的default修饰的默认方法");
    }
}
package com.github.demo9;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:56
 */
public class D implements C {
    @Override
    public void a() {
        System.out.println("D:a");
    }

    @Override
    public void b() {
        System.out.println("D:b");
    }
}
package com.github.demo9;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:57
 */
public class E implements A, B, C {
    @Override
    public void a() {
        System.out.println("E: a");
    }

    @Override
    public void b() {
        System.out.println("E: b");
    }
}
package com.github.demo9;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 10:58
 */
public class Test {
    public static void main(String[] args) {
        D d = new D();
        d.a(); // D:a
        d.b(); // D:b
        d.methodA(); // A接口的default修饰的methodA方法
        d.methodB(); // C接口的default修饰的默认方法

        System.out.println("--------------");

        E e = new E();
        e.a(); // E: a
        e.b(); // E: b
        e.methodA(); // A接口的default修饰的methodA方法
        e.methodB(); // C接口的default修饰的默认方法
    }
}

2.7 接口和实现类对象的多态引用

  • 实现类实现接口,类似于子类继承父类,因此,接口类型的变量和实现类对象之间,也构成多态引用。通过接口类型的变量调用方法,最终执行的是实现类对象重写的方法。

  • 示例:

package com.github.demo10;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 11:09
 */
public interface Fly {
    void fly();
}
package com.github.demo10;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 11:09
 */
public class Bird implements Fly {
    @Override
    public void fly() {
        System.out.println("小鸟飞");
    }
}
package com.github.demo10;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 11:10
 */
public class Plane implements Fly {
    @Override
    public void fly() {
        System.out.println("飞机在飞");
    }
}
package com.github.demo10;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 11:10
 */
public class Test {
    public static void main(String[] args) {
        Fly bird = new Bird();
        bird.fly();// 小鸟飞

        System.out.println("----------------");

        Fly plane = new Plane();
        plane.fly();// 飞机在飞

    }
}

2.8 经典接口的介绍

2.8.1 java.lang.Comparable

  • 基本数据类型的数据(除了boolean类型以外)比较大小的话,直接使用比较运算符即可;但是,引用数据类型是不能直接使用比较运算符来比较大小的,那么,怎么办呢?

  • Java给所有的引用数据类型的大小比较,提供了一个标准接口,就是java.lang.Comparable接口。

package java.lang;
import java.util.*;

public interface Comparable<T> {
    
    public int compareTo(T o);
}
  • 那么,如果我们想是的我们某个类的对象可以比较大小,怎么做?

    • ① 那个类的对象要比较大小,那个类就需要实现java.lang.Comparable接口,并重写方法。
      • 方法体就是如何比较当前对象和指定的另一个对象大小的规则。
    • ② 对象比较大小的时候,通过对象调用compareTo方法,根据方法的返回值决定谁大谁小。
      • this对象(调用compareTo方法的对象)大于指定对象(传入compareTo()的参数对象),返回正整数。
      • this对象(调用compareTo方法的对象)小于指定对象(传入compareTo()的参数对象),返回负整数。
      • this对象(调用compareTo方法的对象)等于指定对象(传入compareTo()的参数对象),返回零。

温馨提示:这种方式称为自然排序或内部比较器排序。

  • 自然排序:Comparable接口强行对实现它的每个类的对象进行整体排序,类的 compareTo 方法被称为它的自然比较方法。

  • 内部比较器排序:在比较对象所在类的内部完成比较规则的制定。

  • 示例:

package com.github.demo11;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 13:14
 */
public class Student implements Comparable<Student> {

    private String name;

    private int age;

    private double score;

    public Student() {}

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

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

    @Override
    public int compareTo(Student o) {
        return Integer.compare(this.age, o.age);
    }

    @Override
    public String toString() {
        return "Student{" + "name='" + this.name + '\'' + ", age=" + this.age + ", score=" + this.score + '}';
    }
}
package com.github.demo11;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 13:30
 */
public class Test {
    public static void main(String[] args) {
        Student student = new Student("张三", 35, 99);
        Student student2 = new Student("李四", 25, 88);

        /*
        * 按照年龄进行比较
        * 
        * 如果是0,表示两数相等。
        * 如果是正数,那么第一个数大于第二个数。
        * 如果是负数,那么第一个数小于第二个数。
        */
        int sort = student.compareTo(student2);
        if (sort > 0) {
            System.out.println("student比student2大");
        } else if (sort == 0) {
            System.out.println("student和student2一样大");
        } else {
            System.out.println("student比student2小");
        }
    }
}
  • 示例:冒泡排序
package com.github.demo12;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 14:38
 */
public class Person implements Comparable<Person> {
    private String name;

    private int age;

    public Person() {}

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

    @Override
    public String toString() {
        return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + '}';
    }

    @Override
    public int compareTo(Person o) {
        return Integer.compare(this.age, o.age);
    }
}
package com.github.demo12;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 14:40
 */
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 25);
        Person p2 = new Person("李四", 74);
        Person p3 = new Person("王五", 10);
        Person p4 = new Person("赵六", 34);
        Person p5 = new Person("天气", 59);

        Person[] persons = new Person[] {p1, p2, p3, p4, p5};

        System.out.println("---------------排序前:---------------");

        printArray(persons);

        System.out.println("---------------排序后:---------------");

        for (int i = 0; i < persons.length -1; i++) {
            for (int j = 0; j < persons.length - i - 1; j++) {
                if (persons[j].compareTo(persons[j + 1]) > 0) {
                    Person temp = persons[j];
                    persons[j] = persons[j + 1];
                    persons[j + 1] = temp;
                }
            }
        }

        printArray(persons);

    }

    private static void printArray(Person[] persons) {
        for (Person person : persons) {
            System.out.println(person);
        }
    }

}
  • 示例:自定义数组工具排序类
package com.github.demo13;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 15:02
 */
public class Arrays {

    public static void sort(Object[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - i - 1; j++) {
                // 如果arr数组中的元素实现了Comparable接口,就进行排序
                // 否则,原样输出
                if (arr[j] instanceof Comparable) {
                    Comparable comparable = (Comparable)arr[j];
                    if (comparable.compareTo(arr[j + 1]) > 0) {
                        Object temp = arr[j];
                        arr[j] = arr[j + 1];
                        arr[j + 1] = temp;
                    }
                } else {
                    break;
                }
            }
        }
    }
}
package com.github.demo13;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 14:38
 */
public class Person implements Comparable<Person> {
    private String name;

    private int age;

    public Person() {}

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

    @Override
    public String toString() {
        return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + '}';
    }

    @Override
    public int compareTo(Person o) {
        return Integer.compare(this.age, o.age);
    }
}
package com.github.demo13;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 14:40
 */
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 25);
        Person p2 = new Person("李四", 74);
        Person p3 = new Person("王五", 10);
        Person p4 = new Person("赵六", 34);
        Person p5 = new Person("天气", 59);

        Person[] persons = new Person[] {p1, p2, p3, p4, p5};

        System.out.println("---------------排序前:---------------");

        printArray(persons);

        System.out.println("---------------排序后:---------------");

        Arrays.sort(persons);

        printArray(persons);

    }

    private static void printArray(Person[] persons) {
        for (Person person : persons) {
            System.out.println(person);
        }
    }

}

2.8.2 java.util.Comparator

  • 思考如下场景:

    • ① 如果一个类,没有实现java.lang.Comparable接口,而这个类不方便修改(比如:我们拿到的是第三方类,只有.class文件,没有源文件),那么这样的对象要进行比较大小该怎么办?
    • ② 如果一个类,实现了java.lang.Comparable接口,也指定了两个对象比较大小的规则,但是此时我不想按照它预定义的规则进行对象大小的比较,但是我又不能去修改这个类,因为会影响到其他地方的使用,那么这样的对象要进行比较大小该怎么办?
  • Java在设计类库之初,已经考虑到这种情况了,所以又增加了一个java.util.Comparator接口。

package java.util;

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}
  • 那么,我们想要比较某个类的两个对象的大小,该怎么办?

    • ① 编写一个类,称为比较器类型,实现java.util.Comparator接口,并重写方法。
      • 方法体就是如何比较当前对象和指定的另一个对象大小的规则。
    • ② 比较大小的时候,通过比较器类型的对象调用compare()方法,将要比较大小的两个对象作为compare方法的实参传入,根据方法的返回值决定谁大谁小。
      • o1对象大于o2,返回正整数。
      • o1对象小于o2,返回负整数。
      • o1对象等于o2,返回零。

温馨提示:这种方式称为自定义排序或外部比较器排序。

  • 示例:
package com.github.demo14;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 15:43
 */
public class Person {
    private String name;

    private int age;

    private double salary;

    public Person() {}

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

    @Override
    public String toString() {
        return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + ", salary=" + this.salary + '}';
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return this.salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}
package com.github.demo14;

import java.util.Comparator;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 15:57
 */
public class PersonAgeCompare implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return Integer.compare(o1.getAge(), o2.getAge());
    }
}
package com.github.demo14;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 15:45
 */
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 56, 5000);
        Person p2 = new Person("李四", 20, 9000);

        PersonAgeCompare compare = new PersonAgeCompare();
        int sort = compare.compare(p1, p2);
        if (sort > 0) {
            System.out.println(p1.getName() + "的年龄比" + p2.getName() + "大");
        } else if (sort < 0) {
            System.out.println(p1.getName() + "的年龄比" + p2.getName() + "小");
        } else {
            System.out.println(p1.getName() + "的年龄和" + p2.getName() + "相等");
        }
    }
}
  • 示例:冒泡排序
package com.github.demo15;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 15:43
 */
public class Person {
    private String name;

    private int age;

    private double salary;

    public Person() {}

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

    @Override
    public String toString() {
        return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + ", salary=" + this.salary + '}';
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return this.salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}
package com.github.demo15;

import java.util.Comparator;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 15:57
 */
public class PersonAgeCompare implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return Integer.compare(o1.getAge(), o2.getAge());
    }
}
package com.github.demo15;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 15:45
 */
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 78, 100);
        Person p2 = new Person("李四", 16, 9000);
        Person p3 = new Person("王五", 39, 500);
        Person p4 = new Person("赵六", 20, 3000);

        Person[] persons = new Person[] {p1, p2, p3, p4};

        System.out.println("----------------排序前----------------");

        printArray(persons);

        System.out.println("----------------排序后----------------");

        PersonAgeCompare compare = new PersonAgeCompare();

        for (int i = 0; i < persons.length - 1; i++) {
            for (int j = 0; j < persons.length - i - 1; j++) {
                if (compare.compare(persons[j], persons[j + 1]) > 0) {
                    Person temp = persons[j];
                    persons[j] = persons[j + 1];
                    persons[j + 1] = temp;
                }
            }
        }

        printArray(persons);

    }

    private static void printArray(Person[] persons) {
        for (Person person : persons) {
            System.out.println(person);
        }
    }
}
  • 示例:自定义数组工具排序类
package com.github.demo16;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 15:43
 */
public class Person {
    private String name;

    private int age;

    private double salary;

    public Person() {}

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

    @Override
    public String toString() {
        return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + ", salary=" + this.salary + '}';
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return this.salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}
package com.github.demo16;

import java.util.Comparator;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 16:08
 */
public class Arrays {

    public static void sort(Object[] arr, Comparator comparator) {
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - i - 1; j++) {
                if (comparator.compare(arr[j], arr[j + 1]) > 0) {
                    Object temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }

}
package com.github.demo16;

import com.github.demo15.Person;
import com.github.demo15.PersonAgeCompare;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 15:45
 */
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 78, 100);
        Person p2 = new Person("李四", 16, 9000);
        Person p3 = new Person("王五", 39, 500);
        Person p4 = new Person("赵六", 20, 3000);

        Person[] persons = new Person[] {p1, p2, p3, p4};

        System.out.println("----------------排序前----------------");

        printArray(persons);

        System.out.println("----------------排序后----------------");

        PersonAgeCompare compare = new PersonAgeCompare();

        Arrays.sort(persons, compare);

        printArray(persons);

    }

    private static void printArray(com.github.demo15.Person[] persons) {
        for (Person person : persons) {
            System.out.println(person);
        }
    }
}

2.8.3 java.lang.Cloneable

  • 在Object类中,有如下的方法:
protected native Object clone() throws CloneNotSupportedException;
  • 所有的类都可以重写这个方法,它是获取一个对象的克隆体对象,就是造一个和当前对象属性一模一样的对象,地址是不同的。

  • 要求,重写这个方法的类需要实现java.lang.Cloneable接口,否则会报CloneNotSupportedException的异常。

  • 示例:

package com.github.demo17;

import java.util.Objects;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 16:33
 */
public class Teacher implements Cloneable {
    private int id;

    private String name;

    public Teacher() {}

    public Teacher(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || this.getClass() != o.getClass()) {
            return false;
        }
        Teacher teacher = (Teacher)o;
        return this.id == teacher.id && Objects.equals(this.name, teacher.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.id, this.name);
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Teacher{" + "id=" + this.id + ", name='" + this.name + '\'' + '}';
    }
}
package com.github.demo17;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-10 16:33
 */
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Teacher teacher = new Teacher(1, "张三");
        Object clone = teacher.clone();

        System.out.println(teacher == clone); // false
        System.out.println(teacher.equals(clone)); // true
    }
}

3 包装类

3.1 概述

  • Java提供了两种数据类型:基本数据类型和引用数据类型。

  • 使用基本数据类型在于效率,然而要使用只针对对象设计的API或新特性(如:泛型),那么基本数据类型的数据就需要用包装类来包装了。

  • Java针对基本数据类型定义了相应的引用类型,这些对应的引用类型称为包装类。

  • 有了类的特点,就可以调用类中的方法,Java才是真正的面向对象。

序号基本数据类型包装类(java.lang包)
1byteByte
2shortShort
3intInteger
4longLong
5floatFloat
6doubleDouble
7charCharacter
8booleanBoolean
9voidVoid

温馨提示:Byte、Short、Integer、Long、Float、Double的父类是Number。

  • 示例:
package com.wrapper.demo1;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-13 11:05
 */
public class Test {
    public static void main(String[] args) {
        System.out.println("int数据类型的最小值 = " + Integer.MIN_VALUE); // -2147483648
        System.out.println("int数据类型的足最大值 = " + Integer.MAX_VALUE); // 2147483647
        // 将一个十进制的数转换为十六进制
        String s = Integer.toHexString(11);
        System.out.println("s = " + s); // b
        // 将一个十进制的数转换为八进制
        String s1 = Integer.toOctalString(11);
        System.out.println("s1 = " + s1); // 13
        // 将一个十进制的数转换为二进制
        String s2 = Integer.toBinaryString(11);
        System.out.println("s2 = " + s2); // 1011
    }
}

3.2 装箱和拆箱

  • 装箱:

    • 定义:将基本数据类型转换为包装类对象。
    • 目的:为了使用专门为对象设计的API和特性。
  • 拆箱:

    • 定义:将包装类对象转换为基本数据类型。
    • 目的:一般是因为需要运算,Java中的大多数的运算符都是为基本数据类型而设计的,比如:比较、算术等。
  • 在JDK 5之后,可以自动进行装箱和拆箱,但是需要注意的是:只能和自己对应的类型之间才能实现自动装箱和拆箱

  • 示例:装箱

package com.wrapper.demo2;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-13 11:12
 */
public class Test {
    public static void main(String[] args) {
        int num = 10;
        // 通过构造函数将基本数据类型转换为包装类对象
        Integer integer = new Integer(num);
        System.out.println("integer = " + integer);
        // 通过Integer.valueOf()方法将基本数据类型转换为包装类对象
        Integer integer1 = Integer.valueOf(num);
        System.out.println("integer1 = " + integer1);
    }
}
  • 示例:拆箱
package com.wrapper.demo2;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-13 11:14
 */
public class Test2 {
    public static void main(String[] args) {
        Integer integer = new Integer(10);
        // 通过Integer对象的intValue方法,将包装类对象转换为基本数据类型
        int i = integer.intValue();
        System.out.println("i = " + i);
    }
}
  • 示例:自动装箱
package com.wrapper.demo2;

/**
 * 自动装箱:直接将基本类型的数据赋值给包装类类型
 * 
 * @author open1024
 * @version 1.0
 * @since 2021-09-13 11:21
 */
public class Test3 {
    public static void main(String[] args) {
        // 自动装箱
        Integer num = 10;
        System.out.println("num = " + num);
    }
}
  • 示例:自动拆箱
package com.wrapper.demo2;

/**
 * 自动拆箱:将包装类对象直接赋值给基本数据类型
 * 
 * @author open1024
 * @version 1.0
 * @since 2021-09-13 11:22
 */
public class Test4 {
    public static void main(String[] args) {
        Integer num = 10;
        int i = num;
        System.out.println("i = " + i);
    }
}

3.3 基本数据类型、包装类和String间的相互转换

  • 基本数据类型转换为字符串:

    • ① 使用+拼接""
String str = 5 + "";
    • ② 使用String的重载方法valueOf()静态方法:
String str = String.valueOf(5);
  • 字符串转换为基本数据类型:

    • ① 通过包装类(除了Character类)的构造器:
int i = new Integer("12");
    • ② 通过包装类(除了Character类)的parsetXxx(String s)的静态方法:
int i = Integer.parsetInt("12");
    • ③ 通过包装类除了Character类)的valueOf(String s)的静态方法:
int i = Integer.valueOf("12");

注意:如果字符串参数的内容无法正确的转换为对应的基本数据类型,则会抛出java.lang.NumberFormatException异常。

  • 示例:
package com.wrapper.demo3;

/**
 * 字符串转换为基本数据类型
 * 
 * @author open1024
 * @version 1.0
 * @since 2021-09-13 13:21
 */
public class Test {
    public static void main(String[] args) {
        String str = "12";

        int num = Integer.parseInt(str);

        System.out.println("num = " + num);

        num = new Integer(str);

        System.out.println("num = " + num);

        num = Integer.valueOf(str);
        
        System.out.println("num = " + num);
    }
}

3.4 包装类对象的缓存问题

包装类缓存对象
Byte-128~127
Short-128~127
Integer-128~127
Long-128~127
Float没有
Double没有
Character0~127
Booleantrue和false
  • 示例:
package com.wrapper.demo4;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-13 13:56
 */
public class Test {
    public static void main(String[] args) {

        Integer i1 = 100; // Integer.valueOf(100)
        Integer i2 = 100;
        System.out.println(i1 == i2); // true

        i1 = new Integer(100);
        i2 = new Integer(100);
        System.out.println(i1 == i2); // false

        i1 = 400;
        i2 = 400;
        System.out.println(i1 == i2); // false

        Double d1 = 1.0;
        Double d2 = 1.0;
        System.out.println(d1 == d2);// false 比较地址,没有缓存对象,每一个都是新new的
    }
}

4 内部类

4.1 概述

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

  • 在Java中,允许一个类A的定义位于另一个类B的内部,类A就被称为内部类,而类B就被称为外部类。

  • 因为内部类在外部类的里面,因此可以直接访问外部类的私有成员。

4.2 内部类的分类

  • 根据内部类声明的位置不同,我们可以将内部类分为如下两类:

    • ① 成员内部类。
      • 静态成员内部类。
      • 非静态成员内部类。
    • ② 局部内部类。
      • 有名字的局部内部类。
      • 匿名内部类。
  • 成员内部类作为类的成员角色:

    • 外部类只能通过public和default(缺省)修饰,而内部类还可以声明为private和protected。
    • 可以调用外部类的结构。
    • 内部类可以声明为static的,那么此时不能再调用外部类的非static的成员。
  • 成员内部类作为类的角色:

    • 可以在内部定义属性、方法、构造器等结构。
    • 可以声明为abstract类,因此可以被其它的内部类继承。
    • 可以声明为final的。
    • 编译以后生成OuterClass$InnerClass.class类似的字节码文件。

注意:

  • ① 非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类才可以声明static成员。

  • ② 外部类访问成员内部类的成员,需要通过内部类.成员内部类对象.成员的方式。

  • ③ 成员内部类可以直接使用外部类的所有成员,包括私有数据。

  • ④ 当想要在外部类的静态成员中使用内部类的时候,可以考虑将内部类声明为static的。

4.3 静态内部类

  • 语法:
[权限修饰符2种] class 外部类名{
    [权限修饰符4种] static [final] class 内部类名 {
        ...
    }
}
  • 特点:

    • ① 和其他类一样,它只是定义在外部类中的另一个完整的类结构。
      • 可以继承自己想要继承的父类,实现自己想要实现的接口,和外部类的父类以及外部类的父接口无关。
      • 可以在静态内部类中声明属性、方法、构造器等结构,包括静态成员。
      • 可以使用abstract修饰,因此它也可以被其他类继承。
      • 可以使用final修饰,表示不能被继承。
      • 编译后有自己的独立的字节码文件,只不过在内部类前面冠以外部类名和$符号
    • ② 和外部类不同的是,它允许四种权限修饰符:public、protected、缺省、private;而外部类只允许public和缺省。
    • ③ 静态内部类可以访问外部类的资源:
      • 静态的属性。
      • 静态的方法。
    • ④ 外部类使用内部类的资源:
      • 如果是静态资源,可以直接通过内部类.资源名
      • 如果是非静态资源,那么需要通过内部类的对象.资源名
    • ⑤ 如果在内部类中有变量和外部类的成员变量相同,可以使用外部类.变量名进行区别。

其实严格的讲(在James Gosling等人编著的《The Java Language Specification》)静态内部类不是内部类,而是类似于C++的嵌套类的概念,外部类仅仅是静态内部类的一种命名空间的限定名形式而已。所以接口中的内部类通常都不叫内部类,因为接口中的内部成员都是隐式是静态的(即public static)。例如:Map.Entry。

  • 示例:
package top.open1024.inner.demo1;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-13 14:25
 */
public class Outer { // 外部类
    private static final int age = 66;
    int num = 10;

    public static void outerMethod2() {
        System.out.println("outerMethod2");
    }

    public void outerMethod1() {
        System.out.println(Inner.a);
        System.out.println(new Inner().num);
    }

    static class Inner { // 内部类
        static int a = 100;
        static int age = 100;
        int num = 100;

        public void method() {
            System.out.println(age);
            System.out.println(Outer.age);

            outerMethod2();
        }
    }
}
package top.open1024.inner.demo1;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-13 16:16
 */
public class Test {
    public static void main(String[] args) {
        Outer outer = new Outer();
        System.out.println("outer.num = " + outer.num);

        Outer.Inner inner = new Outer.Inner();
        System.out.println("inner.num = " + inner.num);
        inner.method();

    }
}

4.4 非静态成员内部类

  • 语法:
[权限修饰符2种] class 外部类名{
    [权限修饰符4种] [final] class 内部类名 {
        ...
    }
}
  • 特点:

    • ① 和其他类一样,它只是定义在外部类中的另一个完整的类结构。
      • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关。
      • 可以在非静态内部类中声明属性、方法、构造器等结构,但是不允许声明静态成员,但是可以继承父类的静态成员,而且可以声明静态常量
      • 可以使用abstract修饰,因此它也可以被其他类继承。
      • 可以使用final修饰,表示不能被继承。
      • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号
    • ② 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private。
      • 外部类只允许public或缺省的。
    • ③ 非静态内部类可以直接使用外部类的所有资源。
    • ④ 外部类使用内部的资源:
      • 首选创建非静态内部类的对象,然后才能使用。
      • 如果是内部类中的静态常量,可以直接使用。
    • ⑤ 如果在内部类中有变量和外部类的成员变量相同,可以使用外部类.this.变量名进行区别。
  • 示例:

package top.open1024.inner.demo2;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-13 16:24
 */
public class Outer {
    static int age = 20;

    int num = 30;

    public static void outerMethod2() {
        System.out.println("outerMethod2");

    }

    public void outerMethod1() {
        System.out.println("outerMethod1");

        System.out.println(Inner.a);

        Inner inner = new Inner();
        System.out.println(inner.name);
        inner.innerMethod();
    }

    class Inner {

        static final int a = 10;
        int num = 100;
        String name = "张三";

        public void innerMethod() {
            System.out.println(age);
            System.out.println(Outer.this.num);
            System.out.println(this.num);
            outerMethod2();
        }

    }
}
package top.open1024.inner.demo2;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-13 17:08
 */
public class Test {
    public static void main(String[] args) {
        Outer outer = new Outer();
        System.out.println("outer.num = " + outer.num);

        Outer.Inner inner = new Outer().new Inner();
        inner.innerMethod();
        System.out.println(inner.num);
    }
}

4.5 局部内部类(极其不重要)

  • 语法:
[权限修饰符] class 外部类 {
    [权限修饰符] 返回值类型 方法名(形参列表){
        [abstract|final] class 内部类 {
            ...
        }
    }
}
  • 特点:

    • ① 和外部类一样,它只是定义在外部类的某个方法中的另一个完整的类结构。
      • 可以继承自己想要继承的父类,实现自己想要实现的父接口,和外部类的父类和父接口无关。
      • 可以在局部内部类中声明属性、方法、构造器等结构,但是不包括静态成员,除非从父类继承而来的静态常量
      • 可以用abstract修饰,因为它可以被同一个方法在它后面的其他内部类继承。
      • 可以用final修饰,表示不能被继承。
      • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号
    • ② 局部内部类只能被缺省权限访问修饰符修饰。
    • ③ 局部内部类如同局部变量一样,有作用域。
    • ④ 局部内部类中是否能访问外部类的静态还是非静态的成员,取决于所在的方法。
    • ⑤ 局部内部类中不能存在静态属性,但是可以存在静态的常量。
    • ⑥ 在方法内创建内部类对象,然后通过内部类对象调用内部类中的成员。
    • ⑦ 局部内部类中还可以使用所在方法的局部常量,即用final声明的局部变量(JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final)。
  • 示例:

package top.open1024.inner.demo3;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-13 19:56
 */
public class Outer {

    static String name = "张三";
    int age = 30;

    public static void outerMethod() {

        class Inner2 {
            public void innerMethod() {
                System.out.println(name);
            }
        }
    }

    public void method() {
        // 局部变量
        int number = 10;
        // 局部内部类
        class Inner {

            public void innerMethod() {
                System.out.println(Outer.this.age);
                System.out.println(name);
                System.out.println(number);
            }
        }

        Inner inner = new Inner();
        inner.innerMethod();
    }

}
package top.open1024.inner.demo3;

/**
 * @author open1024
 * @version 1.0
 * @since 2021-09-13 20:23
 */
public class Test {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.method();
    }
}

4.6 匿名内部类

4.6.1 概述

0

评论区