java的封装继承和多态

封装、继承和多态

封裝

定义

面向对象编程语言,需要对现实世界中的事物进行抽象、模拟。现实世界中的对象属性,都是隐藏 在对象内部的,外界无法直接操作和修改。

  • 在类中定义属性的时候,一般需要把属性隐藏起来;
  • 如果外界需要访问这个属性,那么就需要提供公共方法对其访问
public class Student{
//使用private关键字来修饰属性,不允许外部直接访问该属性
	private String name;//属性的封装
//提供公共的setName方法,可以让外部调用该方法给name属性赋值
	public void setName(String name){
		this.name = name;//
	}
	//提供公共的getName方法,可以让外部调用该方法获取name属性的值
	public String getName(){
		return name;
	}
}

setXxx方法,是设置指定的xxx属性值用的

getXxx方法,是获取指定的xxx属性值用的

private

代码中,private修饰符的特点:

  • private是四种权限修饰符中的一种,并且是权限最小的一种;
  • private可以修饰成员变量和成员方法;
  • private修饰属性和方法,就只有当前类中才能访问,在当前类以外的其他地方,都不能访问;
  • private修饰的属性,成为类中的私有属性,private修饰的方法,称为类的私有方法

例如

public class Student{
	//私有属性,只允许在当前类中访问,出了这个类的范围,就不能访问
	private String name;
	//私有方法,只允许在当前类中调用,出了这个类的范围,就不能调用
	private void test(){//行为的封装
	}
}

public class Test{
	public static void main(String[] args){
	Student stu = new Student();
	stu.name = "tom";//编译报错,不允许访问
	stu.test();//编译报错,不允许访问
	}
}
封装的优点
  1. 提高代码的安全性,重要信息可以私有化,不对外暴露;
  2. 提高代码的复用性,常用代码或者功能封装到方法中,可以在其他地方反复使用;
  3. 封装代码的实现细节,便于修改内部代码,提高可维护性;
  4. 简化外部的调用,便于调用者使用;

其实,我们在定义一个类的时候,就是在完成封装的过程

属性:field 、property、 attribute

  //属性封装 	field(字段)

private String name;
private int age;

  //getter/setter方法		property(性能)
public String getName(){
return this.name
}

方法的重载(overloading)

抽象行为

类有很多个方法,具有相同的方法名,但是方法的参数各不相同,这种情况称为方法的重载

System.out.println 中的 println 方法,可以处理很多种参数的情况,例如,可以处理无参的,int类型参数的,String类型参数的,double类型参数、boolean类型参数参数的、char类型参数等;

其实println并不是一个方法,而是有很多println方法,这些方法分别可以处理不同的情况,因为他们的参数列表是不一样的,如图:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RTMQmqcb-1638148891539)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210607105346520.png)]

要求:

方法的重载,必须是在同一个类中,并且要求如下:

  1. 方法的名字必须相同;
  2. 方法的参数列表必须不同:
    • 参数的类型不同;
    • 参数的个数不同;
    • 参数的顺序不同;
  3. 方法的修饰符、返回类型、抛出异常这些地方没有限制(可以相同也可以不同,但是一般都相同);

例如:

public class Test{
	//参数的个数不同
	public void test(){}
	public void test(int a){}
	public void test(String s1,String s2){}
	//参数的类型不同
	public void test(boolean flag){}
	public void test(double salary){}
	//参数的顺序不同
	public void test(int a,String s){}
	public void test(String s,int a){}
//方法的参数可以相同,也可以不同
	public int test(int a,int b){
	return 0;
	}
}

特殊情况:

public class Test{
	//方法重载
    public void test(int a,long b){}
    public void test(long b,int a){}
    public static void main(String[] args){
    Test t = new Test();
    t.test(1,1L);//调用到第一个方法
    t.test(1L,1);//调用到第二个方法
    t.test(1,1);//问题:这个会调用到哪一个test方法?
    }
}

注意,这时候, t.test(1,1) 代码是会编译报错的,因为类中定义的俩个方法都匹配

注意,当参数无法完全精确匹配到方法的时候,参数会尝试做自动转换,然后再去尝试匹配方法

例如:

public class Test {
    //重载方法1
    public void test(int a){}
    //重载方法2
    public void test(short a){}
    public static void main(String[] args){
    Test t = new Test();
    byte a = 1;
    t.test(a);//这里会调用第二个方法,也就是short类型参数的test方法
    }
}

虽然byte数据类型可以自动转换成short类型,也可以转化成int类型,但是short离byte“更近”;

创建和初始化对象

new Student();该代码完成了两个过程

eg:

public static void main(String[] args){
	new Student();
}
  1. new关键字,,给对象申请/分配内存空间,并将对象中的属性进行默认值的初始化,根据属性类型 的不同,其默认值分别为:0 0.0 false ‘\u0000’ null

  2. 如果在代码中,还给这些属性进行了显示赋值,那么就会用这个显示赋的值,把之前的默认值给 覆盖掉。

    显示赋值: private String name = "tom";

    最开始name的值是null,之后做了显示赋值,name的值就变为tom

  3. 调用类中的构造器,在构造器中也可以对属性进行赋值,这个赋值会把之前默认值或者显示赋值 给覆盖掉

实例化:

对象实例化

在一个类中引入另一个类的对象(实例化)

会默认调用该类的构造器以及方法

调用有参构造器:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L5BbQjuC-1638148891543)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210608211627898.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zg4ue9vM-1638148891545)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210608211841933.png)]

无参构造器例子

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gk6CvMhw-1638148891547)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210608212036328.png)]

这里因为父类中的是无参构造器,所以这里隐式调用了父类的无参构造器,即super()(默认有);

若父类中的是有参构造器,例如public A(String name){},那么子类的构造器中即要显式调用public B(){ super(name)}

构造器

如果类中一个构造器都没有定义,那么jvm就会给类提供一个无参构造器,但是一旦有构造器,那么默认的构造器就不存在;

构造器之间还可以相互调用(不要出现递归调用的情况);

类中的构造器也称为构造方法、构造函数,是在创建对象的时候必须要调用的

两特点:
  • 必须和类的名字保持一致
  • 必须没有返回值类型,也不能写void
public class Student{
    //构造器
    public Student(){
    }
}

作用:
  • 使用new关键字来创建对象的时候,后面跟的必须是类中存在的构造器
  • 构造器中的代码,在对象创建后会被调用,从而可以完成对象的初始化工作
public class Student{
    private String name;
    private int age;
    //构造器
    public Student(String name,int age){
    this.name = name;
    this.age = age;
    }
    public static void main(String[] args){
    //创建对象的时候使用构造器
    //并且传参到构造器中,构造器中可以使用这些参数给属性进行初始化
    Student stu = new Student("tom",20);
    }
}
构造器的重载:

除了默认的无参构造器之外,在类中还可以对构造器进行重载,让构造器可以接收一些参数,然后使用 这些参数进行对象的初始化工作。

public class Student{
    private String name;
    private int age;
    //无参构造器
    public Student(){}
    //有参数构造器,接收参数,进行对象属性的初始化
    public Student(String name){
    this.name = name;
    }
    public Student(int age){
    this.age = age;
    }
    public Student(String name,int age){
    this.name = name;
    this.age = age;
	}
}
构造器之间的调用:

使用this关键字,可以在构造器中,调用另一个构造器

public class Student{
    private String name;
    private int age;
    //无参构造器
    public Student(){
    //调用俩参的构造器
    this("tom",20);
    }
    public Student(String name){
    //调用俩参的构造器
    this(name,0)
    }
    public Student(int age){
    //调用俩参的构造器
    this(null,age);
    }
    public Student(String name,int age){
    this.name = name;
    this.age = age;
    }
}

默认构造器:

即使在类中没有定义构造器,那么在编译之后,也会自动的生成一个无参构造器,并且构造器中不执行 任何代码。这个无参构造器就被称为默认的构造器。

我们也可以主动在类中定义出这个无参的默认构造器:

public class Student{
    private String name;
    private int age;
    //这个构造器即使没有写出来,编译后也会自动生成的
    public Student(){
    }
}

需要注意的是,如果我们在类中定义了一个构造器,那么系统就不在为该类生成无参的默认构造器 了。

继承

定义

类和类之间的关系有很多种,继承就是其中的一种关系,除此之外还有依赖、组合、聚合等;

继承的例子有很多:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PjloBdNa-1638148891550)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210607141042854.png)]

继承描述的是事物之间的所属关系,这就是:is-a的关系

子类继承父类,子类就可以继承父类中定义的属性和方法;

例如,

蛇属于爬行动物,爬行动物属于动物。

爬行动物继承了动物的基本属性和方法,蛇又继承了爬行动物的基本属性和方法。

由此可见,父类更通用,子类更具体;

继承的优点:
  • 提高代码的复用性
  • 类与类之间产生的关系(is a),这是使用多态性的前提;
继承的关键字

子类继承父类,使用关键字extends

//定义Person类,作为父类
public class Person{
    String name;
    public void sayHello(){
    System.out.println("hello~ I am "+name);
    }
}

//定义Student类,作为子类
public class Student extends Person{
    private int sid;

    public static void main(String[] args){
    Student stu = new Student();
    stu.name = "tom";//name属性从父类中继承而来
    stu.sayHello();//sayHello方法从父类中继承而来
    }
}


从语法上讲,一个类可以继承任何一个类,只要满足语法即可。但是从意义上来考虑的话,这种继承关系可能并不适合。一般只有具有“is a”关系的两个类,才会考虑是否要使用继承;

子类继承父类,继承父类的属性和方法,并可以在子类中访问这些属性和方法:

//定义Person类,作为父类
public class Person{
    String name;
    private int age;
    public void sayHello(){
    System.out.println("hello~ I am "+name);
    }
}
//定义Student类,作为子类
class Student extends Person{
//这个是子类中单独定义的方法,和父类无关
    public void hi(){
    name = "jack";//访问从父类中继承过来的方法
    sayHello();//调用从父类中继承过来的方法
    age = 20;//编译报错,age是父类中的私有属性,子类中不能访问
    }
}

  • 父类中的私有属性和方法,子类中不能访问;
  • 注意,父类中的构造器,子类是不能继承的;
单继承

java中,类和类之间的继承是单继承;

//编译报错,一个类只能有且只有一个父类,不能同时继承俩个父类
public class Stduent extends Person,Object{
}
Object类

java中没有,如果没有指定父类的话,那么这个类会默认继承父类Object:

//默认继承了Object,编译后会自动生成 extends Object 的代码语句
public class Stduent{
}

从object中继承的方法有:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oqso9Igc-1638148891551)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210607155423550.png)]

绿色原点表示public修饰方法,黄色菱形表示protected修饰的方法,红色方块表示private修饰的方法;

注意,object类没有属性;

java中,每个类都直接的或间接的继承了Object,可以说Object是所有类的父类类型;

默认构造器

子类继承父类,创建子类对象的时候,会默认调用父类的构造器:

//定义Person类,作为父类
public class Person{
    public Person(){
    System.out.println("Person类中的构造器被调用");
    }
}
//定义Student类,作为子类
class Student extends Person{
    public Student(){
    System.out.println("Student类中的构造器被调用");
    }
}
public static void main(String[] args){
    new Student();
}
    //main方法执行会输出以下语句:
    Person类中的构造器被调用
    Student类中的构造器被调用

子类继承父类,会继承父类的属性和方法,那么就需要先调用父类的构造器对父类中的属性进行初始化,初始化后再给子类使用;

super关键字

在类中,除了使用this关键字之外,还有super关键字

在子类中使用super关键字一般做以下事情:

  • 访问父类的属性;
  • 调用父类的方法;
  • 调用父类中的构造器;

访问父类中的属性:

public class Person{
    String name = "zs";
    }
public class Student extends Person{
    //注意,这里是故意和父类中的属性重名
    String name = "lisi";
    public void test(){
    //直接使用name,表示Student中的name属性(就近原则)
    System.out.println(name);
    //也可以使用this和super来区分访问的是哪个name
    System.out.println(this.name);//Student中的name
    System.out.println(super.name);//父类Person中的name
    }
}

调用父类的方法:
  • super关键字和this关键字的用法
  • 当父类中的方法与子类中的方法重名时,根据引用指向的对象选择调用哪个类的方法

当子类重写父类方法使,父类的引用指向哪个类的对象,即调用哪个类的方法

package com.company;

public class Person{
    public void run(){
        System.out.println("person run..");
    }
}
 class Student0 extends Person{
    //注意,这里是故意和父类中的方法重名
    public void run(){//重写了父类的方法
        System.out.println("student run..");
    }
    public void test(){
        //直接使用name,表示Student中的run方法(就近原则)
        run();
        //也可以使用this和super来区分调用的是哪个run方法
        this.run();
        super.run();
    }

    public static void main(String[] args) {
        new Student0().run();
        Person p1=new Person();
        p1.run();
        Person p=new Student0();
        p.run();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3jnIxjjx-1638148891554)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210610212852862.png)]

当父类中的方法是用final修饰时,

即有重写的意义,但时不能叫做重写

package com.company;

public class FinalMethodTest {
    public static void main(String[] args) {
        FinalMethodTest test = new Sub();
        test.test();//输出父类
        Sub s=new Sub();
        s.test();//输出子类
    }
    private final void test() { //私有方法,因为main方法在同一个类中所以才能调用

        System.out.println("父类");
    }
}
class Sub extends FinalMethodTest {
    /**
     * 访问修饰符不能被缩小,这里放大了所以符合重写,
     * 但是final修饰的方法只能被继承不被重写
     */
    public void test() {
        System.out.println("子类");
    }
}

即使引用test指向子类对象,但是还是调用了父类的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Htf7Ll2-1638148891556)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210610213927463.png)]

调用父类中的构造器:
隐式调用

子类构造器中隐式调用父类无参构造器,例如

public class Person{
    public Person(){
    }
}
public class Student extends Person{
    public Student(){
    //这里会隐式调用(自动调用)父类的无参构造器
    }
}

显式调用

子类构造器中显式调用父类无参构造器,例如

public class Person{
    public Person(){
    }
}
public class Student extends Person{
    public Student(){
    //也可以使用super关键字,显示调用父类的构造器(有参的无参的都可以调用)
    super();
    }
}

子类构造器中显式调用父类有参构造器,例如

public class Person{
    public Person(){
    }
    public Person(String name){
    }
}
public class Student extends Person{
    public Student(){
    //也可以使用super关键字,显示调用父类的构造器(有参的无参的都可以调用)
    super("tom");
    }
}

在构造器中,可以使用this调用类中其他构造器,也可以使用super调用父类中的构造器。

但是this和super这俩种调用构造器的代码,不能同时出现,否则会报错。

这是因为this调用构造器的语句和super调用构造器的语句,都要求自己是第一句代码,但是构造器中的 第一句代码只能有一个,所以它们俩个不能同时出现,例如

public class Person{
    public Person(){
    }
}
public class Student extends Person{
//编译报错,在使用this和super调用构造器功能的时候,它们俩个不能同时出现
    public Student(){
    this("tom");
    super();
    }
    public Student(String name){
    }
}

但是,**如果this和super不是都要调用构造器,**那么同时出现就没有问题,例如

public class Person{
    public void sayHello(){
    }
}
public class Student extends Person{
    //编译通过
    public Student(){
    this("tom");
    super.sayHello();
    }
    public Student(String name){
    }
}
小结

子类构造器一定要调用父类构造器:

  1. 隐式调用:

    ​ 子类的构造器中隐式调用父类无参的构造器;

  2. 显式调用:

    ​ 如果父类没有无参构造器,就必须调用父类的有参构造器;

    super();


方法重写(override)

如果子类和父类中出现了相同的方法,这种情况就叫做方法重写 (Override)。

注意,方法重载是发生在同一个类中,方法重写发生在子父类之间

要求:
  • 方法名必须相同;

  • 参数列表必须相同;

  • 访问控制符可以放大,但是不可以缩小

    public–>protected–>default–>扩大–>Exception

  • 方法抛出异常类型的范围可以被缩小,但是不能被放大

    例如:ClassNotFoundException --扩大–> Exception

    例如:Exception --缩小–> ClassNotFoundException

  • 返回值类型可以相同,也可以不同:

    • 如果父类的返回类型是引用类型,子类重写后的方法返回类型可以和父类方法的返回类型保持 一致,也可以是父类方法返回类型的子类型

      例如,父类方法的返回类型是Person,子类重写后的返回类可以是Person也可以是Person的子 类型

    • 如果父类的返回类型是基本类型,那么子类重写后的返回类型必须和父类的保持一致

      例如: 父类方法的返回类型是int,子类重写后的返回类也必须是int

注意,大多数情况下,子类中重写的方法 会和 父类中的方法 完全保持一致,只有方法的实现不 同。(也就是大括号中代码不一样)

父类中哪些方法不能被重写:

  • 父类中的静态方法不能被子类重写
  • 父类中的私有方法不能被子类重写

也就是说,只有在子类中,可以直接访问到的,父类的方法,并且是非静态的方法,才能被子类重 写

常见的重写情况,例如

public class Person{
    public void sayHello(){
    System.out.println("你好!很高兴认识你");
    }
}
public class Student extends Person{
    //子类中,重写父类的sayHello方法
    public void sayHello(){
    System.out.println("hello!nice to meet you");
    }
}
public static void main(String[] args) {
    Student stu = new Student();
    //由于子类重写了sayHello方法,所以这里将会调用到子类中重写后的sayHello
    //如果子类中没有重写sayHello方法,那么这里将会调用到从父类继承过来的sayHello方法
    stu.sayHello();
}

子类继承父类,在调用方法的时候,如果子类中没用重写,那么调用的是从父类继承的方法,如果 子类重写了这个方法,那么将会调用到子类中重写后的方法。( 非常重要

在JavaAPI中,String类继承了Object,并且重写了Object类中的toString、equals等方法: 注意,Object所所有类的父类,每一个类都是直接或间接继承了Object类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PT7DaYIa-1638148891558)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210608151440986.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dVZ0U7q7-1638148891560)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210608151451869.png)]

所以,在调用String对象的toString、equals等方法时候,其实调用的是子类String中重写后的方 法!

思考,一般什么情况下,我们会在子类中,对从父类继承的方法进行重写?

子类继承父类,继承了父类中的方法,但是父类中的方法并不一定能满足子类中的功能需要,所以子类 中需要把方法进行重写。

private、final:

private修饰的方法不能被重写(符合重写的要求但是不能说是重写,不会报错);

final修饰的方法不能 被重写(直接报红,除非和private结合);

package com.company;

public class FinalMethodTest {
    public static void main(String[] args) {
        FinalMethodTest test = new Sub();
        test.test();//输出父类
        test.t1();
        /*Sub s=new Sub();
        s.test();//输出子类*/
    }
    private  void test() { //私有方法,不能被继承 ,因为main方法在同一个类中所以才能调用

        System.out.println("父类");
    }
     private final void t1(){
        System.out.println("fu");
    }
}
class Sub extends FinalMethodTest {
    /**
     * 访问修饰符不能被缩小,这里放大了所以符合重写,
     * 但是final修饰的方法只能被继承不被重写
     */

    public void test() {
        System.out.println("子类");
    }

    public void t1(){

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wT3WChVs-1638148891563)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210610215213508.png)]


多态

定义

相同类型的不同对象,调用同一个方法,最终执行结果是不同的

例如,猫、狗、猎豹都属于动物类,它们都有“跑”这个共同的行为,但是它们各自跑起来的方式又是不 一样的。由此可见,不同的对象,进行同一个行为,但是它们的表现出来的确实不同的形态。

java中的多态,就是来描述这种情况的。

多态的前提:

  • 子类继承父类
  • 子类重写父类中的方法
  • 父类的引用指向子类的方法

注意,类实现接口,这是一种特殊形式的继承,多态也可以体现在类和接口的关系中。

eg:

package com.company.inherited;

public class Person {
    public void sayHello(){
        System.out.println("你好!");
    }
}
class Student1 extends Person{
    public void sayHello(){
        System.out.println("hello!我是一名酷酷的学生");
    }
}
class Teacher extends Person {
    public void sayHello() {
        System.out.println("hi!我是一名酷酷的老师");
    }

    public static void main(String[] args) {
        //声明父类的引用
        Person person;
        /*int random = (int) (Math.random() * 10);
        //根据随机数的情况,让父类引用person指向不同的子类对象(Student对象或者Teacher对象)
        if (random % 2 == 0) {
            person = new Student1();
        } else {
            person = new Teacher();
        }*/
        //使用person引用,调用子类对象中重写的方法
        //关键点在于,在调用sayHello方法的时候,引用person指向的对象是谁
        person =new Student1();
        person.sayHello();

        person= new Teacher();
        person.sayHello();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N2fesxhw-1638148891565)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210608161255877.png)]

同一类型(Person)的引用,指向不同的子类对象(Student或者Teacher),调用同一个方法 (sayHello),最后是不一样的表现形式(执行结果不同)!

eg:

package com.company.inherited;

public class Animal {
    public void eat(){}
}
class Cat extends Animal {
    public void eat() {
        System.out.println("我是喵咪,我喜欢吃鱼");
    }
}
class Dog extends Animal {
    public void eat() {
        System.out.println("我是狗仔,我喜欢吃骨头");
    }

    public static void main(String[] args) {
//多态形式,创建对象
//父类引用指向子类对象
        Animal a = new Cat();
//调用的是Cat中重写的eat方法
//因为这时候引用a指向的是Cat类型对象
        a.eat();
//父类引用指向另一个子类对象
        a = new Dog();
//调用的是Dog中重写的eat方法
//因为这时候引用a指向的是Dog类型对象
        a.eat();
    }
}

注意,一个父类型的引用,可以指向它的任何一个子类对象

  • 编译时多态

    ​ 引用左边的父类该方法

  • 运行时多态

    ​ 引用指向的对象

程序中使用多态的好处:

有一个Game类,里面有俩个方法,可以分别运行篮球游戏和足球游戏。

第一种情况,不使用多态

public class Game {
    public void start(BasketBall basketBall){
    basketBall.play();
    }
    public void start(Football football){
    football.play();
    }
}
class BasketBall{
    public void play(){
    System.out.println("篮球游戏开始初始化...");
    }
}
class Football{
    public void play(){
    System.out.println("足球游戏开始初始化...");
    }
}
public static void main(String[] args) {
    Game game = new Game();
    BasketBall basketBall = new BasketBall();
    game.start(basketBall);
    Football football = new Football();
    game.start(football);
}

使用多态:

package com.company.inherited;

public class Game {
    public void play(){

    }

    public static void main(String[] args) {
        Game b =new BasketBall();
        b.play();
        Game f=new Football();
        f.play();
    }

}
class BasketBall extends Game{
    public void play(){
        System.out.println("篮球游戏。。。。");
    }
}
class Football extends Game{
    public void play(){
        System.out.println("足球游戏。。。。");
    }
}

增加或减少游戏种类变得更加方便;

可以看出,这时候想新增一个球类游戏,并且去运行它,几乎不需要修改什么代码,只需要新增这 个类即可

Object类是所有类的父类

public void test(Object obj){
}

instanceof关键字

在使用多态的情况下,instanceof关键字显得特别重要,因为它告诉我们,当前父类的引用,到底是执行的哪一个子类对象

例如,

public class Person {
}
class Student extends Person{
}
class Teacher extends Person{
}

在这种情况下, 假设有一个test方法: 由多态可知,test方法的参数p,既可以接收Student类型的对象,也可以接收Teacher类型的对象,那么 在这个test中,如何能知道,将来到底接收到的是哪一个类型的对象呢?

public void test(Person p){
//引用p到底是指向的Student对象还是Teacher对象呢??
}

通过instanceof关键字进行判断即可知道引用p到底接收的是一个类型的对象:

public void test(Person p){
    if(p instanceof Student){
    //说明p指向的是Student类型对象
    }
    else if(p instanceof Teacher){
    //说明p指向的是Teacher类型对象
    }
}

package com.company.inherited;

public class Person {
    /*public void test(Person p){
        System.out.println("....");
//引用p到底是指向的Student对象还是Teacher对象呢??
    }*/
    public void test(Person p){
        if(p instanceof Student1){
            System.out.println("student....");
//说明p指向的是Student类型对象
        }
        else if(p instanceof Teacher){
            System.out.println("teacher....");
//说明p指向的是Teacher类型对象
        }
    }


    public static void main(String[] args) {
        Person person=new Student1();
        person.test(person);
    }

}
class Student1 extends Person{
}
class Teacher extends Person{
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sUIUyYQM-1638148891566)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210608165013784.png)]

package com.company;

public class Person {
    private String name;

    public static void main(String[] args) {
        Person p=new Student1();
        System.out.println("p is Student "+(p instanceof Student1));
        System.out.println("p is Teacher "+(p instanceof Teacher));
        System.out.println("p is Person "+(p instanceof Person));
    }

}
class Student1 extends Person{

}

class Teacher extends Person{

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MzzlSV19-1638148891568)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210609095034611.png)]

引用类型转换

定义

多态中的转型分为向上转型向下转型

  • 向上转型(子类转父类)

    • 多态本身就是将子类的对象赋值给父类的引用,这就是一个自动的向上转换的过程(类型自动转换)

    • 例如:Person p = new Student();

  • 向下转型(父类转子类)

    • 父类类型向子类类型转换,是向下转换的过程。(类型强制转换)最好先用instanceof判断是否属于该类,然后再转换,

    • 例如:

    • Person p = new Student();
      Student s = (Student)p; //向下转型
      
为什么需要做向下类型转换:

当使用多态方式调用方法时,编译器会先检查父类中是否有该方法,如果父类中没有,则编译报错;

在这种情况下,即使子类中有这个方法,也是无济于事,因为编译器这一关都过不了,更不要说去运行

也就是说,在使用多态的情况下,父类引用是无法调用到子类中独有的方法;

例如

package com.company;
public class Person {
    public void sayHello(){
        System.out.println("hello....");
    }
}
class Student1 extends Person{
    //注意,这是子类中单独定义的方法,和父类无关
    public void study(){

    }
    //这是子类中重写父类的方法
    /*public  void sayHello(){
        System.out.println("nihao");
    }*/

    public static void main(String[] args) {
//多态,父类的引用指向子类对象
        Person p = new Student1();
        p.sayHello();//编译运行都没问题,子类继承了父类中sayHello方法
//编译报错
//因为引用p是Person类型的,而Person类型中就没有定义这个study方法
//编译器检查到Person中没有这个方法就直接报错了
      //  p.study();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IK4dSCKl-1638148891569)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210609103636385.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aEbgJ0le-1638148891571)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210609103752441.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YPUOcNcw-1638148891572)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210609103848838.png)]

在这个情况下,只能将引用p向下转型,转成Student类型,才能调用到study方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gHLPFTnO-1638148891574)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210609104056925.png)]

引用类型强制转换时的异常:

在类型强制转换的过程中,可能会遇到类型转换异常,例如:

package com.company;

public class Person {
}
class Students extends Person{
    //注意,这是子类中单独定义的方法,和父类无关
    public void study(){}
}
class Teacher extends Person{

    public static void main(String[] args) {
        Person p = new Teacher();
//编译通过,运行报错!
//错误类型是:ClassCastException
        Students s = (Students)p;
        s.study();
    }
}

报以下类型转换异常:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cHgeZnZ6-1638148891575)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210609104354593.png)]

上面代码运行报错的原因,是因为运行过程中,强制转换的时候,引用p实际指向的对象是Teacher类型 的,在这种情况下,它是不能转为Student对象的,因为Teacher对象和Student类型没有任何子父类的关 系!

那么这句代码 Student s = (Student)p; 想转换成功,最终运行不报错,只有一种情况,那就是在转 换的时候,引用p指向的对象,真的就是Student类型的对象才可以。

加上判断条件,代码如下,解决报错

public static void main(String[] args) {
        Person p = new Teacher();
//如果引用p真的是指向Student类型的对象,那么才进行类型的强制转换
//避免在强制转换的过程中出现ClassCastException类型的异常
        if(p instanceof Students){ //p属于Students类着返回true,执行下面代码
            Students s = (Students)p;
            s.study();
        }
    }
案例
//动物园管理员
public class ZooManager {
    //管理员要完成的工作,让动物吃饱然后让它活动活动
    public void doWork(Animal animal){
    //管理员让这个动物吃饱
    animal.eat();//父类中的方法,所以动物都有
    //管理员让这个动物活动活动
    if(animal instanceof Bird){
        //如果是鸟的话,就让他飞翔吧
        Bird bird = (Bird) animal;
        bird.fly();//子类中独有的方法,只能是鸟类对象才能调用
    }
    else if(animal instanceof Horse){
        //如果是马的话,就让他奔跑吧
        Horse horse = (Horse) animal;
        horse.run();//子类中独有的方法,只能是马类对象才能调用
        }
    }
}
//动物类,父类
class Animal{
    String name;
    public void eat(){
        System.out.println("我是"+name+",我吃饱了!");
        }
}
//鸟类,子类
class Bird extends Animal{
    //子类中独有的方法
    public void fly(){
    System.out.println("我要起飞啦~");
	}
}
//马类,子类
class Horse extends Animal{
    //子类中独有的方法
    public void run(){
        System.out.println("我要奔跑啦");
    }
}
    public static void main(String[] args){
        ZooManager zooManager = new ZooManager();
        Animal animal;
        //可以更换动物,让管理员针对当前这个动物进行工作
        animal = new Bird();
        //animal = new Horse();
        zooManager.doWork(animal);
    }

小结:

数据类型转换:

基本类型之间:
引用类型之间:

  • 存在父子类关系:
    • 隐式转换:
    • 显示转换:
  • 不存在任何关系时:
    • 要转的两种类型中是否有提供方法,如果有方法就可以转;如果没有那么有没有中间类可以转;也可以自己写方法(几乎都能转换)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值