类与类之间的关系

作者: zsh2517 分类: 未分类 发布时间: 2021-07-07 14:39
  1. 面向过程与面向对象的思想
  2. 类与类之间的关系
  3. 复杂类型的设计原则

类与类之间的关系

1. 继承与派生

一个类能够衍生出其他的类,这个衍生的过程叫做“派生”,其中新类(派生类)也叫做子类,旧类(基类)也叫做父类。

比如 class People 定义了人的性质,那么可以有 class Student extends People 进一步的定义 Student 学生,其中 People 的一切都会继承给 Student,而 Student 可以继续拥有新的成员。

class People {
    String name;
    Integer age;
}

class Student extends People {
    Integer score;
    Integer grade;
}

class Main {
    public static void main(String[] args) {
        People people = new People();
        Student student = new Student();

        System.out.println(people.name);
        System.out.println(people.age);

        System.out.println(student.name);
        System.out.println(student.age);
        System.out.println(student.score);
        System.out.println(student.grade);
    }
}

1.1 Java 的单继承

在 Java 里面,派生类只能有一个父类,(C++ 支持多继承,即一个派生类可以同时有多个基类)

但是可以通过委托(详见后面)的方式,将另一个类的实例作为成员,同步操作到另一个类,从而间接地实现一定程度上的功能的复用(

1.2 Java 的继承权限

修饰符 当前类 同 包 子 类 其他包
public
protected ×
default × ×
private × × ×

default 即默认情况,不写访问限定的时候,成员变量和成员函数使用的访问方式

简单说,不写访问限定的话,default 模式下,同包相当于 public,非同包相当于 private

1.3 方法的重写

重写是子类对于能够访问的父类的方法的重新实现,在派生类中,可以定义(重新实现)相同(包括参数、返回值和函数名都相同)的成员函数,从而覆盖以前的函数。

对于异常来说,重写可以不抛异常,抛出相同的异常或者是父类异常类型的子异常。(即范围只能比父类小,也就是说需要子类需要抛出父类能够覆盖到的异常

比如对于图形 Shape 可以有一个函数 void Draw() 绘制这个图形。而圆形 class Circle extends Shape 可以继承自 Shape ,之后重写 void Draw 并且把绘制出来图案改成圆形。根据后面多态部分可以得知,如果 Draw 再被调用,被调用的就是画圆了。

注解 @Override

class People {
    void sleep() {
        System.out.println("People sleep");

    }
}

class Student extends People {
    @Override
    void sleep() {
        System.out.println("Student sleep");
    }
}

@Override 可以不写,不影响函数的重写,但是加上后强制的说明了这个函数是重写函数,这样如果函数定义出了问题(比如被当作了新的函数,或者重载函数)的话,会发生错误

1.4 final

使用final关键字做标识有“最终的”含义。

修饰类,则该类不允许被继承。

修饰方法,则该方法不允许被覆盖(重写)。

修饰属性,则该类的该属性不会进行隐式的初始化,所以 该final 属性的初始化属性必须有值,或在构造方法中赋值(但只能选其一,且必须选其一,因为没有默认值!),初始化之后就不能改了,只能赋值一次。

修饰变量,则该变量的值只能赋一次值,在声明变量的时候才能赋值,即变为常量。

1.5 this

this 指代当前个体,和其他语言的 this(python 为 self 类似)。

(关于 this 的使用,详见另一篇面向对象的初步(还未发布))

1.6 super

super 是对于父对象的引用,可以用来调用有权限访问的父类的内容(比如构造函数等),对于重写后的函数,可以调用原函数

class People {
    void sleep() {
        System.out.println("People sleep");
    }
}

class Student extends People {
    @Override
    void sleep() {
        System.out.println("Student sleep");
        super.sleep();

    }
}

class Main {
    public static void main(String[] args) {
        People people = new People();
        Student student = new Student();
        people.sleep(); // People sleep
        student.sleep(); // Student sleep, People sleep
    }
}

构造函数

Java 会默认为没有任何构造函数的类生成无参数的构造函数(比如变量初始化赋值为 null 或 0),但是如果已经定义了一个有参数的构造函数,则不会自动生成。

如果子类的构造方法中没有显示调用父类的构造方法,则系统默认调用父类无参的构造方法。

如果自己用super关键字在子类里调用父类的构造方法,则必须在子类的构造方法中的第一行。

如果子类构造方法中既没有显示调用父类的构造方法,而父类没有无参的构造方法,则编译出错。

1.7 Java 的 Object 类

Object 是一个比较特殊的类,是一切 class 的基类。getClass, clone, toString, hashCode, equals 等操作,都是从 Object即开始存在的,之后由于一切继承自 Object ,所以所有的类型都有这些函数

而类型为 Object 的参数/变量,由于继承关系,可以存放任意类型的数据,比如 equals 操作。

ArrayList 是一个很常用的数据结构,继承的关系为

java.lang.Object
java.util.AbstractCollection<E>
java.util.AbstractList<E>
java.util.ArrayList<E>

1.8 Java 基元类型引用类型

Java 的类型严格来说分为基元类型和引用类型,基元类型就像 C 语言的类型一样,它的值就是它的值,而引用类型则是从 Object 派生出来的某个类型,本质上都是一个class,每个基元类型都有对应的引用类型

引用类型除了多了方法(因为是对象了)之外,额外的增加了取值 null 即空引用(类似于空指针)

数据类型 大小 符号值范围 类别 引用类型
byte 1 字节 -128 –> 127 整数 Byte
short 2 字节 -32768 –> 32767 整数 Short
int 4 字节 -2147483648 –> 2147483647 整数 Integer
long 8 字节 -9,223,372,036,854,775,808 –> 9,223,372,036,854,775,807 整数 Long
float 4 字节 7位有效数字 浮点数 Float
double 8 字节 15位有效数字 双精度数 Double
char 2 字节 ’\u0000’ –> ’\uffff’
0 –> 65535
16位 Unicode 字符 Character
boolean 1 位 true or false 逻辑值 Boolean

在 Java 的泛型操作中,类型必须是引用类型,比如不允许 List<int> arrayList = new ArrayList<int>();,但是可以用包装后的引用类型 List<Integer> arrayList = new ArrayList<Integer>();

2. 委托

委托(Delegation)的目的更多的是提高代码的复用性,简单说是在一个类里面创建另一个类的实例,并且将调用自己的操作转发给这个实例。

(16条消息) Java中的委托和继承(Delegation and Inheritance)_黑白沙漠-CSDN博客

3. 接口与实现

(以下 实现接口 两个词,均表示 Java 里面 Interfaceimplements 的含义

接口 Interface,在 java 编程语言中是一个抽象类型,是抽象方法的集合。

一个类可以以 实现 (implements) 一个 Interface 的方式,“继承”来自这个类的抽象方法

但是函数的参数,以及变量类型都可以使用一个 Interface 作为类型,接收该接口实现的类型

简单说就是接口里面定义了一系列的成员函数的声明,如果需要用这些函数,那么可以直接实现该接口

3.1 接口与实现

语法

public Interface InterfaceName [extends AnotherInterface] {
    // 抽象方法
    public int countEquals(String name);
}

实现

class ClassName implements InterfaceName;

接口中也可以定义变量,但是此变量会被强制以 public static final 进行修饰(5. Java基础:接口 – 知乎 (zhihu.com))而且 IDEA 对于这种的补全还是有点问题

interface Inter {
    public String name = "123";
}

class Test implements Inter{
    public void print() {
        System.out.println(this.name);
    }
}

public class InterfaceTest {
    public static void main(String[] args) {
        Test t = new Test();
        t.print();
    }
}


3.2 单继承与多接口

在 C++ 中,一个类可以继承自多个类,比如

class TeacherAssistant: public Student, public Teacher;

但是在 Java 中,一个类仅能有一个父类,如果需要额外的继承关系(比如作为参数传递,或者作为规约实现),可以通过接口的方式

class ArrayList<T> extends AbstractList<T> implements Serializable, Cloneable, Iterable<T>, Collection<T>, List<T>, RandomAccess

比如经常写的 List<String> stringList = new ArrayList<String>(); 就是用到了 ArrayList<T> implements List<T> 的关系

Map<String> stringMap = new HashMap(); stringMap.addAll(stringList); 里面的 addAll 则是 Collection<T> 里面定义的

boolean addAll(Collection<? extends E> c)
Adds all of the elements in the specified collection to this collection (optional operation).

3.3 抽象类与抽象方法

抽象类更像是一个介于 类 和 接口 中间的概念,接口的不完全实现是抽象类,可以作为基类进一步派生出其他的类,也可以作为参数/变量类型接受新的类。

(但是抽象类不一定要从接口出发,可以独立定义)

但是抽象类由于方法的实现不完全,本身并不能实例化。

主动定义一个抽象类的时候,如果需要不实现的方法,应该为其添加 abstruct 声明,表示抽象方法

主动定义一个抽象类

abstract class Shape {
    private String name;
    private int lines;

    Shape(String name, int lines) {
        this.name = name;
        this.lines = lines;
    }

    abstract void Draw();
}

从接口实现不完全的抽象类

interface Shape {
    void Draw();
}

abstract class FourLineShape {
    private int a;
    private int b;
    private int c;
    private int d;

    FourLineShape(int a, int b, int c, int d) {
        this.a = a;
        this.b = b;
        this.c = c;
        this.d = d;
    }
}

class Square extends FourLineShape {
    Square() {
        super(0, 0, 0, 0);
    }
    Square(int l) {
        super(l, l, l, l);
    }
    void Draw() {
        // draw the square
    }
}

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注