【JavaSE 第十天】

【JavaSE 第十天 】

一、 对象的多态性

引入:
生活中的多态性:一个事物具备不同的形态。
(例如:人在不同时期,不同环境下,状态是不同的。)

1. 对象多态性前提

  • 必须有继承或者是接口实现
  • 必须有方法的重写

多态的语法规则:父类或者接口的引用指向自己的子类的对象。

父类 变量(对象名) = new 子类对象(); // 多态写法

对象调用方法,执行的是子类的方法重写

2. 多态中成员的特点

  1. 多态当中成员变量的特点
  • 编译:如果父类中没有成员变量,编译失败
  • 运行:如果父类和子类都有成员变量,运行父类中的成员变量
  1. 多态中成员方法的特点
  • 编译:如果父类中没有成员方法,编译失败
  • 运行:如果父类和子类都有成员方法,运行子类的方法重写
  1. 简记:成员方法编译看左边(父类),运行看右边(子类)。成员变量都是左边(父类)。(Person p = new Student();

父类:

public class Person {
   String s = "父类成员";

   public void eat(){
       System.out.println("人在吃饭");
   }
}

子类:

public class Student extends Person {
    String s = "子类成员";

    public void eat(){
        System.out.println("学生吃饭");
    }
}

调用:

    public static void main(String[] args) {
        Person p = new Student();
        //对象 p,子类对象,调用成员变量 s
        System.out.println(p.s);
        //子类对象调用方法
        p.eat();
    }

解释:
多态JVM解释

3. 多态的转型

  • 多态的程序中,不能调用子类的特有成员。
  • 多态的程序中,只能调用子类和父类共有的成员
(1) 基本数据类型的转换
① 自动转换:

byte → short → int → long → float → double
例如:

int a = 1;
byte b = 1;
// b+a 的结果是 int
short s = 1;
int i = 1;
s = s + 1; // 错误的运算
// s + 1 结果是 int 类型
s = (short)(s + 1) // 正确的写法
② 强制类型转换

类型强制转换的公式

转后类型 变量名 = (转后类型)要转换的数据
(2) 引用数据类型的转换
① 自动转换
 // 父类类型   // 子类类型
Animal animal = new Cat();

等号两边类型不同,所以程序出现了自动的类型转换
自动类型转换,小类型转换为大类型,父类为大,子类为小(子类类型转换为父类类型)
Cat 类型自动转换,提升为了父类 Animal 类型:类型向上转型
(这也就是多态程序中,为什么不能调用子类的特有成员)

② 强制类型转换

需要使用子类的特有成员:必须进行强制类型转换
目标:是将已经提升为父类的 Cat 类型,再转回 Cat 类型(子类)
类型强制转换的公式

转后类型 变量名 = (转后类型)要转换的数据
Cat 变量名 = (Cat)animal; // 类型的向下转型
    public static void main(String[] args) {
        // 创建对象,多态性
        // 父类 = new 任意子类对象(); 扩展性
        Animal animal=new Cat();
        animal.eat();
        // Cat 类的特有功能
        // catchMouse() 方法 但是多态编译看父类
        // 强制类型转换
        // Cat 提升为了 Animal ,再转回 Cat 类型
        Cat c=(Cat)animal;
        c.catchMouse();
    }

(调用 Cat 类型子类的特有成员,不能再次 new 一个 Cat 类型对象,那就是另一个对象(另一块内存))
强转回子类类型会影响多态的扩展性,是否强转类型要看具体情况,是否要调用子类的特有成员。

4. 多态中的转型异常

  • 异常 ClassCastException(类型转换异常),在多态中经常发生
    在进行强制类型转换的时候发生,例如:
    public static void main(String[] args) {
        Animal animal=new Dog(); // 子类 Dog 类型也继承自 Animal
        animal.eat();            // 调用子类与父类都有的成员方法
        Cat c=(Cat)animal;       // 这里将 Dog 类型强转为 Cat 类型
        c.catchMouse();          // 就会出现 ClassCastException(类型转换异常)
    }
  • 解决异常:注意对象是什么类型就转换成什么类型
    • 这里存在一个运算符方便判断对象的类型的运算符:属于比较运算符,结果是 boolean 类型
  • 运算符是关键字 instanceof
    • instanceof 的语法格式:
对象名 instanceof 类的名字
  • 解释:比较这个对象,是不是由这个类产生的

例如:

c instanceof Cat;  // 解释:c 对象是不是由 Cat 类产生的,如果是结果就是 true

解决:保障安全性,强制类型转换之前的安全性判断

        if(animal instanceof Dog){
            Dog d=(Dog)animal;
            d.lookHome();
        }
        if(animal instanceof Cat){
            Cat c=(Cat)animal;
            c.catchMouse();
        }

二、 抽象类 abstract

程序中:如果知道这个功能存在,但是怎么完成,说不清楚,程序中就出现了抽象。

1. 抽象方法定义

  • 使用关键字 abstract 定义抽象方法:
权限修饰符 abstract 返回值类型 方法名字(参数列表);
// abstract 关键字
// 抽象方法没有方法体,不需要 {} ,直接分号结束
  • 当一个类中方法是抽象方法的时候,这个类必须是抽象类,在类的关键字 class 前面使用 abstract 修饰

具体使用:

public abstract class 类名{
    权限修饰符 abstract 返回值类型 方法名字(参数列表);
}

2. 抽象类的使用方式

  1. 抽象类不能实例化对象,不能进行 new 对象 操作
    类中没有主体的方法存在,建立对象调用抽象方法是绝对错误的,因此不能建立对象
  2. 需要子类继承抽象类,重写抽象方法。(强制性操作,否则会报错,并且有几个重写几个)
  3. 创建子类对象(共两类:一种是使用多态性创建对象,一种是只利用子类创建对象)
  4. (建议)使用多态性创建对象,调用方法执行子类的重写

父类:

public abstract class Animal {
    public abstract void eat();
}

子类:

public class Cat extends Animal{
    /**
     *  重写父类的方法
     *  去掉关键字 abstract
     *  添加主体
     */
    public void eat(){
        System.out.println("猫吃鱼");
    }
}

调用:

    public static void main(String[] args) {
        // 创建 Animal 的子类对象(用多态)
        Animal animal=new Cat();
        // eat 方法不可能执行父类,那就运行子类重写
        animal.eat();
    }

3. 抽象类中成员的定义

(1) 抽象类中定义成员变量
  • 在抽象类中定义成员变量方法
    • ①成员变量私有修饰符
    • ②提供 get/set 方法
    • ③由子类的对象使用
  • 示例:

父类:

public abstract class Animal {
    // 抽象类中定义成员变量
    private String name;
    public abstract void eat();

    public String getName() {
        return name;
    }

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

子类:

public class Cat extends Animal{
    /**
     *  重写父类的方法
     *  去掉关键字 abstract
     *  添加主体
     */
    public void eat(){
        System.out.println("猫吃鱼");
    }
}

调用:

    public static void main(String[] args) {
        // 利用多态创建 Animal 的子类对象
        Animal animal=new Cat();
        // eat 方法不可能执行父类,运行子类重写
        animal.eat();
        // 调用 get/set 方法
        animal.setName("Tom");
        animal.getName();
        System.out.println(animal.getName());
    }
(2) 抽象类中的构造方法(构造器)

抽象类中具有默认的构造方法,也可以自己定义有参数的构造方法,全部正常使用(遵循继承后的构造方法的特点)。

父类:

public abstract class Animal {
    // 自己写的父类的构造方法
    public Animal(){
        // 此构造方法对象一经创建就会执行
        System.out.println("Animal 的构造方法");
    }
    public abstract void eat();
}

子类:

public class Cat extends Animal{

    // 在子类中,也有默认的构造方法,并且第一行(这个位置)就是调用(super();)父类中的构造方法(遵循继承后的构造方法的特点)
    
    public void eat(){
        System.out.println("猫吃鱼");
    }
    
}

调用:

    public static void main(String[] args) {
        Animal animal=new Cat(); // 对象一经创建就会执行父类中的构造方法
        // eat 方法不可能执行父类,运行子类重写
        animal.eat();
    }
(3) 抽象类中可以不定义抽象方法

抽象类中,可以不定义抽象方法,但是,如果有抽象方法存在,这个类必须是抽象类

补充:

/**
 *  抽象类中不定义抽象方法,用作适配器
 *  Web 开发中,浏览器请求的数据分为,部分是固定的,部分是可变的
 */
public abstract class Adapter {
    public void gu(){
        // 这里写的是固定的部分
    }
    public void ding(){
        // 这里写的是固定的部分
    }
    /**
     *  在子类重写中是可变的部分
     */
}
(4) 抽象类下的子类还是抽象类问题

当一个子类继承一个抽象类的时候,子类必须重写全部的抽象方法(这时子类不是抽象类)。假如子类重写了部分抽象方法,这个子类依然还是抽象类。

父类:

public abstract class Animal {
    public abstract void eat();
    public abstract void sleep();
}

子类:

/**
 * Cat 继承父类 Animal,Cat 类拥有了父类的成员
 */
public abstract class Cat extends Animal {
    public void eat(){}
    /**
     * 方法 sleep 没有重写
     * 还是一个抽象的方法(必须要声明此类为抽象类,即加入 abstract 关键字)
     */
} 
(5) 员工案例:

公司类:

/**
 *  公司类
 *  定义的是所有员工的共性内容
 */
public abstract class Company {
    private String name; //员工姓名
    private String id; // 员工编号,唯一标识
    // 工作行为,具体到某个岗位是不同,无法写出具体的工作内容
    public abstract void work();

    public String getName() {
        return name;
    }

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

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

研发部类:

/**
 *  研发部类
 */
public class Development extends Company{
    // 重写工作的抽象方法
    @Override
    public void work() {
        // 调用父类的方法
        System.out.println(super.getName()+":"+super.getId()+":"+"研发部的员工在工作");
    }
}

财务部类:

/**
 *  定义财务部类
 */
public class Financial extends Company {
    @Override
    public void work() {
        System.out.println(super.getName()+"::"+super.getId()+":"+"财务部员工在工作");
    }

调用:

    public static void main(String[] args) {
        // 创建对象,子类对象,多态性
        Company c1 = new Development();
        // 父类的方法,属性赋值
        c1.setName("张三");
        c1.setId("研发部001");
        c1.work();
        Company c2 = new Financial();
        c2.setName("李四");
        c2.setId("财务部001");
        c2.work();
    }

4. 抽象类的使用意义:

继承下强制性的方法重写,保证其父类下的子类的功能的完整性。

三、 接口 interface

1. 接口无处不在

身边的接口有:笔记本上 USB 接口、HDMI、TypeC 接口、插座等
例如: USB 接口 : 连接鼠标、键盘、摄像头、手机、移动硬盘等
设备的工作原理不同,但是都可以连接到 USB 接口上,完成他的任务。说明 : 这些设备都满足 USB 的接口规范!!!

接口:就是一个规范,或者称为标准 ,无论什么设备,只要符合接口标准,就可以正常使用,因此接口的扩展性很强大。

2. Java 中接口的定义

  • 一个抽象类中的所有方法全部都是抽象方法的时候,可以将这个抽象类换一个更加贴切的名词,叫做接口,接口是特殊的抽象类
  • 定义接口:使用关键字 interface
    语法规范:
public interface 接口名{}
  • 接口在编译后,依然是 .class 文件 (JVM 只运行 .class 文件)

3. 接口中成员定义(JDK1.7版本)

(1) 成员变量:
  • 成员变量的定义是有固定格式的
  • 成员变量的修饰符是固定的 public static final
public static final 数据类型 变量名 =;

示例:

/**
 * 自定义接口,接口的名字为 MyInterface
 * 将关键字 class 改为 interface
 * 注意:接口中成员变量的修饰符是固定的,不可以改变,所以完全可以不写
 * 另外:还可以挑着写,并且没有顺序规定(建议按照顺序书写习惯)
 */
public interface MyInterface {
    // 接口的成员变量 public static final
    // final 最终修饰符,变量一经赋值,终身不变
    public static final int A=1;
}

对于完全不写的情况,通过 javap(反编译 .class文件)仍然可以看到 public static final 的字样存在

(2) 成员方法:
  • 成员方法的定义实际有固定格式的
  • 成员方法的修饰符是固定的 public abstract
public abstract 返回值类型 方法名(参数列表);

示例:

public interface MyInterface {
    // 接口的成员方法 public abstract 返回值类型 方法名(参数列表);
    public abstract void MyInterface();
    // 与接口中的成员变量一样,前面的修饰符是固定的,不可以改变,所以可以不写
}

4. 接口的使用方式

  • 接口不能建立对象(实例化),不能 new 对象。
  • 需要定义类,实现接口(继承类,在接口中称为实现,可以理解为继承)
  1. 实现接口,使用关键字 implements
  2. 实现的格式 :
public classimplements 接口名{}
  • 重写接口中的抽象方法
  • 创建子类的对象

示例:

接口:

public interface MyInterFace {
    // 定义接口的成员变量
    public static final int A=1;
    // 定义接口的成员方法
    public abstract void myInter();
}

接口的实现类:

/**
 * 定义 MyInterFace 接口的 实现类
 * 重写接口的抽象方法
 */
public class MyInterFaceImpl implements MyInterFace{
    @Override
    public void myInter() {
        System.out.println("实现类实现接口,重写方法");
    }
}

调用:

    public static void main(String[] args) {
        // 创建对象,多态性,创建接口实现类的对象
        MyInterFace my=new MyInterFaceImpl();
        my.myInter();
        // 输出接口中的成员 A 的值
        System.out.println(my.A);
        // 这里不能更改 A 的值,但是可以进行运算
        // 例如:int i = my.A + 1;
    }

5. 接口的多实现

  • 类和类之间单继承,具有局限性的问题。接口的出现,是对单继承的改良,它允许一个类同时实现多个接口。

假如存在继承下的多继承,进行对比:

继承下的多继承接口的多实现
禁止允许
子类继承多个父类实现连接着多个接口
父类中有可以实例化对象的能力,若多个父类中方法相同,子类全部继承,无法正常调用,出现冲突接口中的方法是抽象的,没有主体,接口的实现需要重写方法,才可以正常调用
可以直接对父类建对象不能对接口直接建对象

语法格式:

public class C implements A,B{}
(1) 实现类,重写实现的多有接口的抽象方法:

接口 A :

public interface A {
    public abstract void a();
}

接口 B :

public interface B {
    public abstract void b();
}

实现接口 A 和 B :

public class C implements A,B{
    @Override
    public void a() {
        System.out.println("重写A接口方法");
    }

    @Override
    public void b() {
        System.out.println("重写B接口方法");
    }
}

调用:

    public static void main(String[] args) {
        // 这里创建对象不能使用多态,只能用子类
        C c=new C();
        c.a();
        c.b();
    }
  • 弊端:接口越多,实现需要重写的方法就越多
(2) 冲突问题:

接口 D :

public interface D {
    public abstract void d();
}

接口 E :

public interface E {
    public abstract void d();
}

实现接口 D 和 E :

public class F implements D,E{
    @Override
    public void d() {
		System.out.println("");
    }
}

①这种两个接口方法一样,进行一次方法重写就可解决
②当接口 D 变为:public abstract void d(); 、而接口 E 变为:public abstract int d(); 、导致方法重写时出现冲突,无法实现,导致报错。
③当接口 D 变为:public abstract void d();、而接口 E 变为:public abstract int d(int a); 、这样就可以实现进行两次方法重写就可以解决,这时就实现了方法的重载。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值