Java基础 | 面向对象(中)

Java基础 | 面向对象(中)

继承

语法格式

class A extends B {}

  • A 类即子类、派生类、subclass
    B 类即父类、基类、超类、superclass
  • 一旦子类继承父类后,子类会获取父类中所有声明的属性和方法,但是父类中 private 属性或方法,因封装性,在子类中不可见
  • 子类继承父类后,可以声明自己特有的属性和方法,以扩展类的功能
  • 一个子类只能有一个父类(但是类可以实现多个接口),一个类可以被多个子类继承
    多层继承是允许的,直接继承的即直接父类,间接继承的即间接父类,继承后会获取所有直接/间接父类的属性和方法

重写 Override

子类根据需要对从父类继承来的方法进行改造,即方法的重写、覆盖。重写以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。

重载与重写

  • 重载:重载发生在同一个类中,允许存在多个同名不同参(类型、个数)的方法,编译器会根据方法不同的参数列表,对同名方法的方法名进行修饰,对于编译器而言,这些方法就成了不同方法,它们的调用地址在编译时期就绑定了,即早绑定或者静态绑定
  • 多态与重写:重写发生在子类,子类方法的方法名和参数列表与父类相同,运行时期才会确定具体调用重写的哪个方法,即晚绑定或者动态绑定

不要犯傻,如果它不是晚绑定,它就不是多态。

要求

  1. 子类重写的方法必须和父类中被重写的方法具有相同的方法名参数列表
  2. 子类重写的方法的返回值类型不大于父类中被重写的方法
父类被重写方法的返回值子类重写方法的返回值
voidvoid
A类A类或A类的子类
基本数据类型相同的基本数据类型
  1. 子类重写的方法的访问权限不小于父类中被重写的方法,子类不能重写父类的 private 方法
  2. 子类重写的方法抛出异常不大于父类中被重写的方法的异常(否则在多态的情况下,可能出现处理不了的异常类型)
  3. 子父类中的同名同参的方法,必须同时被声明为非 static(使用重写),或同时声明为 static(不是重写),static 方法随类的加载而加载,static 方法不可被重写。

super 关键字

super 可以理解为 父类的...,使用 super 关键字可以调用父类的属性、方法、构造器

调用父类的属性和方法

当子类中定义了与父类同名的属性或重写了父类的方法时,要想在子类中调用父类中声明的属性或方法,则必须显式的使用 super.属性super.方法 的方式,表明调用的是父类中声明的属性或方法。

调用父类的构造器

  • 可以在子类的构造器中显式的使用 super(形参列表) 的方式,调用父类中声明的指定的构造器,且必须声明在子类构造器的首行,所以,针对于 this(形参列表)super(形参列表) 只能二选一,不能同时出现
  • 在构造器的首行,没显式的声明 this(形参列表)super(形参列表),则默认调用的是父类中空参的构造器:super()
  • 在类的多个构造器中,至少一个类的构造器中使用了 super(形参列表),调用父类中的构造器

对象实例化的全过程

  • 结果上来看:子类继承父类后,获取了父类中声明的属性和方法,创建子类的对象,在堆空间中,就会加载所有父类中声明的属性
  • 过程上来看:n 个构造器 最多 有 n-1 个 构造器使用到了 this() 关键字调用本类中的其他构造器,而没有使用 this() 的构造器一定使用 super() 关键字显示或隐式的调用了父类的构造器,直至调用 java.lang.Object 的空参构造器,正因如此,子类才会获取父类中的所有结构,子类对象才可以考虑进行调用
  • 注意:尽管调用了父类的构造器,但是内存中只创建了一个对象

多态

引用变量有两个类型:编译时类型、运行时类型,编译时类型声明该变量所使用的类型决定,运行时类型由实际赋给该变量的对象决定,若编译时类型和运行时类型不一致,即形成多态。简言之,多态就是父类的引用指向子类的对象。

  • 多态的使用前提:有类的继承关系,有方法的重写
  • 多态情况下,对象调用子父类同名同参的方法时,实际运行时执行的是子类重写的方法,即虚拟方法调用,但是,如果该对象调用父类中没有的方法,则无法通过编译
  • 多态可以应用在抽象类和接口上
// 一个数据库的例子
public void doData(Connection conn) {
    // 规范步骤操作数据库
    // conn可以是任何数据库实现的对象,比如MySQL会重写如下的数据操作的标准方法
    conn.method1();
    conn.method2();
    conn.method3();
}

注意 多态不适用于属性,编译时和运行时都看声明变量所使用的的类型,父类的引用不能调用子类所特有的属性和方法

向下转型

为什么使用向下转型

有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。若要调用子类特有的属性和方法,必须使用向下转型。而实现向下转型,就要使用强制类型转换。

class Man extends Person {
    //...
}

class Test {
    void testMethod() {
        // 此时p是 Person 类型,但对象是Man的对象,使用p无法调用Man特有的属性和方法
        Person p = new Man();
        // 强制类型转换,把 Person 类型的p转换成Man类型的m
        // 这样就可以使用m去调用Man特有的属性和方法
        Man m = (Man)p;
    }
}

但是使用强制类型转换的时候,可能出现 ClassCastException 异常,所以有必要先使用 instanceof 来检查,返回true之后再进行强转

instanceof

  • a instanceof A:判断对象 a 是否是类 A 的一个实例。如果是,返回true;如果不是,返回false
  • 要求 a 所属的类与类 A 必须是子类和父类的关系,否则编译错误

Objiect类

如果一个类没有使用 extends 显式的声明其父类,则其默认继承于 java.lang.Object类,所有 Java 类(除了 java.lang.Object)都直接或间接继承于 java.lang.Object 类,Object类没有属性,只有一个空参构造器

常用方法

最常用

  • equals()
  • toString()
  • getClass()
  • hashCode()
  • clone()

垃圾回收相关

  • finalize()

多线程

  • wait()
  • notify()
  • notifyAll()

== 和 equals()

==

  • 是一个运算符
  • 可以用于基本数据类型变量和引用数据类型变量的比较
  • 若比较基本数据类型变量,实际比较的是变量所保存的数据数值是否相等,而且所比较的两个变量的类型不一定相同(不可用于boolean和其他类型的比较)
  • 若比较引用数据类型变量,实际比较的是引用所指向对象的地址值是否相同,即比较两个引用是否指向同一对象实体

equals()

  • 是一个方法
  • 只能通过对象调用,不可以使用在基本数据类型变量上
  • 若比较的类没有重写 Object 中的 equals(),则其等价于 == ,即比较两个对象的地址值
  • 若类重写了 equals() 方法,比如 String 类、Date 类、File 类、包装类等,则比较对象的实体内容是否相同,即比较对象的某个或某些属性的值是否相同
  • 自定义类可以重写 equals() 方法,实际开发中,IDEA 可以自动生成重写的 equals() 方法
class MyClass {
    int data;
    String str;

    @Override
    public boolean equals(Object obj) {
        if(obj == null)
            return false;
        if(this == obj)
            return true;
        if(obj instanceof MyClass) {
            MyClass anotherObj = (MyClass)obj;
            return this.data==anotherObj.data && this.str.equals(anotherObj.str);
        }else {
            return false;
        }
    }
}

toString

  • Object 类中的 toString() 就是返回一个字符串,内容是 对象类名@对象的16进制哈希值(JVM地址值)
  • 重写了Object类的toString的类,比如 String 类、Date 类,调用 toString() 方法,会返回对象的“实体内容”
  • 当使用 System.out.println() 打印一个对象的引用时,实际上是在该方法的参数中调用该对象的 toString() 方法,参数就是对象 toString() 的返回值

包装类 Wrapper

针对八中几本数据类型定义对应的应用类型:包装类

基本数据类型对应包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
charCharacter

其中 Byte、Short、Integer、Long、Float、Double 类有共同的父类 Number

基本数据类型--->包装类

// 包装类的构造器,手动装箱
int num1 = 10;
Integer num2 = new Integer(num1);

// 自动装箱
int num3 = 10;
Integer num4 = num3;
boolean b1 = true;
Boolean b2 = b1;

包装类--->基本数据类型

// 包装类的方法,手动拆箱
Integer num1 = new Integer(10);
int num2 = num1.intValue();
Double num3 = new Double(3.14);
double num4 = num3.doubleValue();

// 自动拆箱
int num5 = num1;

基本数据类型--->String

// 连接一个空字符串
double num = 3.14;
String str1 = ""+ num;

// String 类重载的静态方法 valueOf( int/double/... )
float f = 3.14F;
String str2 = String.valueOf(f);

String--->基本数据类型

// 对应包装类的静态方法 parseXxx(String)
// z字符串内容必须是纯粹数值
String str1 = "123";
int num = Integer.parseInt(str);
// 不区分大小写
String str2 = "TrUe";
boolean b = Boolean.parseBoolean(str2);

包装类--->String

// String 类重载的静态方法 valueOf( Integer/Double/... )
Double d = new Double(5.2);
String str1 = String.valueOf(d);

// 包装类的 toString 方法
String str2 = d.toString();

String--->包装类

// 包装类的构造器
Integer num = new Integer("123");
// 字符串必须是纯粹的数值

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://yzt.cool/archives/2020-03-02-javaobjectoriented2