Featured image of post Chapter11

Chapter11

继承 和 多态(java)

继承 和 多态(java)

这个章节是基于类的延伸

子类和父类

在 java 中有一个关键字叫 ’extends’,意思是扩展, 这个关键字可以定义一个类的延伸类, 并具有所有原先类的方法(method)和变量(data field)。

for example: 定义一个类叫 person, 那么他的子类就可以是学生, 老师, 员工等.

这样定义有什么好处吗?

  1. 当你已经使用了一个类完成某项代码任务的时候, 有其他的代码也需要用到这个类 但是这个类中的某些东西方法需要改变, 或者有些变量需要增加的时候, 你就可以在不改变原来的类的情况下创建一个子类, 对子类进行修改来完成新的任务.
  2. 通过这样的方法可以大大减少代码的冗余.提高代码的复用性.

具体实现

public class Person {
    private String name;
    private int age;
    private String gender;

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

public class Student extends Person {
    private String major;
    public Student(String name, int age, String gender, String major) {
        super(name, age, gender);
        // 在这里, 我们的super keyword 的作用就是调用父类的构造器.
        this.major = major;
    }
}
  1. 如果这里的构造器没有调用父类的构造器, 那么编译器会会在编译的时候在该构造器的最前面先调用父类的无参构造器.
  2. 当父类使用 final 关键字的时候, 则无法定义其子类

转型

在 Java 中,类型转换主要分为两种:向上转型(upcasting)和向下转型(downcasting)。 转型可以改变原有实例的 data field. 但同时也伴随着风险.

向上转型(Upcasting)

向上转型是将子类类型转换为父类类型,这种转换是自动的、安全的。因为子类一定包含父类的所有特性。

  1. Method: 如果子类重写方法则使用重写之后的方法.
  2. Data Field: 保留父类的变量, 但是不能使用子类的变量.

应用场景

  1. 当你需要子类的方法的时候但是却不需要子类的变量的时候.
// 向上转型示例
Student student = new Student("张三", 20, "男", "计算机科学");
Person person = student; // 自动向上转型
// 或者显式写法
Person person2 = (Person)student; // 效果相同

子类想临时使用父类的某个方法的时候可以仿照这个例子调用: “((father)child).method();”

向下转型(Downcasting)

能够向下转型的几种情况.

  1. new 类的时候使用的是子类, 但是当作了父类来使用.
  2. 或者在继承类上转换, 例如: person -> faculty -> teacher 这三个类. 当 new 的类是 teacher, 但是被当作 person 使用的时候, 那 person 类就能转型为 faculty.
// 向下转型示例
Person person = new Student("李四", 19, "女", "数学"); // 首先向上转型
Student student = (Student)person; // 向下转型

// 错误的向下转型示例
Person person2 = new Person("王五", 25, "男");
Student student2 = (Student)person2; // 运行时会抛出 ClassCastException

// 转型前错误检查能有效避免运行错误
if (person instanceof Student) {
    // instanceof这个关键字能检查实例是否属于某个指定的类.
    Student student = (Student)person;
    System.out.println("专业:" + student.getMajor());
} else {
    System.out.println("这个人不是学生");
}

关于 “instanceof” 关键字: 检查的是该类在创建时的 new 的类是否属于某个类链条上.

Nest class | 嵌套类(非大纲)

作用

  1. 可以避免多个顶层类.
  2. 可以将一些只在外部类中使用的类封装在外部类中. 比如, 只有学生这个类需要课程表这个类, 那么这个类就只需要在学生类下定义.

内部类 (Inner Class)

  1. 能够访问外部类的所有成员.
public class OuterClass {
    private String msg = "Hello";

    // 非静态内部类
    class InnerClass {
        public void display() {
            // 可以访问外部类的所有成员
            System.out.println(msg);
        }
    }
}

静态嵌套类 (Static Nested Class)

  1. 静态嵌套类不能直接访问内部的静态成员. 当前示例代码中的静态子类就无法访问num这个变量.
  2. 静态嵌套类虽然无法访问外部类的非静态成员, 但是可以访问该类内部的成员. 比如示例代码中的a.
public class OuterClass {
    private static String msg = "Hello";
    private int num = 10;

    // 静态嵌套类
    static class StaticNestedClass {
        int a = 10;
        public void display() {
            // 可以访问外部类的静态成员
            System.out.println(msg);
        }
    }
}

Overriding Methods | 方法重写

当一个学生类想要调用toString方法输出自己的信息的时候, person类的toString方法可能并没有涉及到学生类新data field, 比如年纪, 学号等. 这个时候就是方法重写大展拳脚的时候了.

  1. 在子类中, 我们可以使用@Override签名来重写父类的方法.
  2. 这样做的好处是:
    • 拒绝冗余的方法定义: 我们可以在不变动父类方法的情况下创建一个新的方法来实现相同的功能. 但是父类的方法就会继承到子类中, 造成代码的冗余.
    • 同时当我们调用方法的时候, 也会造成调用格式不统一的问题.
class father{
    void methodA{
        System.out.println("This is A")
    }
}

class children{
    @override
    // 其实这里不使用这个签名方法也能顺利重写, 但为了代码规范还是建议在重写的方法前添加这个签名
    void methodA{
        System.out.println("This is also A")
    }
}

比如之前章节学的, 当我们使用wrapper class的时候, 不管是什么类型的类, 我们都可以使用相同的compareTo方法.

方法重写的前提是父类的方法没有使用 final 关键字. 使用 final 关键字的方法无法被子类重写, 这样做可以防止一些关键的方法被错误的重写, 能让子类的代码更加规范.

Overriding vs. Overloading

方法重写

方法重写是子类对与父类的方法的重新定义

方法重载

方法重载是多个相同签名的方法, 但是在调用的时候会根据传入的参数类型和数量来决定使用哪个方法.

Object Class | 对象类

在 java 中所有的类都是 object class 的子类

toString method

不考试的同学可以跳过本章节, 博主认为这个方法没有特别的作用, 了解一下就好.

  1. 这是所有对象都包含的一个方法.

  2. 调用后会显示一些关于这个类的信息. 信息的构成为<实例名称>@<哈希值>

当你使用 print 直接打印的类的时候, 类就会自动调用这个方法然后返回字符串.

多态

这个章节主要讲类可以作为一个参数传入方法中使用.

Dynamic Binding | 动态绑定

前文提到的方法签名统一的好处就体现在这里.

  1. 当我们按照之前的代码规范重写代码, 保持方法签名的统一的时候. 我们就能在方法中使用相同的方法签名来调用不同类中相同功能的方法, 而不需要提前规定好方法只能接受哪种类.

equals method

在原始的对象类中, 这个方法会对比对象的内存地址是否相同.

实际中的作用

由于这个方法签名比较常用. 所以很多时候, 如果我们想要自定义某个类的该方法, 我们就会重写这个方法.

== vs. equals

在 equals 方法没有被重写的时候, 我们有: ‘==’ > ’equals’的结论. 因为双等号除了能对比对象地址外还能比较基本变量的值.

重写规范

当你重写这个方法的时候传入的参数一定是 object 类. 示例:

@override
public boolean equals(Object circle) {
    return this.radius == ((Circle)circle).radius;
    }

The ArrayList Class

  1. 容量是动态的

创建

// 创建一个空的 ArrayList
ArrayList<String> list1 = new ArrayList<>();

// 创建一个指定初始容量的 ArrayList
ArrayList<Integer> list2 = new ArrayList<>(10);

// 从其他集合创建 ArrayList
ArrayList<Double> list3 = new ArrayList<>(Arrays.asList(1.1, 2.2, 3.3));

‘<>‘是泛型, 目前知道知道括号里填什么类型的 wrapper 类, 这个数组就会存储什么类型的变量.

add

// 在列表末尾添加元素
list.add(element);           // 返回 boolean

// 在指定位置插入元素
list.add(index, element);    // void 返回

// 添加多个元素
list.addAll(collection);     // 返回 boolean
list.addAll(index, collection); // 返回 boolean

visit

// 获取指定位置的元素
Element element = list.get(index);

// 获取第一次出现的位置
int index = list.indexOf(element);

// 获取最后一次出现的位置
int lastIndex = list.lastIndexOf(element);

modify

// 替换指定位置的元素
Element oldElement = list.set(index, newElement);

delete

// 删除指定位置的元素
Element removedElement = list.remove(index);

// 删除第一次出现的指定元素
boolean isRemoved = list.remove(element);

// 删除所有元素
list.clear();

query

// 检查是否包含某个元素
boolean contains = list.contains(element);

// 检查是否为空
boolean isEmpty = list.isEmpty();

// 获取列表大小
int size = list.size();

transfer

// 转换为数组
Object[] array = list.toArray();
String[] stringArray = list.toArray(new String[0]);

// 获取子列表(视图)
List<E> subList = list.subList(fromIndex, toIndex);

sort

// 排序(使用自然顺序)
Collections.sort(list);

// 使用自定义比较器排序
Collections.sort(list, comparator);

// 反转列表
Collections.reverse(list);

这个类并不线程安全, 这意味着当多个线程同时操作这个类的时候可能会出现一些问题. 另外虽然这个类的容量的可变的, 但还是尽量在一开始就把容量定义好. 因为当类需要自己扩增容量的时候会影响性能.

A winner is just a loser who tried one more time.
Robust AI
使用 Hugo 构建
主题 StackJimmy 设计