面向对象

面向对象

类和对象

客观存在的事物皆为对象,所以我们也常常说万物皆对象.

    • 类的理解
      • 类是对现实生活中一类具有共同属性和行为的事物的抽象
      • 类是对象的数据类型,类是具有相同属性和行为的一组对象的集合
      • 简单理解:类就是对现实事物的一种描述
    • 类的组成
      • 属性:指事物的特征,例如:手机事物(品牌,价格,尺寸)
      • 行为:指事物能执行的操作,例如:手机事物(打电话,发短信)
  • 类和对象的关系
    • 类:类是对现实生活中一类具有共同属性和行为的事物的抽象
    • 对象:是能够看得到摸得着的真实存在的实体
    • 简单理解:类是对事物的一种描述,对象则为具体存在的事物

类的定义

类的组成是由属性和行为两部分组成

  • 属性:在类中通过成员变量来体现(类中方法外的变量)
  • 行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)

类定义的步骤

  1. 定义类
  2. 编写类的成员变量
  3. 编写类的成员方法
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 类名{
//成员变量
变量1的数据类型 变量1;
变量2的数据类型 变量2;

//成员方法
方法1;
方法2;
}


public class phone{
//成员变量
private String brand;
private int price;

//成员方法
public void call(){
System.out.println("打电话");
}

public void sendMessage(){
System.out.println("发短信");
}
}

对象的使用

  • 创建对象的格式

    类名 对象名 = new 类名();

  • 调用成员的格式

    • 对象名.成员变量
    • 对象名.成员方法()
  • 示例代码

    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
    /*
    创建对象
    格式:类名 对象名 = new 类名();
    范例:Phone p = new Phone();

    使用对象
    1:使用成员变量
    格式:对象名.变量名
    范例:p.brand
    2:使用成员方法
    格式:对象名.方法名()
    范例:p.call()
    */
    public class PhoneDemo {
    public static void main(String[] args) {
    //创建对象
    Phone p = new Phone();

    //使用成员变量
    System.out.println(p.brand);
    System.out.println(p.price);

    p.brand = "小米";
    p.price = 2999;

    System.out.println(p.brand);
    System.out.println(p.price);

    //使用成员方法
    p.call();
    p.sendMessage();
    }
    }

对象内存图

单个对象内存图

Student s = new Student();

  1. 加载class文件
  2. 申名局部变量
  3. 在堆内存中开辟一个空间
  4. 默认初始化
  5. 显示初始化
  6. 构造方法初始化

image-20221128222919320

上述图片执行流程为:

  1. 方法区存储StudentTest.class,虚拟机去执行StudentTest中的main方法

  2. 由于运行main方法,所以在栈内存中开辟一块main方法的内存区域,开始执行main方法中的代码

  3. Student s = new Student(),表示在main方法内存中创建一个变量Student s,因为Student是一个类,所以也会存储到方法区内,又因为使用new关键字创建对象,会在堆内存中开辟一个空间来存储Student对象

    1. 在new 一个Student对象时,会进行成员变量的默认初始化(就是成员变量的默认值,比如 int为0,String为null)

    2. 如果在Student对象中的成员变量设置了默认值(private String name=”小明”),这个就是显示初始化

    3. 如果在new Student时传入了参数(new Student(“小红”,18)),这就是构造方法初始化。

    4. 还会存储方法区内Student.class中成员方法的地址(这里指的是study方法)

  4. 当在对堆内存的对象初始化完成后,会将堆内存的地址值赋值给存在于栈内存中的s局部变量

  5. System.out.println(s);控制台打印输出的就是Student对象中的堆内存地址001

  6. System.out.println(s.name+”……”+s.age);控制台输出的就是默认初始化的值,因为在创建Student对象时,没有经历过显示初始化和构造方法初始化

  7. s.name=”阿强”、s.age=23;,再去打印输出,就是阿强…23。通过s.name找到堆内存中Student对象并给该对象的name赋值为阿强,通过s.age找到堆内存中Student对象并给该对象的age赋值为23

  8. s.study();这里再次调用一个方法,所以study方法会进入栈内存中。在该方法中只是System.out.println(“好好学习”);在控制台打印好好学习

  9. 执行完study方法后出栈,接着main方法出栈

  10. 最后student没有方法去使用,也会当做垃圾被回收。

多个对象内存图

image-20221128225351702

具体流程就是重复单个的对象的创建内存图(这个new 的相同的对象,但是对象在堆内存的地址是不同的)

总结:多个对象在堆内存中,都有不同的内存划分,成员变量存储在各自的内存区域,成员方法对个对象公用一份。

两个引用指向同一个对象

image-20221128225802704

注意:如果将通过Student stu2=stu1,这样是将new的stu1的堆内存中的地址值赋值给stu2,所以stu1和stu2是指向同一个堆内存的Student的内存空间,那么修改stu1的值,stu2再去读取,也就会读到stu1修改后的值。

成员变量和局部变量

区别

  • 类中的位置不同

    成员变量(类中方法外),局部变量(方法内部或方法声明上)

  • 内存中位置不同

    成员变量(堆内存),局部变量(栈内存)

  • 生命周期不同

    成员变量(随着对象的存在而存在,随着对象的消失而消失),局部变量(随着方法的调用而存在,随着方法的调用完毕而消失)

  • 初始化值不同

    成员变量(有默认初始化值),局部变量(没有默认初始化值,必须先定义,赋值才能使用)

封装

封装思想

  1. 封装概述

    是面向对象三大特征之一(封装、继承、多态)

    对象代表什么,就得封装对应的数据,并提供数据对应的行为

    就比如:人关门(门是通过人的力,导致门关闭的)

    这里人和门是分别两个对象,那么就对应的有Person类和Door类,在Door类中有一个属性为门的关闭状态,那么就应该提供该状态的行为,也就是关门的方法。

  2. 封装代码实现

    将类的某些信息隐藏在类的内部,不允许外部程序直接访问,而是通过类提供的方法来实现对隐藏信息的操作和访问

    成员变量private,提供对应的getXxx()/setXxx()方法

private关键字

private是一个修饰符,可以用来修饰成员(成员变量、成员方法)

  • 被private修饰的成员,只能在本类进行访问,针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作

    • 提供get变量名()方法,用于获取成员变量的值,方法用public修饰
    • 提供set变量名(参数)方法,用于设置成员变量的值,方法用public修饰
  • 示例

    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
    /*
    学生类
    */
    class Student {
    //成员变量
    String name;
    private int age;

    //提供get/set方法
    public void setAge(int a) {
    if(a<0 || a>120) {
    System.out.println("你给的年龄有误");
    } else {
    age = a;
    }
    }

    public int getAge() {
    return age;
    }

    //成员方法
    public void show() {
    System.out.println(name + "," + age);
    }
    }
    /*
    学生测试类
    */
    public class StudentDemo {
    public static void main(String[] args) {
    //创建对象
    Student s = new Student();
    //给成员变量赋值
    s.name = "林青霞";
    s.setAge(30);
    //调用show方法
    s.show();
    }
    }

this关键字

this修饰的变量用于指代成员变量,其主要作用是区分局部变量名和成员变量名重名问题

  • 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
  • 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Student {
private String name;
private int age;

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

public String getName() {
return name;
}

public void setAge(int age) {
this.age = age;
}

public int getAge() {
return age;
}

public void show() {
System.out.println(name + "," + age);
}
}

继承

java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起继承关系。

例如 public class Student extends Person{}中,Student称为子类(派生类),Person称为父类(基类或者超类)。

当类与类之间,存在相同(共性)的内容,并满足子类是父类中的一种,就可以考虑使用继承,来优化代码

这里要注意:一定是要满足子类是父类的一种,比如员工有id和name,商品也有id和name,这个存在相同的内容,但是不满足子类是父类的一种,不能把员工和商品抽取出一个共同的父类。

使用继承的好处

  • 可以把多个子类中重复的代码抽取到父类中,提高代码的复用性
  • 子类可以得到父类的属性和行为,子类可以使用,并且子类可以在父类的基础上,增加其他的功能,使子类更强大

继承的特点

  • java只支持单继承,不支持多继承,但支持多层继承

    • 为什么支持多继承(下列代码为假设java支持多继承,演示会出现的问题)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      public class 父类A{
      public void method(){
      System.out.println("复习数学")
      }
      }

      public class 父类B{
      public void method(){
      System.out.println("复习语文")
      }
      }

      public class 子类 extends 父类A,父类B{

      }

      public class Test{
      public static void main(String[] args){
      子类 z =new 子类();
      z.method(); //这里在子类调用方法时,不知道去加载那个父类的方法。
      }
      }
    • 多层继承:子类A继承父类B,父类B可以继承父类C

    • 每一个类都直接或者间接的继承与Object类

子类继承父类方法的内容

内容非私有私有
构造方法不能不能
成员变量
成员方法不能

构造方法是否可以被继承

类的构造方法名应该要和类名一致,所以父类的构造方法不能被子类继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//假设构造方法可以被继承,以下代码将没错

public class Fu{
String name;
int age;
public Fu(){}
public Fu(Sring name,int age){
this.name=name;
this.age =age;
}
}

public class Zi extends Fu{
//这里的代码会报错
public Fu(){}
public Fu(Sring name,int age){
this.name=name;
this.age =age;
}
}

//但是类的构造方法名应该要和类名一致,所以父类的构造方法不能被子类继承

成员变量是否可以被继承

image-20221205234201494

加载流程

  1. 将TestStudent.class字节码文件加载到方法区,jvm将自动把main方法加载到栈内存中
  2. 创建一个Zi对象的变量z
    1. 会加载Zi.class的字节码文件,这里会存储Zi类的成员变量name
    2. 在加载Zi.class后,发现继承与Fu,所以这边会加载Fu.class文件,在Fu中存储name和age
    3. new Zi(),会在堆内存中创建对象,该内存会分为两份,左边存储的是父类的成员变量,右边存储自己的成员变量
    4. 将堆内存的地址值赋值给z变量
  3. 打印z的地址值
  4. 把钢门吹雪赋值给z对象的name(查找顺序,先找右边的,没找到再去找左边的)
  5. 把23赋值给z对象的age,王者农药赋值给z对象的game
  6. 打印z对象的name、age、game
父类成员变量被private修饰

image-20221205235824146

看内存图可以发现,父类private修饰的成员变量是被子类继承了,存储在子类对象的内存中,但是由于是private修饰的,z.name在堆内存中的Zi对象找不到对应的name去赋值。

成员方法是否可以被继承

子类主要继承的是父类的虚方法(非private、非static、非final修饰的成员方法就是需虚方法)

image-20221206000041198

image-20221206000311308

加载顺序

  1. 加载TestStudent.class字节码文件到方法区,jvm调用该类的main方法
  2. 创建Zi类变量z
    1. 加载Zi.class
    2. 加载父类Fu.class
    3. 由于Fu没有继承父类,则会默认继承Object.class
    4. 加载Object.class.它有五个虚方法存储在虚方法表中
    5. Fu继承Object,会继承Object的5个虚方法表,加上自己的fuShow1,就有六个虚方法
    6. Zi继承Fu,继承六个虚方法,加上自己的ziShow(),总共7个虚方法
  3. new Zi(),在堆内存中创建内存区域,分为2份,右边的存自己的成员变量,左边的存父类的成员变量,把堆内存中的地址值赋值给变量z
  4. 打印z的地址值
  5. 调用z的ziShow方法和fuShow1方法打印出对应的值(去虚方法表中找对应的方法并找到了)
  6. 最后调用z的fuShow2方法报错,说明私有的成员方法不能被继承。(没在虚方法中找到对应的方法)

继承中,成员变量的访问特点

就近原则:谁离我近,我就用谁。先在局部位置找,本类成员位置找,父类成员位置找,逐级往上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test{
public static void main(String[] args){
Zi z = new Zi();
z.ziShow();
}
}

class Fu{
String name = "Zi";
}

class Zi extends Fu{
String name ="Zi";
public void ziShow(){
String name = "ziShow";
System.out.println(name); //ziShow,如果没有局部变量name打印的就是成员变量的name,如果没有局部和成员变量的name,就打印Fu类的name
System.out.println(this.name);//Zi,打印本类的成员变量name
System.out.println(super.name);//Fu 打印父类的成员变量name
}
}

继承中,成员方法的访问特点

直接调用满足就近原则:谁离我近,我就用谁,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
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
public class Test{
public static void main(String[] args){
OverseasStudent s = new OverseasStudent();
s.lunch();
}
}

class Person{
public void eat(){
System.out.println("吃米饭,吃菜");
}

public void drink(){
System.out.println("喝开水");
}
}


class Student extends Person{
public void lunch(){
//像这种方法的直接调用,在方法前面有隐藏的this,所以就是this.eat(),因为方法在调用的时候,需要调用者,像这种情况就会触发就近原则。先在本类中查看eat和drink方法,如果有就直接调用,如果没有,就会调用从父类中继承下来的eat和drink方法
eat(); //吃米饭,吃菜
drink();//喝开水

//直接调用父类的eat和drink方法
super.eat();//吃米饭,吃菜
super.drink();//喝开水
}
}

//留学生
class OverseasStudent extends Person{
@Override
public void eat(){
System.out.println("吃意大利面");
}
@Override
public void drink(){
System.out.println("喝凉水");
}

public void lunch(){
eat();//吃意大利面
drink();//喝凉水

super.eat();//吃米饭,吃菜
super.drink();//喝开水
}
}

继承中:构造方法的访问特点

  • 父类中的构造方法不会被子类继承

  • 子类中所有的构造方法默认先访问父类中的无参构造,在执行自己

    • 为什么(原因)

      • 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据

      • 子类初始化之前,一定要调用父类构造方法先完成父类数据空间的初始化

    • 怎么调用父类的构造方法

      • 子类构造方法的第一行语句默认都是super(),不写也存在,且必须在第一行
  • 如果想要方法访问父类有参构造,必须手动书写

this、super使用总结

  • this:理解为一个变量,表示当前方法调用者的地址值
  • super:代表父类存储空间
关键字访问成员变量访问成员方法访问构造方法
thisthis.成员变量
访问本类成员变量
this.成员方法(….)
访问本类成员方法
this(…)
访问本类构造方法
supersuper.成员变量
访问父类成员变量
super.成员方法(…)
访问父类成员方法
super(…)
访问父类构造方法

方法的重写

当父类的方法不能满足子类现在的需求时,需要进行方法重写。

书写格式

在继承体系中,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法时重写的方法。

@Override重写注解

  1. @Override是放在重写后的方法上,校验子类重写时语法是否正确
  2. 加上注解后如果有红色波浪线,表示语法错误
  3. 建议重写方法都加@Override注解,代码安全,优雅

方法重写的本质

image-20221207213606019

image-20221207213756709

方法重写注意事项和要求

  1. 重写方法的名称,形参列表必须与父类中的一致
  2. 子类重写父类方法时,访问权限子类必须大于父类(空着不写<protected<public)
  3. 子类重写方法时,返回值类型子类必须小于等于父类
  4. 重写的方法尽量和父类保持一致
  5. 私有方法不能被重写
  6. 子类不能重写父类的静态方法,如果重写会报错。

构造方法

构造方法概述

构造方法是一种特殊的方法

  • 作用

    创建对象 Student stu = new Student();

  • 格式

    public class 类名{

    ​ 修饰符 类(参数){

    ​ }

    }

  • 功能

    主要是完成对象数据的初始化

  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Student {
    private String name;
    private int age;

    //构造方法
    public Student() {
    System.out.println("无参构造方法");
    }

    public void show() {
    System.out.println(name + "," + age);
    }
    }
    /*
    测试类
    */
    public class StudentDemo {
    public static void main(String[] args) {
    //创建对象
    Student s = new Student();
    s.show();
    }
    }

构造方法注意事项

  • 构造方法的创建

    如果没有定义构造方法,系统将给出一个默认的无参构造方法

    如果定义了构造方法,系统将不在提供默认的构造方法

  • 构造方法的重载

    如果自定义了带参构造方法,还要使用无参构造方法,就必须在写一个无参构造方法

  • 推荐使用方式

    无论是否使用,都手写无参构造方法

  • 重要功能

    可以使用带参构造,为成员变量进行初始化

多态

多态的格式

1
2
父类类型  变量名  = new 子类/实现类构造器;
变量名.方法名();

多态的前提:有继承关系,子类对象是可以赋值给父类类型的变量。例如Animal是一个动物类型,而Cat是一个猫类型,Cat继承了Animal,Cat对象也是Animal类型,自然可以赋值给父类类型的变量。

多态的使用场景

如果没有多态,在下图中register方法只能传递学生对象,其他的Teacher和administrator对象是无法传递给register方法的,在这种情况下,只能定义三个不同的register方法分别接收学生、老师和管理员

多态的应用场景1

有了多态之后,方法的形参就可以定义为共同的父类Person。

注意点

  • 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象
  • 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象
  • 而且多态还可以根据传递的不同对象来调用不同类中的方法

代码示例

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
64
65
66
67
68
69
70
71
72
73
74
75
父类:
public class Person {
private String name;
private int age;

空参构造
带全部参数的构造
get和set方法

public void show(){
System.out.println(name + ", " + age);
}
}

子类1
public class Administrator extends Person {
@Override
public void show() {
System.out.println("管理员的信息为:" + getName() + ", " + getAge());
}
}

子类2
public class Student extends Person{

@Override
public void show() {
System.out.println("学生的信息为:" + getName() + ", " + getAge());
}
}

子类3
public class Teacher extends Person{

@Override
public void show() {
System.out.println("老师的信息为:" + getName() + ", " + getAge());
}
}

测试类:
public class Test {
public static void main(String[] args) {
//创建三个对象,并调用register方法

Student s = new Student();
s.setName("张三");
s.setAge(18);


Teacher t = new Teacher();
t.setName("王建国");
t.setAge(30);

Administrator admin = new Administrator();
admin.setName("管理员");
admin.setAge(35);



register(s);
register(t);
register(admin);


}



//这个方法既能接收老师,又能接收学生,还能接收管理员
//只能把参数写成这三个类型的父类
public static void register(Person p){
p.show();
}
}

多态的定义和前提

多态:是指同一行为,具有多个不同表现形式

前提

  1. 有继承或者实现关系
  2. 方法的重写(意义体现:不重写,无意义)
  3. 父类的引用指向子类对象(格式体现)

多态的运行特点

调用成员变量时:编译看左边,运行看左边

调用成员方法时:编译看左边,运行看右边

代码解析

1
2
3
4
5
6
7
Fu f= new Zi();
//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
//编译看左边的父类中有没show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();

理解:

​ Fu f= new Zi();现在是用f去调用变量和方法的,而f是Fu类型的,默认都会从Fu这个类去找。

  • 成员变量

    ​ 在子类的对象中,会把父类的成员变量也继承下来,比如父:name 子:name

  • 成员方法

    如果子类对方法进行了重写,那么在虚方法表中时会把父类的方法进行覆盖的。

多态调用成员的内存图解

image-20221211120454714

代码执行流程

  1. 将Test类加载到方法区,虚拟机将Test.class的main方法加载到栈内存中执行
  2. 定义一个Animal a变量,将Animal.class加载到方法区(有所有的成员变量和所有的成员方法,并在虚方法表中有一个show方法)
  3. new Dog(),将Dog.class加载到方法区,由于Dog是继承Animal的,所以会继承Animal的所有成员变量和虚方法表中的方法,发现Dog中重写的show方法,这里会把show方法覆盖,由Animal的show方法变为Dog的show方法。接着在堆内存中开辟一块内存空间,将这块内存分类两部分,左边的存储父类继承过来的成员变量name,值为动物,右边存储自己的成员变量name,值为狗,将堆内存中的Dog内存地址赋值给变量a
  4. sout(a.name),打印变量a.name,找到堆内存中存储的父类的成员变量name,值为动物并打印
  5. a.show,通过a的地址值找到堆内存中的信息,在找到方法区中的Dog.class的信息,因为该class中的show方法已经重写了,这里是子类的show方法,所以加载到栈内存执行show方法,打印的是Dog—–show方法

多态的弊端

​ 我们已经知道多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类的独有功能了

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
class Animal{
public void eat()
System.out.println("动物吃东西!")

}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}

public void catchMouse() {
System.out.println("抓老鼠");
}
}

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

class Test{
public static void main(String[] args){
Animal a = new Cat();
a.eat();
a.catchMouse();//编译报错,编译看左边,Animal没有这个方法
}
}

引用类型转换

为什么要转型

多态的写法就无法访问子类独有的功能了

回顾基本数据类型转换

  • 自动转换:范围小的赋值给范围大的,自动完成 double d =5;
  • 强制转换:范围大的赋值给范围小的,强制转换:int i = (int)3.14;

多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种

向上转型(自动转换)

  • 多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。当父类引用指向一个子类对象时,便是向上转型

    1
    2
    父类类型 变量名 = new 子类类型
    Animal a = new Cat();

向下转型(强制转换)

  • 父类类型向子类类型向下转换的过程,这个过程是去强制的。一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型

    使用格式:

    1
    2
    3
    子类类型 变量名 = (子类类型) 父类变量名;
    如:Aniaml a = new Cat();
    Cat c =(Cat) a;

转型的异常

转型的过程中,一不小心就会遇到这样的问题

1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat

// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}

这段代码可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。

instanceof关键字

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

1
2
3
变量名 instanceof 数据类型 
如果变量属于该数据类型或者其子类类型,返回true
如果变量不属于该数据类型或者其子类类型,返回false

所以,转换前,我们最好先做一个判断,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat

// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
}

instanceof新特性

JDK14的时候提出了新特性,把判断和强转合并成了一行

1
2
3
4
5
6
7
8
9
10
//新特性
//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是,则不强转,结果直接是false
if(a instanceof Dog d){
d.lookHome();
}else if(a instanceof Cat c){
c.catchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}

综合练习

根据需求完成代码:

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
1.定义狗类
属性:
年龄,颜色
行为:
eat(String something)(something表示吃的东西)
看家lookHome方法(无参数)
2.定义猫类
属性:
年龄,颜色
行为:
eat(String something)方法(something表示吃的东西)
逮老鼠catchMouse方法(无参数)

3.定义Person类//饲养员
属性:
姓名,年龄
行为:
keepPet(Dog dog,String something)方法
功能:喂养宠物狗,something表示喂养的东西
行为:
keepPet(Cat cat,String something)方法
功能:喂养宠物猫,something表示喂养的东西
生成空参有参构造,set和get方法
4.定义测试类(完成以下打印效果):
keepPet(Dog dog,String somethind)方法打印内容如下:
年龄为30岁的老王养了一只黑颜色的2岁的狗
2岁的黑颜色的狗两只前腿死死的抱住骨头猛吃
keepPet(Cat cat,String somethind)方法打印内容如下:
年龄为25岁的老李养了一只灰颜色的3岁的猫
3岁的灰颜色的猫眯着眼睛侧着头吃鱼
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package com.zhuixun.demo;

/**
* @Author: zhuixun
* @Date: 2022/12/11 14:21
* @Version 1.0 动物类
*/
public class Animal {
//ctrl+shift+u 小写转大写快捷键
private int age;
private String color;

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;
}

public Animal() {
}

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

public void eat(String something){
}
}

package com.zhuixun.demo;

/**
* @Author: zhuixun
* @Date: 2022/12/11 14:18
* @Version 1.0 饲养员类
*/
public class Person {
private String name;
private int age;

public Person() {
}

public Person(String name, int age) {
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;
}

public void keepPet(Animal animal,String something){
String animalCategory="";
if (animal instanceof Dog){
animalCategory="狗";
((Dog) animal).lookHome();
}else if (animal instanceof Cat){
animalCategory="猫";
((Cat) animal).catchMouse();
}
System.out.println("年龄为"+this.age+"岁的"
+this.name+"养了一只"+animal.getColor()+"的"+animal.getAge()+"岁的"+animalCategory);
animal.eat(something);
}
}



package com.zhuixun.demo;

/**
* @Author: zhuixun
* @Date: 2022/12/11 14:15
* @Version 1.0 猫类
*/
public class Cat extends Animal{

public Cat() {
}

public Cat(int age, String color) {
super(age, color);
}

@Override
public void eat(String something){
System.out.println(this.getAge()+"岁的"+this.getColor()+"的猫眯着眼睛侧着头吃"+something);
}

public void catchMouse(){
System.out.println("猫抓老鼠");
}
}


package com.zhuixun.demo;

/**
* @Author: zhuixun
* @Date: 2022/12/11 14:12
* @Version 1.0 狗类
*/
public class Dog extends Animal{

public Dog(){}


public Dog(int age, String color) {
super(age, color);
}

@Override
public void eat(String something){
System.out.println(this.getAge()+"岁的"+this.getColor()+"的狗两只前腿死死的抱住"+something+"猛吃");
}
public void lookHome(){
System.out.println("看家");
}
}

package com.zhuixun.demo;

/**
* @Author: zhuixun
* @Date: 2022/12/11 14:25
* @Version 1.0
*/
public class Test11 {
public static void main(String[] args) {
Person person = new Person("老王",30);
Person person1 = new Person("老李",25);
Animal dog = new Dog(2, "黑色");
person.keepPet(dog,"骨头");
Animal cat = new Cat(3,"灰色");
person1.keepPet(cat,"鱼");
}
}



标准类制作

  1. 类名需要见名知意

  2. 成员变量使用private修饰

  3. 提供至少两个构造方法

    • 无参构造方法
    • 带全部参数的构造方法
  4. get和set方法

    提供每一个成员变量对应的setXxx()/getXxx()

  5. 如果还有其他行为,也需要写上。


面向对象
http://example.com/2023/01/29/Java基础/面向对象/object-oriented/
作者
zhuixun
发布于
2023年1月29日
许可协议