JavaLearn # 面向对象编程_4

Posted LRcoding

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaLearn # 面向对象编程_4相关的知识,希望对你有一定的参考价值。

1. final 关键字

  • 修饰变量:使用 final 修饰后,变量的值不可改变,就成了常量
    • 修饰基本数据类型时:值只能赋值一次,后续不能再赋值 final int NUM = 5;
    • 修饰引用类型时:引用变量的值(地址) 不能变,但是对象的属性可以改变
  • 修饰方法:不能被子类重写,但可以重载
  • 修饰类:不能被继承

常用的 final 类System, Math, String

public final class Maths {
    public static final double PI = 3.1415926;  // 不加 final 的时候还可以修改

    // 如果类已经用 final 修改了,方法可以不加 final
    public static final double abs(double d) {
        if (d >= 0) {
            return d;
        } else {
            return -d;
        }
    }

    public static void main(String[] args) {
        final int NUM = 5;
        // NUM = 6;   只允许赋值一次,不允许再修改
        final Dog dog = new Dog("旺财");
        dog.name = "来福";  // 可以修改对象的属性
        // dog = new Dog("小强");  不允许改变
    }
}

2. 抽象类

问题1:Animal an = new Animal(); 没有一种动物,名称是 Animal,所以 Animal 不能被实例化

解决抽象类

问题2:子类必须重写父类的某个方法,否则报错

解决抽象方法

  • 有抽象方法的类,必须定义成抽象类
  • 抽象类不能实例化,即不能new
  • 抽象类必须有构造方法,构造方法不能使用 abstract 修饰
  • 一个抽象类可以有 0 或多个抽象方法
  • 子类必须重写父类的抽象方法
public abstract class Animal {
    private String color;

    // 抽象类,虽然不能 new,必须有构造方法
    public Animal() {

    }

    public Animal(String color) {
        this.color = color;
    }

    // 抽象类可以有普通方法
    public void shout() {
        System.out.println("----发出声音----");
    }

    // 要求子类必须重写的方法,用 abstract 定义
    abstract public void eat();
}

3. 接口

接口是规范,定义的是一组规则,体现了现实世界中 “如果你是…则必须能…” 的思想。

[访问修饰符] interface 接口名 [extends 父接口1, 父接口2.....] {
    常量定义;
    方法定义;
}
  • 访问修饰符:只能是 public 或者 默认
  • 接口名:和类名一样的命名规则
  • extends:接口可以多继承,弥补了Java单继承的不足(必须先 extends,再 implements)
  • 常量:接口中的属性只能是常量,总是用 public static final 修饰
  • 方法:接口中的方法,默认总是用 public abstract 修饰(JDK1.8之前)

示例需求 1:飞机、鸟、超人、导弹参加飞行表演

  • 思路1:定义一个父类 Fly,让它们都去继承 Fly。 ==》 不可以,继承是 is-a 的关系,显然不是
  • 思路2:定义一个接口 Flyable,让它们都去实现 Flyable接口 ==》 可以, 接口是 has-a 的关系

Flyable 接口

public interface Flyable {
    // 变量:默认用 public static final 修饰
    double PI = 3.14;

    // 接口不能 new,也没有构造方法,不会自动继承某个类

    // 成员方法,默认用 public abstract 修饰
    void fly();

    // JDK1.8之后方法可以有实现体,但必须用 static 修饰
    static void testMethod() {
        System.out.println("飞飞飞");
    }
}

Bird 类

public class Bird implements Main {
    // 必须实现接口中定义的方法(没有方法体的)
    @Override
    public void fly() {
        System.out.println("鸟飞。。。。");
    }
    
    public void shout() {
        System.out.println("-----鸟叫-------");
    }
}

SuperMan类

public class SuperMan implements Main {
    @Override
    public void fly() {
        System.out.println("超人飞飞飞。。。。");
    }
}

Test 类

public class Test {
    public static void main(String[] args) {
        Bird bird = new Bird();
        fly(bird);

        SuperMan superMan = new SuperMan();
        fly(superMan);
    }

    /**
     * 多态:形参是一个接口,实参可以是该接口的任意一个实现类的对象
     * @param main
     */
    public static void fly(Flyable flyable) {
        flyable.fly();
        // 不能调用实现类中定义的方法  flyable.shout();
    }
}

示例需求2:内部比较器 Comparable

图书类、学生类、新闻类、商品类等都是不同的类,但是都需要比较的功能。共同的父类显然不可以,可以定义一个比较接口 Comparable,在其中定义一个实现比较的方法 compareTo(Object obj)。让各个类实现该接口即可(模拟Java的Comparable接口)。

Comparable 接口

public interface Comparable {
    int compareTo(Object obj);
}

Book 类

public class Book implements Comparable{
    private String name;
    private int price;

    public Book() {
    }

    public Book(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

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

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\\'' +
                ", price=" + price +
                '}';
    }

    /**
     * 实现了接口,要实现比较的方法,从而达到比较 对象属性大小 的目的
     * @param obj
     * @return
     */
    @Override
    public int compareTo(Object obj) {
        Book book = (Book) obj;
        int res = this.price - book.getPrice();
        // 先比较年龄,如果年龄相同,再比较名称
        if (res == 0) {
            // 此处调用的是 String 的CompareTo,已经实现了Java的 Comparable 接口
            return this.name.compareTo(book.name);
        } else {
            return res;
        }
    }
}

Test测试类

public class TestBook {
    public static void main(String[] args) {
        Book book1 = new Book("白鹿原", 90);
        Book book2 = new Book("平凡的世界", 90);

        // 比较两本书的 价格、名称 的大小
        System.out.println(book1.compareTo(book2));
    }
}

4. JDK1.8的接口新特性

JDK7 及之前:

  • 接口的变量都是 public final static修饰的
  • 接口中都是 抽象(abstract) 方法,不能有 static 方法

JDK1.8 及其之后

  • 接口中可以添加非抽象方法(static),实现类不能重写,只能通过接口名调用
  • 如果实现类中定义了相同名字的静态方法,不是重写,是直接从属于实现类的
  • 接口中可以用 default修饰方法,在实现类中,可以直接使用,也可以重写(不能有default关键字),且只能通过实现类的对象名调用
  • 调用实现的接口的上级接口中的 default 方法:MyInterface.super.method2()

MyInterface类

public interface MyInterface {
    // 常量没有变化,还是用 public static final 修饰

    // 抽象方法没有变化  默认用 public abstract 修饰
    void method1();

    // 变化1:可以有非抽象方法 ==》 static 修饰【实现类不可以重写,只能通过接口名调用】
    static void method2() {
        System.out.println("JDK1.8新增的特性,实现类不可以重写,只能通过接口名调用");
    }

    // 变化2:可以有非抽象方法 ==》 default 修饰【实现类可以选择是否重写(重写时要去掉default),只能通过实现类的对象名调用】
    default void method3() {
        System.out.println("JDK1.8新增的,实现类可以选择是否重写(重写时要去掉default),只能通过实现类的对象名调用");
    }
}

MyClass类

public class MyClass implements MyInterface{
    // 实现接口,要实现它的抽象方法
    @Override
    public void method1() {

    }

    // 不是重写了接口的方法,是实现类自己定义了一个新的方法
    static void method2() {
        System.out.println("这是实现类自己定义的方法");
    }

    @Override
    public void method3() {
        System.out.println("实现类可以选择是否重写接口中 default 修饰的方法,重写时要【去掉 default】");
    }

    public static void main(String[] args) {
        // 接口中 static 修饰的方法,只能通过【接口名】调用
        MyInterface.method2();

        // 实现类中,定义了和接口中相同的static修饰的方法,不是重写,是实现类自己的
        MyClass.method2();

        // 接口中 default 修饰的方法,只能通过【实现类的对象名】调用
        MyClass mc = new MyClass();
        mc.method3();
    }
}

提供非抽象方法的目的

  • 为了解决实现类中代码重复的问题(如果一个接口中有10个抽象方法,20个类实现该接口,那么这20个类中都需要重写10个方法,也就是200次,但是用 default 修饰后,实现类可以选择是否重写)
  • 如果一个接口新增了一个功能,不必对那些实现类重新设计。

5. 内部类

内部类是一类特殊的类,是定义在一个类的内部的类。实际开发中,是为了方便的使用外部类的相关属性和方法

在Java中,内部类分为非静态成员内部类静态成员内部类局部内部类匿名内部类

// 外部类
public class OuterClass {
    // 成员变量
    private String color = "red";
    private int num = 10;

    // 构造方法
    public OuterClass() {
    }
    public OuterClass(int num) {
        this.num = num;
    }

    // 静态代码块
    static {

    }

    // 成员方法
    public void outerMethod() {
        // 局部变量
        int num = 20;

        // ==================== 局部内部类 ========================
        class LocalClass {

        }
    }

    // ==================== (非静态)成员内部类 ========================
    class InnerClass {

    }

    // ==================== 静态成员内部类 ========================
    static class StaticInnerClass {

    }
}

5.1 非静态成员内部类

  • 非静态成员内部类可以直接访问外部类的非静态成员
  • 外部类不能直接访问非静态成员内部类的成员,需要先创建非静态成员内部类的对象,再通过对象名访问
  • 非静态成员内部类访问外部类同名的成员变量:OuterClass.this.num
  • 创建非静态成员内部类对象:必须先创建外部类对象,再通过外部类对象创建非静态成员内部类的对象
  • 内部类是一个编译时的概念,一旦编译成功,就会变成两个完全不同的类
  • 非静态成员内部类中,不能有静态方法、变量、代码块
  • 外部类的静态方法、静态代码块,不能访问非静态成员内部类

外部类及非静态成员内部类

// 外部类
public class OuterClass {
    // 成员变量
    private String color = "red";
    private int num = 10;
    // 构造方法
    // 静态代码块
    // 成员方法

    // ======================== 非静态成员内部类(可以使用权限修饰符) =========================
    class InnerClass {
        // 成员变量
        private int num = 30;

        // 构造方法
        public InnerClass() {
        }

        // 成员方法
        public void innerMethod() {
            // 【1. 非静态成员内部类可以直接访问外部类的成员】
            System.out.println(color);
            outerMethod();

            int num = 20;
            // 【3. 如果内部类,外部类有同名的变量,如何访问】
            System.out.println(num);                  // 局部变量的
            System.out.println(this.num);             // 内部类的
            System.out.println(OuterClass.this.num);  // 外部类的!!!
        }
    }

    public void outerMethod2() {
        // 【2. 外部类不能直接访问非静态成员内部类的成员,需要内部类的对象】
        InnerClass in = new InnerClass();
        in.innerMethod();
    }
}

Test类

public class Test {
    public static void main(String[] args) {
        // 创建外部类对象
        OuterClass out = new OuterClass();
        out.outerMethod2();

        // 创建 非静态成员内部类对象 【4. 必须使用外部类的对象创建,非静态成员内部类属于外部类的对象】
        OuterClass.InnerClass inner = out.new InnerClass();    // = new OutClass().new InnerClass();
    }
}

5.2 静态成员内部类

  • 静态内部类,只能访问外部类的静态成员
  • 静态内部类访问外部类的同名的变量:OutClass.num
  • 静态内部类是属于外部类的。创建静态内部类的对象,使用外部类创建 new OutClass.StaticInnerClass()
  • 外部类可以通过静态内部类的类名直接访问内部类的静态成员,访问非静态成员依旧需要先创建内部类对象

外部类及静态成员内部类

// 外部类
public class OuterClass {
    private String color = "red";
    // 静态成员变量
    static private int num = 10;

    // ====================== 静态成员内部类(可以使用权限修饰符) ==========================
    static class StaticInnerClass {
        // 成员变量
        private int num = 30;

        // 构造方法
        public InnerClass() {
        }

        // 成员方法
        public void innerMethod() {
            // 【1. 静态成员内部类,只能访问外部类的静态成员】
            // System.out.println(color);
            // outerMethod();

            int num = 20;
            System.out.println(num);                  // 局部变量的
            System.out.println(this.num);             // 内部类的
            // 【3. 如果内部类,外部类有同名的【【静态】】变量,通过外部类的类名调用】
            System.out.println(OuterClass.num);       // 外部类的!!!
        }

        // 静态成员方法
        public static void staticInnerMethod() {
        }
    }

    public void outerMethod2() {
        // 【2. 外部类不能直接访问静态内部类的非静态成员,需要静态内部类的对象】
        StaticInnerClass in = new StaticInnerClass();
        in.innerMethod();
        
        // 外部类可以通过静态内部类的类名,直接访问内部类的【【静态方法】】
        StaticInnerClass.staticInnerMethod();
    }
}

Test类

public class Test {
    public static void main(String[] args) {
        // 创建静态成员内部类对象【4. 使用外部类创建,静态成员内部类属于外部类】
        OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();
    }
}

5.3 局部内部类

  • 局部内部类的作用范围是 当前方法
  • 要想访问匿名内部类所在方法的变量(JDK1.8默认使用 final 修饰,JDK7及之前,要想访问,需要用 final 修饰)
public class OutClass {
    private int num = 10;

    public void outMethod() {
        int num2 = 20;

        // ====================== 局部内部类(不能使用权限修饰符) =======================
        class LocalInnerClass {
            private int num = 30;

            public void localInnerMethod() {
                int num = 40;
                System.out.println(num);                    // 局部变量
                System.out.println(this.num);               // 匿名内部类的
                System.out.println(OutClass.this.num);      // 外部类的

                // 匿名内部类所在方法的变量(JDK1.8默认使用 final 修饰,JDK7及之前,要想访问,需要用 final 修饰)
                System.out.println(num2);
            }
        }

        // 【1. 局部内部类的作用范围是 当前方法 】
        LocalInnerClass lic = new LocalInnerClass();
        lic.localInnerMethod();
    }
}

5.4 匿名内部类

  • 匿名内部类是一种特殊的局部内部类,在方法中定义
  • 匿名内部类可以实现一个接口(只能实现一个),也可以继承一个类
  • 必须实现所有的方法,匿名内部类不能是抽象类
  • 匿名内部类不可能有构造方法,因为类是匿名的
  • 匿名内部类没有访问修饰符
  • 如果想实现构造方法形式的一些初始化功能,可以通过代码块实现
  • 如果要访问所在方法的局部变量,该变量需要使用 final 修饰(JDK1.8可以省略)

还可以创建外部比较器 Comparator,用于内部比较器定义了比较规则后(比如用 price 比较),想更改比较规则时(不使用 price 了,使用 name 比较)使用。

Comparator接口

public interface Comparator {
    int compare(Object obj1, Object obj2);
}

新增的比较规则类

public class BookNameComparator implements Comparator {
    @Override
    public int compare(Object obj1, Object obj2) {
        Book book1 = (Book)obj1;
        Book book2 = (Book)obj2;
        return book1.getName().compareTo(book2.getName());
    }
}

使用新增的比较规则类

public class Test {
    public static void main(String[] args) {
        Book book1 = new Book("abc", 80);
        Book book2 = new Book("abcd", 80);
        // 内部比较器
        book1.compareTo(book2);
        // 

以上是关于JavaLearn # 面向对象编程_4的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段——JS中的面向对象编程

VSCode自定义代码片段9——JS中的面向对象编程

JavaLearn # 面向对象案例:猜丁壳

python之路之前没搞明白4面向对象(封装)

读编程与类型系统笔记08_面向对象变成的元素

翻译:《实用的Python编程》04_01_Class