当前位置:   article > 正文

2023最新版JavaSE教程——第7天:面向对象编程(进阶)_比较新的java教程

比较新的java教程

目录

一、关键字:this

1.1 this是什么?

  • 在 Java 中,this 关键字不算难理解,它的作用和其词义很接近。
    • 它在方法(准确的说是实例方法或非 static 的方法)内部使用,表示调用该方法的对象
    • 它在构造器内部使用,表示该构造器正在初始化的对象。
  • this 可以调用的结构:成员变量、方法和构造器

1.2 什么时候使用this

1.2.1 实例方法或构造器中使用当前对象的成员

在实例方法或构造器中,如果使用当前类的成员变量或成员方法可以在其前面添加 this,增强程序的可读性。不过,通常我们都习惯省略 this。但是,当形参与成员变量同名时,如果在方法内或构造器内需要使用成员变量,必须添加 this 来表明该变量是类的成员变量。即:我们可以用 this 来区分 成员变量局部变量。比如:
在这里插入图片描述
另外,使用 this 访问属性和方法时,如果在本类中未找到,会从父类中查找。这个在继承中会讲到。举例1:

class Person{		// 定义Person类
	private String name ;	
	private int age ;			
	public Person(String name,int age){	
		this.name = name ;   
		this.age = age ;  
    }
    public void setName(String name){
        this.name = name;
    }
    public void setAge(int age){
        this.age = age;
    }
	public void getInfo(){	
		System.out.println("姓名:" + name) ;
		this.speak();
	}
	public void speak(){
		System.out.println(“年龄:” + this.age);	
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

举例2:

public class Rectangle {
    int length;
    int width;

    public int area() {
        return this.length * this.width;
    }

    public int perimeter(){
        return 2 * (this.length + this.width);
    }

    public void print(char sign) {
        for (int i = 1; i <= this.width; i++) {
            for (int j = 1; j <= this.length; j++) {
                System.out.print(sign);
            }
            System.out.println();
        }
    }

    public String getInfo(){
        return "长:" + this.length + ",宽:" + this.width +",面积:" + this.area() +",周长:" + this.perimeter();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

测试类:

public class TestRectangle {
    public static void main(String[] args) {
        Rectangle r1 = new Rectangle();
        Rectangle r2 = new Rectangle();

        System.out.println("r1对象:" + r1.getInfo());
        System.out.println("r2对象:" + r2.getInfo());

        r1.length = 10;
        r1.width = 2;
        System.out.println("r1对象:" + r1.getInfo());
        System.out.println("r2对象:" + r2.getInfo());

        r1.print('#');
        System.out.println("---------------------");
        r1.print('&');

        System.out.println("---------------------");
        r2.print('#');
        System.out.println("---------------------");
        r2.print('%');
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

1.2.2 同一个类中构造器互相调用

this 可以作为一个类中构造器相互调用的特殊格式。

  • this():调用本类的无参构造器
  • this(实参列表):调用本类的有参构造器
public class Student {
    private String name;
    private int age;

    // 无参构造
    public Student() {
//        this("",18);//调用本类有参构造器
    }

    // 有参构造
    public Student(String name) {
        this();//调用本类无参构造器
        this.name = name;
    }
    // 有参构造
    public Student(String name,int age){
        this(name);//调用本类中有一个String参数的构造器
        this.age = age;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    public String getInfo(){
        return "姓名:" + name +",年龄:" + age;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

注意:

  • 不能出现递归调用。比如,调用自身构造器。
    • 推论:如果一个类中声明了n个构造器,则最多有 n-1 个构造器中使用了 this(形参列表)
  • this() 和 this(实参列表) 只能声明在构造器首行。
    • 推论:在类的一个构造器中,最多只能声明一个 this(形参列表)

1.3 练习

练习1: 添加必要的构造器,综合应用构造器的重载,this 关键字。
在这里插入图片描述
练习2: 按照如下的 UML 类图,创建相应的类,提供必要的结构:
在这里插入图片描述
在提款方法 withdraw() 中,需要判断用户余额是否能够满足提款数额的要求,如果不能,应给出提示。deposit() 方法表示存款。

(2) 按照如下的 UML 类图,创建相应的类,提供必要的结构
在这里插入图片描述
(3) 按照如下的 UML 类图,创建相应的类,提供必要的结构
在这里插入图片描述

  • addCustomer 方法必须依照参数(姓,名)构造一个新的 Customer 对象,然后把它放到 customer 数组中。还必须把 numberOfCustomer 属性的值加 1。
  • getNumOfCustomers 方法返回 numberofCustomers 属性值。
  • getCustomer 方法返回与给出的 index 参数相关的客户。

(4) 创建 BankTest 类,进行测试。内存解析图:
在这里插入图片描述

二、面向对象特征二:继承(Inheritance)

2.1 继承的概述

2.1.1 生活中的继承

财产继承:
在这里插入图片描述
绿化:前人栽树,后人乘凉
在这里插入图片描述
绿水青山,就是金山银山。样貌:
在这里插入图片描述
继承之外,是不是还可以 进化
在这里插入图片描述
继承有延续(下一代延续上一代的基因、财富)、扩展(下一代和上一代又有所不同)的意思。

2.1.2 Java中的继承

角度一:从上而下。 为描述和处理 个人 信息,定义类 Person:
在这里插入图片描述
为描述和处理 学生 信息,定义类 Student:
在这里插入图片描述
通过继承,简化 Student 类的定义:
在这里插入图片描述
说明:Student 类继承了父类 Person 的所有属性和方法,并增加了一个属性 school。Person 中的属性和方法,Student 都可以使用。

角度二:从下而上
在这里插入图片描述
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成 继承关系。如下图所示:
在这里插入图片描述
再举例:
在这里插入图片描述

2.1.3 继承的好处

  • 继承的出现减少了代码冗余,提高了代码的复用性。
  • 继承的出现,更有利于功能的扩展。
  • 继承的出现让类与类之间产生了 is-a 的关系,为多态的使用提供了前提。
  • 继承描述事物之间的所属关系,这种关系是:is-a 的关系。可见,父类更通用、更一般,子类更具体。

注意:不要仅为了获取其他类中某个功能而去继承!

2.2 继承的语法

2.2.1 继承中的语法格式

通过 extends 关键字,可以声明一个类B继承另外一个类A,定义格式如下:

[修饰符] classA {
	...
}

[修饰符] classB extendsA {
	...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.2.2 继承中的基本概念

类B,称为子类、派生类(derived class)、SubClass
类A,称为父类、超类、基类(base class)、SuperClass

2.3 代码举例

1、父类

/*
 * 定义动物类Animal,做为父类
 */
public class Animal {
    // 定义name属性
    String name;
    // 定义age属性
    int age;

    // 定义动物的吃东西方法
    public void eat() {
        System.out.println(age + "岁的"
                + name + "在吃东西");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2、子类

/*
 * 定义猫类Cat 继承 动物类Animal
 */
public class Cat extends Animal {
    int count;//记录每只猫抓的老鼠数量

    // 定义一个猫抓老鼠的方法catchMouse
    public void catchMouse() {
        count++;
        System.out.println("抓老鼠,已经抓了"
                + count + "只老鼠");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

3、测试类

public class TestCat {
    public static void main(String[] args) {
        // 创建一个猫类对象
        Cat cat = new Cat();
        // 为该猫类对象的name属性进行赋值
        cat.name = "Tom";
        // 为该猫类对象的age属性进行赋值
        cat.age = 2;
        // 调用该猫继承来的eat()方法
        cat.eat();
        // 调用该猫的catchMouse()方法
        cat.catchMouse();
        cat.catchMouse();
        cat.catchMouse();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

2.4 继承性的细节说明

1、子类会继承父类所有的实例变量和实例方法

从类的定义来看,类是一类具有相同特性的事物的抽象描述。父类是所有子类共同特征的抽象描述。而实例变量和实例方法就是事物的特征,那么父类中声明的实例变量和实例方法代表子类事物也有这个特征。

  • 当子类对象被创建时,在堆中给对象申请内存时,就要看子类和父类都声明了什么实例变量,这些实例变量都要分配内存。
  • 当子类对象调用方法时,编译器会先在子类模板中看该类是否有这个方法,如果没找到,会看它的父类甚至父类的父类是否声明了这个方法,遵循 从下往上 找的顺序,找到了就停止,一直到根父类都没有找到,就会报编译错误。

所以继承意味着子类的对象除了看子类的类模板还要看父类的类模板。
在这里插入图片描述
2、子类不能直接访问父类中私有的(private)的成员变量和方法

子类虽会继承父类私有(private)的成员变量,但子类不能对继承的私有成员变量直接进行访问,可通过继承的 get/set 方法进行访问。如下图所示:
在这里插入图片描述
3、在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”

子类在继承父类以后,还可以定义自己特有的方法,这就可以看做是对父类功能上的扩展。

4、Java支持多层继承(继承体系)
在这里插入图片描述

class A{}
class B extends A{}
class C extends B{}
  • 1
  • 2
  • 3

说明:子类和父类是一种相对的概念。顶层父类是 Object 类。所有的类默认继承 Object,作为父类。

5、一个父类可以同时拥有多个子类

class A{}
class B extends A{}
class D extends A{}
class E extends A{}
  • 1
  • 2
  • 3
  • 4

6、Java只支持单继承,不支持多重继承
在这里插入图片描述
在这里插入图片描述

public class A{}
class B extends A{}

//一个类只能有一个父类,不可以有多个直接父类。
class C extends B{} 	//ok
class C extends A,B...	//error
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.5 练习

练习1: 定义一个学生类 Student,它继承自 Person 类
在这里插入图片描述
练习2:

(1) 定义一个 ManKind 类,包括

  • 成员变量 int sex 和 int salary;
  • 方法 void manOrWoman():根据 sex 的值显示 man(sex==1) 或者 woman(sex==0)
  • 方法 void employeed():根据 salary 的值显示 no job(salary==0) 或者 job(salary!=0)

(2) 定义类 Kids 继承 ManKind,并包括

  • 成员变量 int yearsOld;
  • 方法 printAge() 打印 yearsOld 的值。

(3) 定义类 KidsTest,在类的 main 方法中实例化 Kids 的对象 someKid,用该对象访问其父类的成员变量及方法。

练习3: 根据下图实现类。在 CylinderTest 类中创建 Cylinder 类的对象,设置圆柱的底面半径和高,并输出圆柱的体积。
在这里插入图片描述

三、方法的重写(override/overwrite)

父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于自己当前的类,该怎么办呢?子类可以对从父类中继承来的方法进行改造,我们称为方法的 重写 (override、overwrite)。也称为方法的 重置覆盖。在程序执行时,子类的方法将覆盖父类的方法。

3.1 方法重写举例

比如新的手机增加来电显示头像的功能,代码如下:

public class Phone {
    public void sendMessage(){
        System.out.println("发短信");
    }
    public void call(){
        System.out.println("打电话");
    }
    public void showNum(){
        System.out.println("来电显示号码");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
//SmartPhone:智能手机
public class SmartPhone extends Phone{
    //重写父类的来电显示功能的方法
	@Override
    public void showNum(){
        //来电显示姓名和图片功能
        System.out.println("显示来电姓名");
        System.out.println("显示头像");
    }
    //重写父类的通话功能的方法
    @Override
    public void call() {
        System.out.println("语音通话 或 视频通话");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
public class TestOverride {
    public static void main(String[] args) {
        // 创建子类对象
        SmartPhone sp = new SmartPhone();

        // 调用父类继承而来的方法
        sp.call();

        // 调用子类重写的方法
        sp.showNum();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

@Override 使用说明:写在方法上面,用来检测是不是满足重写方法的要求。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留,这样编译器可以帮助我们检查格式,另外也可以让阅读源代码的程序员清晰的知道这是一个重写的方法。

3.2 方法重写的要求

子类重写的方法 必须 和父类被重写的方法具有相同的 方法名称参数列表。子类重写的方法的返回值类型 不能大于 父类被重写的方法的返回值类型。(例如:Student < Person)。

注意:如果返回值类型是基本数据类型和 void,那么必须是相同

子类重写的方法使用的访问权限 不能小于 父类被重写的方法的访问权限。(public > protected > 缺省 > private)

注意:① 父类私有方法不能重写 ② 跨包的父类缺省的方法也不能重写

子类方法抛出的异常不能大于父类被重写方法的异常。此外,子类与父类中同名同参数的方法必须同时声明为非 static 的(即为重写),或者同时声明为 static 的(不是重写)。因为 static 方法是属于类的,子类无法覆盖父类的方法。

3.3 小结:方法的重载与重写

方法的重载:方法名相同,形参列表不同。不看返回值类型。方法的重写:见上面。(1) 同一个类中:

public class TestOverload {
    public int max(int a, int b){
        return a > b ? a : b;
    }
    public double max(double a, double b){
        return a > b ? a : b;
    }
    public int max(int a, int b,int c){
        return max(max(a,b),c);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

(2) 父子类中

package com.atguigu.inherited.method;

public class TestOverloadOverride {
    public static void main(String[] args) {
        Son s = new Son();
        s.method(1);//只有一个形式的method方法

        Daughter d = new Daughter();
        d.method(1);
        d.method(1,2);//有两个形式的method方法
    }
}

class Father{
    public void method(int i){
        System.out.println("Father.method");
    }
}
class Son extends Father{
    public void method(int i){//重写
        System.out.println("Son.method");
    }
}
class Daughter extends Father{
    public void method(int i,int j){//重载
        System.out.println("Daughter.method");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

3.4 练习

练习1: 如果现在父类的一个方法定义成 private 访问权限,在子类中将此方法声明为 default 访问权限,那么这样还叫重写吗?(NO)

练习2: 修改继承内容的练习2中定义的类 Kids,在 Kids 中重新定义 employeed() 方法,覆盖父类 ManKind 中定义的 employeed() 方法,输出 Kids should study and no job.

四、再谈封装性中的4种权限修饰

权限修饰符:public,protected,缺省,private

修饰符本类本包其他包子类其他包非子类
private×××
缺省√(本包子类非子类都可见)××
protected√(本包子类非子类都可见)√(其他包仅限于子类中可见)×
public

外部类:public 和缺省。成员变量、成员方法等:public,protected,缺省,private

1、外部类要跨包使用必须是public,否则仅限于本包使用

(1) 外部类的权限修饰符如果缺省,本包使用没问题
在这里插入图片描述
(2) 外部类的权限修饰符如果缺省,跨包使用有问题
在这里插入图片描述
2、成员的权限修饰符问题

(1) 本包下使用:成员的权限修饰符可以是 public、protected、缺省
在这里插入图片描述
(2) 跨包下使用:要求严格
在这里插入图片描述
(3) 跨包使用时,如果类的权限修饰符缺省,成员权限修饰符>类的权限修饰符也没有意义
在这里插入图片描述

五、关键字:super

5.1 super的理解

在 Java 类中使用 super 来调用父类中的指定操作:

  • super 可用于访问父类中定义的属性
  • super 可用于调用父类中定义的成员方法
  • super 可用于在子类构造器中调用父类的构造器

注意:

  • 尤其当子父类出现同名成员时,可以用 super 表明调用的是父类中的成员
  • super 的追溯不仅限于直接父类
  • super 和 this 的用法相像,this 代表本类对象的引用,super 代表父类的内存空间的标识

5.2 super的使用场景

5.2.1 子类中调用父类被重写的方法

如果子类没有重写父类的方法,只要权限修饰符允许,在子类中完全可以直接调用父类的方法;如果子类重写了父类的方法,在子类中需要通过 super. 才能调用父类被重写的方法,否则默认调用的子类重写的方法。举例:

public class Phone {
    public void sendMessage(){
        System.out.println("发短信");
    }
    public void call(){
        System.out.println("打电话");
    }
    public void showNum(){
        System.out.println("来电显示号码");
    }
}

//smartphone:智能手机
public class SmartPhone extends Phone{
    //重写父类的来电显示功能的方法
    public void showNum(){
        //来电显示姓名和图片功能
        System.out.println("显示来电姓名");
        System.out.println("显示头像");

        //保留父类来电显示号码的功能
        super.showNum();//此处必须加super.,否则就是无限递归,那么就会栈内存溢出
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

总结:

  1. 方法前面没有super.和this: 先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯。
  2. 方法前面有this: 先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
  3. 方法前面有super。 当前子类的直接父类找,如果没有,继续往上追溯

5.2.2 子类中调用父类中同名的成员变量

  • 如果实例变量与局部变量重名,可以在实例变量前面加 this. 进行区别
  • 如果子类实例变量和父类实例变量重名,并且父类的该实例变量在子类仍然可见,在子类中要访问父类声明的实例变量需要在父类实例变量前加 super.,否则默认访问的是子类自己声明的实例变量
  • 如果父子类实例变量没有重名,只要权限修饰符允许,在子类中完全可以直接访问父类中声明的实例变量,也可以用 this. 实例访问,也可以用 super. 实例变量访问

举例:

class Father{
	int a = 10;
	int b = 11;
}
class Son extends Father{
	int a = 20;
    
    public void test(){
		//子类与父类的属性同名,子类对象中就有两个a
		System.out.println("子类的a:" + a);//20  先找局部变量找,没有再从本类成员变量找
        System.out.println("子类的a:" + this.a);//20   先从本类成员变量找
        System.out.println("父类的a:" + super.a);//10    直接从父类成员变量找
		
		//子类与父类的属性不同名,是同一个b
		System.out.println("b = " + b);//11  先找局部变量找,没有再从本类成员变量找,没有再从父类找
		System.out.println("b = " + this.b);//11   先从本类成员变量找,没有再从父类找
		System.out.println("b = " + super.b);//11  直接从父类局部变量找
	}
	
	public void method(int a, int b){
		//子类与父类的属性同名,子类对象中就有两个成员变量a,此时方法中还有一个局部变量a		
		System.out.println("局部变量的a:" + a);//30  先找局部变量
        System.out.println("子类的a:" + this.a);//20  先从本类成员变量找
        System.out.println("父类的a:" + super.a);//10  直接从父类成员变量找

        System.out.println("b = " + b);//13  先找局部变量
		System.out.println("b = " + this.b);//11  先从本类成员变量找
		System.out.println("b = " + super.b);//11  直接从父类局部变量找
    }
}
class Test{
    public static void main(String[] args){
        Son son = new Son();
		son.test();
		son.method(30,13);  
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

总结:起点不同(就近原则)

  • 变量前面没有 super.this.

    • 在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的 局部变量
    • 如果不是局部变量,先从当前执行代码的 本类去找成员变量
    • 如果从当前执行代码的本类中没有找到,会往上找 父类声明的成员变量(权限修饰符允许在子类中访问的)
  • 变量前面有 this.

    • 通过 this 找成员变量时,先从当前执行代码的 本类去找成员变量
    • 如果从当前执行代码的本类中没有找到,会往上找 父类声明的成员变量( 权限修饰符允许在子类中访问的)
  • 变量前面 super.

    • 通过 super 找成员变量,直接从当前执行代码的直接父类去找成员变量(权限修饰符允许在子类中访问的)
    • 如果直接父类没有,就去父类的父类中找(权限修饰符允许在子类中访问的)

特别说明:应该避免子类声明和父类重名的成员变量 在阿里的开发规范等文档中都做出明确说明:
在这里插入图片描述

5.2.3 子类构造器中调用父类构造器

① 子类继承父类时,不会继承父类的构造器。只能通过 super(形参列表) 的方式调用父类指定的构造器。

② 规定:super(形参列表), 必须声明在构造器的首行。

③ 我们前面讲过,在构造器的首行可以使用 this(形参列表), 调用本类中重载的构造器,结合②,结论:在构造器的首行,this(形参列表)super(形参列表) 只能二选一。

④ 如果在子类构造器的首行既没有显示调用 this(形参列表), 也没有显式调用 super(形参列表), 则子类此构造器默认调用 super(), 即调用父类中空参的构造器。

⑤ 由③和④得到结论:子类的任何一个构造器中,要么会调用本类中重载的构造器,要么会调用父类的构造器。只能是这两种情况之一。

⑥ 由⑤得到:一个类中声明有n个构造器,最多有 n-1 个构造器中使用了 this(形参列表), 则剩下的那个一定使用 super(形参列表)。

开发中常见错误:如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有空参的构造器,则 编译出错。情景举例1:

class A{

}
class B extends A{

}

class Test{
    public static void main(String[] args){
        B b = new B();
        //A类和B类都是默认有一个无参构造,B类的默认无参构造中还会默认调用A类的默认无参构造
        //但是因为都是默认的,没有打印语句,看不出来
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

情景举例2:

class A{
	A(){
		System.out.println("A类无参构造器");
	}
}
class B extends A{

}
class Test{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个无参构造,
		//B类默认有一个无参构造,
		//B类的默认无参构造中会默认调用A类的无参构造
        //可以看到会输出“A类无参构造器"
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

情景举例3:

class A{
	A(){
		System.out.println("A类无参构造器");
	}
}
class B extends A{
	B(){
		System.out.println("B类无参构造器");
	}
}
class Test{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个无参构造,
		//B类显示声明一个无参构造,        
		//B类的无参构造中虽然没有写super(),但是仍然会默认调用A类的无参构造
        //可以看到会输出“A类无参构造器"和"B类无参构造器")
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

情景举例4:

class A{
	A(){
		System.out.println("A类无参构造器");
	}
}
class B extends A{
	B(){
        super();
		System.out.println("B类无参构造器");
	}
}
class Test{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个无参构造,
		//B类显示声明一个无参构造,        
		//B类的无参构造中明确写了super(),表示调用A类的无参构造
        //可以看到会输出“A类无参构造器"和"B类无参构造器")
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

情景举例5:

class A{
	A(int a){
		System.out.println("A类有参构造器");
	}
}
class B extends A{
	B(){
		System.out.println("B类无参构造器");
	}
}
class Test05{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
		//B类显示声明一个无参构造,        
		//B类的无参构造没有写super(...),表示默认调用A类的无参构造
        //编译报错,因为A类没有无参构造
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

情景举例6:

class A{
	A(int a){
		System.out.println("A类有参构造器");
	}
}
class B extends A{
	B(){
		super();
		System.out.println("B类无参构造器");
	}
}
class Test06{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
		//B类显示声明一个无参构造,        
		//B类的无参构造明确写super(),表示调用A类的无参构造
        //编译报错,因为A类没有无参构造
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

在这里插入图片描述
情景举例7:

class A{
	A(int a){
		System.out.println("A类有参构造器");
	}
}
class B extends A{
	B(int a){
		super(a);
		System.out.println("B类有参构造器");
	}
}
class Test07{
    public static void main(String[] args){
        B b = new B(10);
        //A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
		//B类显示声明一个有参构造,        
		//B类的有参构造明确写super(a),表示调用A类的有参构造
        //会打印“A类有参构造器"和"B类有参构造器"
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

情景举例8:

class A{
    A(){
        System.out.println("A类无参构造器");
    }
	A(int a){
		System.out.println("A类有参构造器");
	}
}
class B extends A{
    B(){
        super();//可以省略,调用父类的无参构造
        System.out.println("B类无参构造器");
    }
	B(int a){
		super(a);//调用父类有参构造
		System.out.println("B类有参构造器");
	}
}
class Test8{
    public static void main(String[] args){
        B b1 = new B();
        B b2 = new B(10);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

5.3 小结:this与super

1、this和super的意义

this:当前对象

  • 在构造器和非静态代码块中,表示正在 new 的对象
  • 在实例方法中,表示调用当前方法的对象

super:引用父类声明的成员

2、this和super的使用格式

this

  • this.成员变量:表示当前对象的某个成员变量,而不是局部变量
  • this.成员方法:表示当前对象的某个成员方法,完全可以省略 this.
  • this() 或 this(实参列表):调用另一个构造器协助当前对象的实例化,只能在构造器首行,只会找本类的构造器,找不到就报错

super

  • super.成员变量:表示当前对象的某个成员变量,该成员变量在父类中声明的
  • super.成员方法:表示当前对象的某个成员方法,该成员方法在父类中声明的
  • super() 或 super(实参列表):调用父类的构造器协助当前对象的实例化,只能在构造器首行,只会找直接父类的对应构造器,找不到就报错

5.4 练习

练习1: 修改方法重写的练习2中定义的类 Kids 中 employeed() 方法,在该方法中调用父类 ManKind 的 employeed() 方法,然后再输出 but Kids should study and no job.

练习2: 修改继承中的练习3中定义的 Cylinder 类,在 Cylinder 类中覆盖 findArea() 方法,计算圆柱的表面积。考虑:findVolume 方法怎样做相应的修改?在 CylinderTest 类中创建 Cylinder 类的对象,设置圆柱的底面半径和高,并输出圆柱的表面积和体积。附加题: 在 CylinderTest 类中创建一个 Circle 类的对象,设置圆的半径,计算输出圆的面积。体会父类和子类成员的分别调用。

六、子类对象实例化全过程

在这里插入图片描述

Dog dog = new Dog("小花","小红");
  • 1

在这里插入图片描述
在这里插入图片描述
举例:

class Creature {
    public Creature() {
        System.out.println("Creature无参数的构造器");
	}
}
class Animal extends Creature {
    public Animal(String name) {
        System.out.println("Animal带一个参数的构造器,该动物的name为" + name);
    }
    public Animal(String name, int age) {
        this(name);
        System.out.println("Animal带两个参数的构造器,其age为" + age);
	}
}
public class Dog extends Animal {
    public Dog() {
        super("汪汪队阿奇", 3);
        System.out.println("Dog无参数的构造器");
    }
    public static void main(String[] args) {
        new Dog();
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

七、面向对象特征三:多态性

一千个读者眼中有一千个哈姆雷特。

7.1 多态的形式和体现

7.1.1 对象的多态性

多态性,是面向对象中最重要的概念,在Java中的体现:对象的多态性:父类的引用指向子类的对象

格式:(父类类型:指子类继承的父类类型,或者实现的接口类型)

父类类型 变量名 = 子类对象;
  • 1

举例:

Person p = new Student();

Object o = new Person();//Object类型的变量o,指向Person类型的对象

o = new Student(); //Object类型的变量o,指向Student类型的对象
  • 1
  • 2
  • 3
  • 4
  • 5

对象的多态:在Java中,子类的对象可以替代父类的对象使用。所以,一个引用类型变量可能指向(引用)多种不同类型的对象

7.1.2 多态的理解

Java 引用变量有两个类型:编译时类型运行时类型。编译时类型由 声明 该变量时使用的类型决定,运行时类型由 实际赋给该变量的对象 决定。简称:编译时,看左边;运行时,看右边。

  • 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
  • 多态情况下,看左边 看的是父类的引用(父类中不具备子类特有的方法)。看右边 看的是子类的对象(实际运行的是子类重写父类的方法)。多态的使用前提:① 类的继承关系 ② 方法的重写

7.1.3 举例

public class Pet {
    private String nickname; //昵称

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public void eat(){
        System.out.println(nickname + "吃东西");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
public class Cat extends Pet {
    //子类重写父类的方法
    @Override
    public void eat() {
        System.out.println("猫咪" + getNickname() + "吃鱼仔");
    }

    //子类扩展的方法
    public void catchMouse() {
        System.out.println("抓老鼠");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
public class Dog extends Pet {
    //子类重写父类的方法
    @Override
    public void eat() {
        System.out.println("狗子" + getNickname() + "啃骨头");
    }

    //子类扩展的方法
    public void watchHouse() {
        System.out.println("看家");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

1、方法内局部变量的赋值体现多态

public class TestPet {
    public static void main(String[] args) {
        //多态引用
        Pet pet = new Dog();
        pet.setNickname("小白");

        //多态的表现形式
        /*
        编译时看父类:只能调用父类声明的方法,不能调用子类扩展的方法;
        运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法体;
         */
        pet.eat();//运行时执行子类Dog重写的方法
//      pet.watchHouse();//不能调用Dog子类扩展的方法

        pet = new Cat();
        pet.setNickname("雪球");
        pet.eat();//运行时执行子类Cat重写的方法
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2、方法的形参声明体现多态

public class Person{
    private Pet pet;
    public void adopt(Pet pet) {//形参是父类类型,实参是子类对象
        this.pet = pet;
    }
    public void feed(){
        pet.eat();//pet实际引用的对象类型不同,执行的eat方法也不同
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
public class TestPerson {
    public static void main(String[] args) {
        Person person = new Person();

        Dog dog = new Dog();
        dog.setNickname("小白");
        person.adopt(dog);//实参是dog子类对象,形参是父类Pet类型
        person.feed();

        Cat cat = new Cat();
        cat.setNickname("雪球");
        person.adopt(cat);//实参是cat子类对象,形参是父类Pet类型
        person.feed();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

3、方法返回值类型体现多态

public class PetShop {
    //返回值类型是父类类型,实际返回的是子类对象
    public Pet sale(String type){
        switch (type){
            case "Dog":
                return new Dog();
            case "Cat":
                return new Cat();
        }
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
public class TestPetShop {
    public static void main(String[] args) {
        PetShop shop = new PetShop();

        Pet dog = shop.sale("Dog");
        dog.setNickname("小白");
        dog.eat();

        Pet cat = shop.sale("Cat");
        cat.setNickname("雪球");
        cat.eat();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

7.2 为什么需要多态性(polymorphism)?

开发中,有时我们在设计一个数组、或一个成员变量、或一个方法的形参、返回值类型时,无法确定它具体的类型,只能确定它是某个系列的类型。案例:

① 声明一个 Dog 类,包含 public void eat() 方法,输出 狗啃骨头
② 声明一个 Cat 类,包含 public void eat() 方法,输出 猫吃鱼仔
③ 声明一个 Person 类,功能如下:

  • 包含宠物属性
  • 包含领养宠物方法 public void adopt(宠物类型Pet)
  • 包含喂宠物吃东西的方法 public void feed(),实现为调用 宠物对象.eat() 方法
public class Dog {
    public void eat(){
        System.out.println("狗啃骨头");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
public class Cat {
    public void eat(){
        System.out.println("猫吃鱼仔");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
public class Person {
    private Dog dog;

    //adopt:领养
    public void adopt(Dog dog){
        this.dog = dog;
    }

    //feed:喂食
    public void feed(){
        if(dog != null){
            dog.eat();
        }
    }
    /*
    问题:
    1、从养狗切换到养猫怎么办?   
    	修改代码把Dog修改为养猫?
    2、或者有的人养狗,有的人养猫怎么办?  
    3、要是还有更多其他宠物类型怎么办?
    如果Java不支持多态,那么上面的问题将会非常麻烦,代码维护起来很难,扩展性很差。
    */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

7.3 多态的好处和弊端

好处:变量引用的子类对象不同,执行的方法就不同,实现动态绑定。代码编写更灵活、功能更强大,可维护性和扩展性更好了。

弊端:一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法。

Student m = new Student();
m.school = "pku"; 	//合法,Student类有school成员变量
Person e = new Student(); 
e.school = "pku";	//非法,Person类没有school成员变量

// 属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编译错误。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

开发中:使用父类做方法的形参,是多态使用最多的场合。即使增加了新的子类,方法也无需改变,提高了扩展性,符合开闭原则。

【开闭原则OCP】

  • 对扩展开放,对修改关闭
  • 通俗解释:软件系统中的各种组件,如模块(Modules)、类(Classes)以及功能(Functions)等,应该在不修改现有代码的基础上,引入新功能

7.4 虚方法调用(Virtual Method Invocation)

在 Java 中虚方法是指在编译阶段不能确定方法的调用入口地址,在运行阶段才能确定的方法,即可能被重写的方法。

Person e = new Student();
e.getInfo();	//调用Student类的getInfo()方法
  • 1
  • 2

子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。举例:
在这里插入图片描述
前提:Person 类中定义了 welcome() 方法,各个子类重写了 welcome()。
在这里插入图片描述
执行:多态的情况下,调用对象的 welcome() 方法,实际执行的是子类重写的方法。拓展:

静态链接(或早起绑定):当一个字节码文件被装载进 JVM 内部时,如果被调用的目标方法在编译期可知,且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。那么调用这样的方法,就称为非虚方法调用。比如调用静态方法、私有方法、final方法、父类构造器、本类重载构造器等。

动态链接(或晚期绑定):如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接。调用这样的方法,就称为虚方法调用。比如调用重写的方法(针对父类)、实现的方法(针对接口)。

7.5 成员变量没有多态性

若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量

public class TestVariable {
    public static void main(String[] args) {
        Base b = new Sub();
        System.out.println(b.a);
        System.out.println(((Sub)b).a);

        Sub s = new Sub();
        System.out.println(s.a);
        System.out.println(((Base)s).a);
    }
}
class Base{
    int a = 1;
}
class Sub extends Base{
    int a = 2;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

7.6 向上转型与向下转型

首先,一个对象在 new 的时候创建是哪个类型的对象,它从头至尾都不会变。即这个对象的运行时类型,本质的类型用于不会变。但是,把这个对象赋值给不同类型的变量时,这些变量的编译时类型却不同。

7.6.1 为什么要类型转换

因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在 编译期间,就会出现类型转换的现象。但是,使用父类变量接收了子类对象之后,我们就 不能调用 子类拥有,而父类没有的方法了。这也是多态给我们带来的一点 小麻烦 所以,想要调用子类特有的方法,必须做类型转换,使得 编译通过
在这里插入图片描述
向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型

  • 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
  • 但是,运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体。
  • 此时,一定是安全的,而且也是自动完成的

向下转型:当左边的变量的类型(子类) < 右边对象/变量的编译时类型(父类),我们就称为向下转型

  • 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了
  • 但是,运行时,仍然是对象本身的类型
  • 不是所有通过编译的向下转型都是正确的,可能会发生 ClassCastException,为了安全,可以通过 isInstanceof 关键字进行判断

7.6.2 如何向上或向下转型

向上转型:自动完成
向下转型:(子类类型)父类变量

public class ClassCastTest {
    public static void main(String[] args) {
        //没有类型转换
        Dog dog = new Dog();//dog的编译时类型和运行时类型都是Dog

        //向上转型
        Pet pet = new Dog();//pet的编译时类型是Pet,运行时类型是Dog
        pet.setNickname("小白");
        pet.eat();//可以调用父类Pet有声明的方法eat,但执行的是子类重写的eat方法体
//        pet.watchHouse();//不能调用父类没有的方法watchHouse

        Dog d = (Dog) pet;
        System.out.println("d.nickname = " + d.getNickname());
        d.eat();//可以调用eat方法
        d.watchHouse();//可以调用子类扩展的方法watchHouse

        Cat c = (Cat) pet;//编译通过,因为从语法检查来说,pet的编译时类型是Pet,Cat是Pet的子类,所以向下转型语法正确
        //这句代码运行报错ClassCastException,因为pet变量的运行时类型是Dog,Dog和Cat之间是没有继承关系的
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

7.6.3 instanceof关键字

为了避免 ClassCastException 的发生,Java 提供了 instanceof 关键字,给引用变量做类型的校验。如下代码格式:

//检验对象a是否是数据类型A的对象,返回值为boolean型
对象a instanceof 数据类型A 
  • 1
  • 2

说明:

  • 只要用 instanceof 判断返回 true 的,那么强转为该类型就一定是安全的,不会报 ClassCastException 异常。
  • 如果对象 a 属于类 A 的子类 B,a instanceof A 值也为 true。
  • 要求对象 a 所属的类与类 A 必须是子类和父类的关系,否则编译错误。

代码:

public class TestInstanceof {
    public static void main(String[] args) {
        Pet[] pets = new Pet[2];
        pets[0] = new Dog();//多态引用
        pets[0].setNickname("小白");
        pets[1] = new Cat();//多态引用
        pets[1].setNickname("雪球");

        for (int i = 0; i < pets.length; i++) {
            pets[i].eat();

            if(pets[i] instanceof Dog){
                Dog dog = (Dog) pets[i];
                dog.watchHouse();
            }else if(pets[i] instanceof Cat){
                Cat cat = (Cat) pets[i];
                cat.catchMouse();
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

7.7 练习

练习1:笔试&面试

题目1:继承成员变量和继承方法的区别

class Base {
    int count = 10;
    public void display() {
        System.out.println(this.count);
    }
}

class Sub extends Base {
    int count = 20;
    public void display() {
        System.out.println(this.count);
    }
}

public class FieldMethodTest {
    public static void main(String[] args){
        Sub s = new Sub();
        System.out.println(s.count);
        s.display();
        Base b = s;
        System.out.println(b == s);
        System.out.println(b.count);
        b.display();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

题目2:

//考查多态的笔试题目:
public class InterviewTest1 {

	public static void main(String[] args) {
		Base base = new Sub();
		base.add(1, 2, 3);

//		Sub s = (Sub)base;
//		s.add(1,2,3);
	}
}

class Base {
	public void add(int a, int... arr) {
		System.out.println("base");
	}
}

class Sub extends Base {

	public void add(int a, int[] arr) {
		System.out.println("sub_1");
	}

//	public void add(int a, int b, int c) {
//		System.out.println("sub_2");
//	}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

题目3:

//getXxx()和setXxx()声明在哪个类中,内部操作的属性就是哪个类里的。
public class InterviewTest2 {
	public static void main(String[] args) {
		Father f = new Father();
		Son s = new Son();
		System.out.println(f.getInfo());//atguigu
		System.out.println(s.getInfo());//尚硅谷
		s.test();//尚硅谷  atguigu
		System.out.println("-----------------");
		s.setInfo("大硅谷");
		System.out.println(f.getInfo());//atguigu
		System.out.println(s.getInfo());//大硅谷
		s.test();//大硅谷  atguigu
	}
}

class Father {
	private String info = "atguigu";

	public void setInfo(String info) {
		this.info = info;
	}

	public String getInfo() {
		return info;
	}
}

class Son extends Father {
	private String info = "尚硅谷";
	
	public void setInfo(String info) {
		this.info = info;
	}

	public String getInfo() {
		return info;
	}
	
	public void test() {
		System.out.println(this.getInfo());
		System.out.println(super.getInfo());
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

题目4:多态是编译时行为还是运行时行为?

//证明如下:
class Animal  {
	protected void eat() {
		System.out.println("animal eat food");
	}
}

class Cat  extends Animal  {
	protected void eat() {
		System.out.println("cat eat fish");
	}
}

class Dog  extends Animal  {
	public void eat() {
		System.out.println("Dog eat bone");
	}
}

class Sheep  extends Animal  {
	public void eat() {
		System.out.println("Sheep eat grass");
	}
}

public class InterviewTest {
	public static Animal  getInstance(int key) {
		switch (key) {
		case 0:
			return new Cat ();
		case 1:
			return new Dog ();
		default:
			return new Sheep ();
		}

	}

	public static void main(String[] args) {
		int key = new Random().nextInt(3);
		System.out.println(key);

		Animal  animal = getInstance(key);
		animal.eat(); 
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

练习2:

class Person {
	protected String name="person";
	protected int age=50;
	public String getInfo() {
	          return "Name: "+ name + "\n" +"age: "+ age;
	}
}
class Student extends Person {
	protected String school="pku";
	public String getInfo() {
  	          return  "Name: "+ name + "\nage: "+ age 
	          + "\nschool: "+ school;
	}	
}
class Graduate extends Student{
	public String major="IT";
	public String getInfo()
	{
		return  "Name: "+ name + "\nage: "+ age 
	          + "\nschool: "+ school+"\nmajor:"+major;
	}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

建立 InstanceTest 类,在类中定义方法 method(Person e);
在method 中:
(1) 根据 e 的类型调用相应类的 getInfo() 方法。
(2)根据 e 的类型执行:如果 e 为 Person 类的对象,输出:"a person" 如果 e 为 Student 类的对象,输出:"a student" "a person",如果 e 为 Graduate 类的对象,输出: "a graduated student" "a student" "a person"

练习3:定义三个类,父类 GeometricObject 代表几何形状,子类 Circle 代表圆形,MyRectangle 代表矩形。定义一个测试类 GeometricTest,编写 equalsArea 方法测试两个对象的面积是否相等(注意方法的参数类型,利用动态绑定技术),编写 displayGeometricObject 方法显示对象的面积(注意方法的参数类型,利用动态绑定技术)。
在这里插入图片描述

八、Object 类的使用

8.1 如何理解根父类

java.lang.Object 是类层次结构的根类,即所有其它类的父类。每个类都使用 Object 作为超类。
在这里插入图片描述
Object类型的变量与除Object以外的任意引用数据类型的对象都存在多态引用

method(Object obj){} //可以接收任何类作为其参数

Person o = new Person();  
method(o);
  • 1
  • 2
  • 3
  • 4

所有对象(包括数组)都实现这个类的方法。如果一个类没有特别指定父类,那么默认则继承自 Object 类。例如:

public class Person {
	...
}
//等价于:
public class Person extends Object {
	...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

8.2 Object类的方法

根据 JDK 源代码及 Object 类的 API 文档,Object 类当中包含的方法有11个。这里我们主要关注其中的6个:

8.2.1 (重点)equals()

= =: 基本类型比较值:只要两个变量的值相等,即为 true。

int a=5; 
if(a==6){}
  • 1
  • 2

引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,== 才返回true。

Person p1=new Person();  	    
Person p2=new Person();
if (p1==p2){}
  • 1
  • 2
  • 3

== 进行比较时,符号两边的 数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错。equals(): 所有类都继承了 Object,也就获得了 equals() 方法。还可以重写。只能比较引用类型,Object 类源码中 equals() 的作用与 == 相同:比较是否指向同一个对象。
在这里插入图片描述
格式:obj1.equals(obj2)。 特例:当用 equals() 方法进行比较时,对类 File、String、Date及包装类(Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对象;原因:在这些类中重写了 Object 类的 equals() 方法。当自定义使用 equals() 时,可以重写。用于比较两个对象的 内容 是否都相等,重写 equals() 方法的原则

  1. 对称性:如果 x.equals(y) 返回是 true,那么 y.equals(x) 也应该返回是 true。
  2. 自反性:x.equals(x) 必须返回是 true。
  3. 传递性:如果 x.equals(y) 返回是 true,而且 y.equals(z) 返回是 true,那么 z.equals(x) 也应该返回是 true。
  4. 一致性:如果 x.equals(y) 返回是 true,只要 x 和 y 内容一直不变,不管你重复 x.equals(y) 多少次,返回都是 true。
  5. 任何情况下,x.equals(null),永远返回是 false;x.equals(和x不同类型的对象) 永远返回是 false。

重写举例:

class User{
	private String host;
	private String username;
	private String password;
	public User(String host, String username, String password) {
		super();
		this.host = host;
		this.username = username;
		this.password = password;
	}
	public User() {
		super();
	}
	public String getHost() {
		return host;
	}
	public void setHost(String host) {
		this.host = host;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public String toString() {
		return "User [host=" + host + ", username=" + username + ", password=" + password + "]";
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		User other = (User) obj;
		if (host == null) {
			if (other.host != null)
				return false;
		} else if (!host.equals(other.host))
			return false;
		if (password == null) {
			if (other.password != null)
				return false;
		} else if (!password.equals(other.password))
			return false;
		if (username == null) {
			if (other.username != null)
				return false;
		} else if (!username.equals(other.username))
			return false;
		return true;
	}
	
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

面试题: == 和 equals 的区别

== 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
equals 的话,它是属于 java.lang.Object 类里面的方法,如果该方法没有被重写过默认也是 ==; 我们可以看到 String 等类的 equals 方法是被重写过的,而且 String 类在日常开发中用的比较多,久而久之,形成了 equals 是比较值的错误观点。具体要看自定义类里有没有重写 Object 的 equals 方法来判断。通常情况下,重写 equals 方法,会比较类中的相应属性是否都相等。

练习1:

int it = 65;
float fl = 65.0f;
System.out.println(6565.0f是否相等?” + (it == fl)); //

char ch1 = 'A'; char ch2 = 12;
System.out.println("65和'A'是否相等?" + (it == ch1));//
System.out.println("12和ch2是否相等?" + (12 == ch2));//

String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1和str2是否相等?"+ (str1 == str2));//

System.out.println("str1是否equals str2?"+(str1.equals(str2)));//

System.out.println(“hello” == new java.util.Date()); //
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

练习2: 编写 Order 类,有 int 型的 orderId,String 型的 orderName,相应的 getter() 和 setter() 方法,两个参数的构造器,重写父类的 equals() 方法:public boolean equals(Object obj),并判断测试类中创建的两个对象是否相等。

练习3: 请根据以下代码自行定义能满足需要的 MyDate 类,在 MyDate 类中覆盖 equals 方法,使其判断当两个 MyDate 类型对象的年月日都相同时,结果为 true,否则为 false。 public boolean equals(Object o)

public class EqualsTest {
    public static void main(String[] args) {
        MyDate m1 = new MyDate(14, 3, 1976);
        MyDate m2 = new MyDate(14, 3, 1976);
        if (m1 == m2) {
            System.out.println("m1==m2");
        } else {
            System.out.println("m1!=m2"); // m1 != m2
        }

        if (m1.equals(m2)) {
            System.out.println("m1 is equal to m2");// m1 is equal to m2
        } else {
            System.out.println("m1 is not equal to m2");
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

8.2.2 (重点)toString()

方法签名:public String toString()

① 默认情况下,toString() 返回的是对象的运行时类型 @ 对象的 hashCode 值的十六进制形式

② 在进行 String 与其它类型数据的连接操作时,自动调用 toString() 方法

Date now=new Date();
System.out.println("now="+now);  //相当于
System.out.println("now="+now.toString()); 
  • 1
  • 2
  • 3

③ 如果我们直接 System.out.println(对象),默认会自动调用这个对象的 toString()

因为 Java 的引用数据类型的变量中存储的实际上时对象的内存地址,但是 Java 对程序员隐藏内存地址信息,所以不能直接将内存地址显示出来,所以当你打印对象时,JVM 帮你调用了对象的 toString()。

④ 可以根据需要在用户自定义类型中重写 toString() 方法,如 String 类重写了 toString() 方法,返回字符串的值。

s1="hello";
System.out.println(s1);//相当于System.out.println(s1.toString());
  • 1
  • 2

例如自定义的 Person 类:

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

    @Override
    public String toString() {
        return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

练习:定义两个类,父类 GeometricObject 代表几何形状,子类 Circle 代表圆形。
在这里插入图片描述
在这里插入图片描述

8.2.3 clone()

//Object类的clone()的使用
public class CloneTest {
	public static void main(String[] args) {
		Animal a1 = new Animal("花花");
		try {
			Animal a2 = (Animal) a1.clone();
			System.out.println("原始对象:" + a1);
			a2.setName("毛毛");
			System.out.println("clone之后的对象:" + a2);
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
	}
}

class Animal implements Cloneable{
	private String name;

	public Animal() {
		super();
	}

	public Animal(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

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

	@Override
	public String toString() {
		return "Animal [name=" + name + "]";
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return super.clone();
	}
	
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

8.2.4 finalize()

当对象被回收时,系统自动调用该对象的 finalize() 方法。(不是垃圾回收器调用的,是本类对象调用的)。永远不要主动调用某个对象的 finalize 方法,应该交给垃圾回收机制调用。什么时候被回收:当某个对象没有任何引用时,JVM 就认为这个对象是垃圾对象,就会在之后不确定的时间使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用 finalize() 方法。 子类可以重写该方法,目的是在对象被清理之前执行必要的清理操作。比如,在方法内断开相关连接资源。如果重写该方法,让一个新的引用变量重新引用该对象,则会重新激活对象。在 JDK9 中此方法已经被 标记为过时 的。

public class FinalizeTest {
	public static void main(String[] args) {
		Person p = new Person("Peter", 12);
		System.out.println(p);
		p = null;//此时对象实体就是垃圾对象,等待被回收。但时间不确定。
		System.gc();//强制性释放空间
	}
}

class Person{
	private String name;
	private int age;

	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	//子类重写此方法,可在释放对象前进行某些操作
	@Override
	protected void finalize() throws Throwable {
		System.out.println("对象被释放--->" + this);
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

8.2.5 getClass()

public final Class<?> getClass():获取对象的运行时类型

因为 Java 有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致,因此如果需要查看这个变量实际指向的对象的类型,需要用 getClass() 方法

public static void main(String[] args) {
	Object obj = new Person();
	System.out.println(obj.getClass());//运行时类型
}
  • 1
  • 2
  • 3
  • 4

结果:

class com.amoxiang.java.Person
  • 1

8.2.6 hashCode()

public int hashCode():返回每个对象的 hash 值。(后续在集合框架章节重点讲解)

public static void main(String[] args) {
	System.out.println("AA".hashCode());//2080
    System.out.println("BB".hashCode());//2112
}
  • 1
  • 2
  • 3
  • 4

8.3 native关键字的理解

使用 native 关键字说明这个方法是原生函数,也就是这个方法是用 C/C++ 等非 Java 语言实现的,并且 被编译成了DLL,由 Java 去调用。本地方法是有方法体的,用C语言编写。由于本地方法的方法体源码没有对我们开源,所以我们看不到方法体,在 Java 中定义一个 native 方法时,并不提供实现体。

1. 为什么要用native方法

Java 使用起来非常方便,然而有些层次的任务用 java 实现起来不容易,或者我们对程序的效率很在意时,例如:Java 需要与一些底层操作系统或某些硬件交换信息时的情况。native 方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解 Java 应用之外的繁琐的细节。

2. native声明的方法,对于调用者,可以当做和其他Java方法一样使用

native method 的存在并不会对其他类调用这些本地方法产生任何影响,实际上调用这些方法的其他类甚至不知道它所调用的是一个本地方法。 JVM 将控制调用本地方法的所有细节。

至此今天的学习就到此结束了,笔者在这里声明,笔者写文章只是为了学习交流,以及让更多学习Java语言的读者少走一些弯路,节省时间,并不用做其他用途,如有侵权,联系博主删除即可。感谢您阅读本篇博文,希望本文能成为您编程路上的领航者。祝您阅读愉快!


在这里插入图片描述

    好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
    如果我的博客对你有帮助、如果你喜欢我的博客内容,请 点赞评论收藏 一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
 编码不易,大家的支持就是我坚持下去的动力。点赞后不要忘了 关注 我哦!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/167943
推荐阅读
相关标签
  

闽ICP备14008679号