Java基础学习笔记(五)—— 面向对象编程(2)

面向对象特点:

面向对象思想是一种更符合我们思考习惯的思想,它可以将复杂的事情简单化,并将我们从执行者变成了指挥者。 面向对象的语言中,包含了三大基本特征,即封装继承多态

1 封装

面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。 封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的 方式。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性,提高了代码的复用性。

1.1 private关键字

使用 private 关键字来修饰成员变量。

  • private是一个权限修饰符,代表最小权限。
  • 可以修饰成员变量和成员方法。
  • 被private修饰后的成员变量和成员方法,只在本类中才能访问。

private 数据类型 变量名;

(2)对需要访问的成员变量,提供对应的一对 getXxx 方法 、 setXxx 方法

  • 对于Setter来说,不能有返回值,参数类型和成员变量对应
  • 对于Getter来说,不能有参数,返回值类型和成员变量对应;
/*问题描述:定义Person的年龄时,无法阻止不合理的数值被设置进来。
解决方案:用private关键字将需要保护的成员变量进行修饰。

一旦使用了private进行修饰,那么本类当中仍然可以随意访问。
但是!超出了本类范围之外就不能再直接访问了。
间接访问private成员变量,就是定义一对儿Getter/Setter方法
*/

public class Person {
    
    

    String name; // 姓名
    private int age; // 年龄

    public void show() {
    
    
        System.out.println("我叫:" + name + ",年龄:" + age);
    }
    
    // 这个成员方法,专门用于向age设置数据
    public void setAge(int num) {
    
    
        if (num < 100 && num >= 9) {
    
     // 如果是合理情况
            age = num;
        } else {
    
    
            System.out.println("数据不合理!");// 此时age取默认值0
        }
    }
    
    // 这个成员方法,专门用于获取age的数据
    public int getAge() {
    
    
        return age;
    }
}
public class DemoPerson {
    
    

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

        person.name = "赵丽颖";
//        person.age = -20; // 直接访问private内容,错误写法!
        person.setAge(20);
        person.show();
    }

}

注意:
对于基本类型当中的boolean值,Getter方法一定要写成isXxx的形式,而setXxx规则不变。

1.2 this关键字

当方法的局部变量和类的成员变量重名的时候,根据“就近原则”,优先使用局部变量。
如果需要访问本类当中的成员变量,需要使用格式:

this.成员变量名

扫描二维码关注公众号,回复: 14463298 查看本文章
public class Person {
    
    

    String name; // 我自己的名字

    // 参数name是对方的名字
    // 成员变量name是自己的名字
    public void sayHello(String name) {
    
    
        System.out.println(name + ",你好。我是" + this.name);
        System.out.println(this);// 地址值 
    }

}
public class DemoPerson {
    
    

    public static void main(String[] args) {
    
    
        Person person = new Person();
        // 设置我自己的名字
        person.name = "王健林";
        person.sayHello("王思聪");
		// 可以得出,this和person的地址值一致
        System.out.println(person); // 地址值
    }
}

注意:
this一定在方法内部,this所在方法被哪个对象调用,this就代表哪个对象

1.3 构造方法

构造方法是专门用来创建对象的方法,当我们通过关键字new来创建对象时,其实就是在调用构造方法

public 类名称(参数类型 参数名称) {
    
    
    方法体
}

注意:

  • 构造方法的名称必须和所在的类名称完全一样,就连大小写也要一样
  • 构造方法不要写返回值类型,连void都不写
  • 构造方法不能return一个具体的返回值
  • 如果没有编写任何构造方法,那么编译器将会默认赠送一个构造方法,没有参数、方法体什么事情都不做。一旦编写了至少一个构造方法,那么编译器将不再赠送
  • 一个标准的类通常有4个组成部分,这样标准的类也叫做Java Bean
    – 所有的成员变量都要使用private关键字修饰
    – 为每一个成员变量编写一对儿Getter/Setter方法
    – 编写一个无参数的构造方法
    – 编写一个全参数的构造方法
public class Student {
    
    

    // 成员变量
    private String name;
    private int age;

    // 无参数的构造方法
    public Student() {
    
    
        System.out.println("无参构造方法执行啦!");
    }

    // 全参数的构造方法
    public Student(String name, int age) {
    
    
        System.out.println("全参构造方法执行啦!");
        this.name = name;
        this.age = age;
    }

    // Getter Setter
    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 class DemoStudent {
    
    

    public static void main(String[] args) {
    
    
        Student stu1 = new Student(); // 无参构造
        System.out.println("============");

        Student stu2 = new Student("赵丽颖", 20); // 全参构造
        System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
        // 如果需要改变对象当中的成员变量数据内容,仍然还需要使用setXxx方法
        stu2.setAge(21); // 改变年龄
        System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
    }
}

2 继承

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。
其中,多个类可以称为子类,也叫派生类,单独那一个类称为父类、超类或者基类。

继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。

  • 提高代码的复用性
  • 类与类之间产生了关系,是多态的前提
  • 子类可以拥有父类的内容,还可以拥有自己专有的内容

2.1 继承的格式

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

class 父类 {
    
     
	...
 }
 class 子类 extends 父类 {
    
     
 	... 
 }

2.2 继承后的特点

在父子类的继承关系当中,如果子类父类中出现不重名的成员变量或成员方法,这时的访问是没有影响的。

(1)成员变量重名

如果父类与子类的成员变量重名,则创建子类对象时,访问有两种方式:

  • 直接通过子类对象访问成员变量:
    – 等号左边是谁,就优先用谁,没有则向上找。
  • 间接通过成员方法访问成员变量:
    – 该方法属于谁,就优先用谁,没有则向上找。

子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用 super关键字:

super.父类成员变量名

  • 局部变量: 直接写成员变量名
  • 本类的成员变量: this.成员变量名
  • 父类的成员变量: super.成员变量名

注意:
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()super()this() 都必须是在构造方法的第一行,所以不能同时出现。

public class Fu {
    
    
    int num = 10;
}
public class Zi extends Fu {
    
    
    int num = 20;
    public void method() {
    
    
        int num = 30;
        System.out.println(num); // 30,局部变量
        System.out.println(this.num); // 20,本类的成员变量
        System.out.println(super.num); // 10,父类的成员变量
    }
}
public class Test {
    
    
    public static void main(String[] args) {
    
    
        Zi zi = new Zi();
        zi.method();
    }
}

(2)成员方法重名

在父子类的继承关系当中,创建子类对象,访问成员方法的规则:
创建的对象是谁,就优先用谁,如果没有则向上找。

注意:
无论是成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类的。

(3)构造方法

public class Fu {
    
    
    public Fu() {
    
    
        System.out.println("父类无参构造");
    }

    public Fu(int num) {
    
    
        System.out.println("父类有参构造");
    }
}
public class Zi extends Fu {
    
    

    public Zi() {
    
    
        super(); // 在调用父类无参构造方法
//        super(20); // 在调用父类重载的构造方法
        System.out.println("子类构造方法!");
    }

    public void method() {
    
    
//        super(); // 错误写法!只有子类构造方法,才能调用父类构造方法。
    }

}
    public static void main(String[] args) {
    
    
        Zi zi = new Zi();
    }

继承关系中,父子类构造方法的访问特点:

  • 子类构造方法当中有一个默认隐含的super()调用,所以一定是先调用的父类构造,后执行的子类构造。
  • 子类构造可以通过super关键字来调用父类重载构造。
  • super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造。

子类必须调用父类构造方法,不写则赠送super();写了则用写的指定的super调用,super只能有一个,还必须是第一个。

总结:继承的三个特点

  1. Java只支持单继承,不支持多继承(一个类的直接父类只能有一个)
  2. Java支持多层继承(继承体系)
  3. 子类和父类是一种相对的概念

super关键字的用法有三种:

  1. 在子类的成员方法中,访问父类的成员变量。
  2. 在子类的成员方法中,访问父类的成员方法。
  3. 在子类的构造方法中,访问父类的构造方法。

super关键字用来访问父类内容,而this关键字用来访问本类内容。用法也有三种:

  1. 在本类的成员方法中,访问本类的成员变量。
  2. 在本类的成员方法中,访问本类的另一个成员方法。
  3. 在本类的构造方法中,访问本类的另一个构造方法。 在第三种用法当中要注意: A. this(…)调用也必须是构造方法的第一个语句,唯一一个。
    B. super和this两种构造调用,不能同时使用。

2.3 抽象类

(1)抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。

修饰符 abstract 返回值类型 方法名 (参数列表)public abstract void run()

(2)抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。

abstract class 类名字 {
    
     
}

public abstract class Animal {
    
     
	public abstract void run()}

(3)抽象的使用

public abstract class Animal {
    
    

    // 这是一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定。
    public abstract void eat();
    // 这是普通的成员方法
    public void normalMethod() {
    
    
    }
}
public class Cat extends Animal {
    
    

    @Override
    public void eat() {
    
    
        System.out.println("猫吃鱼");
    }
}
public class Test {
    
    

    public static void main(String[] args) {
    
    
//        Animal animal = new Animal(); // 错误写法!不能直接创建抽象类对象

        Cat cat = new Cat();
        cat.eat();
    }
}

注意:

  • 不能直接创建new抽象类对象,必须用一个子类来继承抽象父类,创建子类对象进行使用
  • 子类必须覆盖重写抽象父类当中所有的抽象方法,除非该子类也是抽象类
    覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号。
  • 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
  • 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。 理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

2.4 覆盖重写

重写(Override):在继承关系当中,方法的名称一样,参数列表也【一样】。覆盖、覆写。
重载(Overload):方法的名称一样,参数列表【不一样】。

方法的覆盖重写特点:创建的是子类对象,则优先用子类方法。

注意:

  • 必须保证父子类之间方法的名称相同,参数列表也相同。
    @Override:写在方法前面,用来检测是不是有效的、正确的覆盖重写。
    这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。

  • 子类方法的返回值必须【小于等于】父类方法的返回值范围。
    小扩展提示:java.lang.Object类是所有类的公共最高父类(祖宗类),java.lang.String就是Object的子类。

  • 子类方法的权限必须【大于等于】父类方法的权限修饰符。
    小扩展提示:public > protected > (default) > private
    备注:(default)不是关键字default,而是什么都不写,留空。

2.5 继承案例

群主发普通红包。某群有多名成员,群主给成员发普通红包。普通红包的规则:

  1. 群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取。
  2. 成员领取红包后,保存到成员余额中。

请根据描述,完成案例中所有类的定义以及指定类之间的继承关系,并完成发红包的操作。

public class User {
    
    

    private String name; // 姓名
    private int money; // 余额,也就是当前用户拥有的钱数

    public User() {
    
    
    }

    public User(String name, int money) {
    
    
        this.name = name;
        this.money = money;
    }

    // 展示一下当前用户有多少钱
    public void show() {
    
    
        System.out.println("我叫:" + name + ",我有多少钱:" + money);
    }

    public String getName() {
    
    
        return name;
    }

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

    public int getMoney() {
    
    
        return money;
    }

    public void setMoney(int money) {
    
    
        this.money = money;
    }
}
// 群主的类
public class Manager extends User {
    
    

    public Manager() {
    
    
    }

    public Manager(String name, int money) {
    
    
        super(name, money);
    }

    public ArrayList<Integer> send(int totalMoney, int count) {
    
    
        // 首先需要一个集合,用来存储若干个红包的金额
        ArrayList<Integer> redList = new ArrayList<>();

        // 首先看一下群主自己有多少钱
        int leftMoney = super.getMoney(); // 群主当前余额
        if (totalMoney > leftMoney) {
    
    
            System.out.println("余额不足");
            return redList; // 返回空集合
        }

        // 扣钱,其实就是重新设置余额
        super.setMoney(leftMoney - totalMoney);

        // 发红包需要平均拆分成为count份
        int avg = totalMoney / count;
        int mod = totalMoney % count; // 余数,也就是甩下的零头

        // 除不开的零头,包在最后一个红包当中
        // 下面把红包一个一个放到集合当中
        for (int i = 0; i < count - 1; i++) {
    
    
            redList.add(avg);
        }

        // 最后一个红包
        int last = avg + mod;
        redList.add(last);

        return redList;
    }
}
// 普通成员
public class Member extends User {
    
    

    public Member() {
    
    
    }

    public Member(String name, int money) {
    
    
        super(name, money);
    }

    public void receive(ArrayList<Integer> list) {
    
    
        // 从多个红包当中随便抽取一个,给我自己。
        // 随机获取一个集合当中的索引编号
        int index = new Random().nextInt(list.size());
        // 根据索引,从集合当中删除,并且得到被删除的红包,给我自己
        int delta = list.remove(index);
        // 当前成员自己本来有多少钱:
        int money = super.getMoney();
        // 加法,并且重新设置回去
        super.setMoney(money + delta);
    }
}
import java.util.ArrayList;

public class MainRedPacket {
    
    

    public static void main(String[] args) {
    
    
        Manager manager = new Manager("群主", 100);

        Member one = new Member("成员A", 0);
        Member two = new Member("成员B", 0);
        Member three = new Member("成员C", 0);

        manager.show(); // 100
        one.show(); // 0
        two.show(); // 0
        three.show(); // 0
        System.out.println("===============");

        // 群主总共发20块钱,分成3个红包
        ArrayList<Integer> redList = manager.send(20, 3);
        // 三个普通成员收红包
        one.receive(redList);
        two.receive(redList);
        three.receive(redList);

        manager.show(); // 100-20=80
        // 6、6、8,随机分给三个人
        one.show();
        two.show();
        three.show();
    }

}

3 多态

3.1 接口

(1)接口的概述

接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、成员方法和构造方法,那么 接口的内部主要就是封装了方法,包含常量、抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法 (JDK 9)。

接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。

接口的使用,它不能创建对象,但是可以被实现(implements ,类似于被继承)。一个实现接口的类(可以看做是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。

注意:
引用数据类型:数组,类,接口。

(2)接口的定义格式

public interface 接口名称 {
    
    
    // 接口内容
}

注意:
换成了关键字interface之后,编译生成的字节码文件仍然是:.java —> .class

(3)接口的使用

类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类 似继承,格式相仿,只是关键字不同,实现使用 implements关键字

① 抽象方法的使用

接口使用步骤:

  • 接口不能直接使用,必须有一个“实现类”来“实现”该接口。
    格式:

    public class 实现类名称 implements 接口名称 {
          
          
        // 重写接口中抽象方法【必须】 
        // 重写接口中默认方法【可选】
    }
    
  • 接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。
    实现:去掉abstract关键字,加上方法体大括号。

  • 不能直接new接口对象使用,所以接口没有构造方法,应该创建实现类的对象,进行使用。

注意:
如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。

在任何版本的Java中,接口都能定义抽象方法

[public] [abstract] 返回值类型 方法名称(参数列表);
public interface MyInterface {
    
    

    // 这是一个抽象方法
    public abstract void methodAbs1();

    // 这也是抽象方法
    abstract void methodAbs2();

    // 这也是抽象方法
    public void methodAbs3();

    // 这也是抽象方法
    void methodAbs4();

}

注意:

  • 接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract,这两个关键字修饰符,可以选择性地省略
  • 方法的三要素,可以随意定义。

② 默认方法的使用

从Java 8开始,接口里允许定义默认方法。

  • 接口的默认方法,可以通过接口实现类对象,直接调用。
  • 接口的默认方法,也可以被接口实现类进行覆盖重写。
[public] default 返回值类型 方法名称(参数列表) {
    
    
    方法体
}

注意:

  • 接口当中的默认方法,可以解决接口升级的问题
  • 默认方法是有方法体的
public interface MyInterfaceDefault {
    
    

    // 抽象方法
    public abstract void methodAbs();

    // 新添加了一个抽象方法
//    public abstract void methodAbs2(); 子类若是没有重写该方法会报错

    // 新添加的方法,改成默认方法,子类若是没有重写该方法不会报错
    public default void methodDefault() {
    
    
        System.out.println("这是新添加的默认方法");
    }
public class MyInterfaceDefaultA implements MyInterfaceDefault {
    
    
    @Override
    public void methodAbs() {
    
    
        System.out.println("实现了抽象方法,AAA");
    }
}
public class MyInterfaceDefaultB implements MyInterfaceDefault {
    
    
    @Override
    public void methodAbs() {
    
    
        System.out.println("实现了抽象方法,BBB");
    }

    @Override
    public void methodDefault() {
    
    
        System.out.println("实现类B覆盖重写了接口的默认方法");
    }
}
public class DemoInterface {
    
    

    public static void main(String[] args) {
    
    
        // 创建了实现类对象
        MyInterfaceDefaultA a = new MyInterfaceDefaultA();
        a.methodAbs(); // 调用抽象方法,实际运行的是右侧实现类。

        // 调用默认方法,如果实现类当中没有,会向上找接口
        a.methodDefault(); // 这是新添加的默认方法
        System.out.println("==========");

        MyInterfaceDefaultB b = new MyInterfaceDefaultB();
        b.methodAbs();
        b.methodDefault(); // 实现类B覆盖重写了接口的默认方法
    }

}

③ 静态方法的使用

从Java 8开始,接口当中允许定义静态方法,但接口中不能有静态代码块 static {}

[public] static 返回值类型 方法名称(参数列表) {
    
    
    方法体
}

注意:

  • 静态方法的使用就是将abstract或者default换成static即可,带上方法体
  • 不能通过接口实现类的对象来调用接口当中的静态方法
    正确用法:通过接口名称,直接调用其中的静态方法,接口名称.静态方法名(参数);
public interface MyInterfaceStatic {
    
    

    public static void methodStatic() {
    
    
        System.out.println("这是接口的静态方法!");
    }
}
public class MyInterfaceStaticImpl implements MyInterfaceStatic {
    
    
}
public class Demo03Interface {
    
    

    public static void main(String[] args) {
    
    
        // 创建了实现类对象
        MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();

        // 错误写法!
//        impl.methodStatic();

        // 直接通过接口名称调用静态方法
        MyInterfaceStatic.methodStatic();
    }

}

④ 私有方法的使用

我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题。
但是这个共有方法不应该让实现类使用,应该是私有化的。private 的方法只有接口自己才可以调用,不能被实现类或被人调用

从Java 9开始,接口当中允许定义私有方法。

  • 普通私有方法,解决多个默认方法之间重复代码问题
private 返回值类型 方法名称(参数列表) {
    
    
    方法体
}
  • 静态私有方法,解决多个静态方法之间重复代码问题
private static 返回值类型 方法名称(参数列表) {
    
    
    方法体
}

⑤ 常量的使用

接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。
从效果上看,这其实就是接口的【常量】。

[public] [static] [final] 数据类型 常量名称 = 数据值;

注意:

  1. 接口当中的常量,可以省略 public static final ,注意:不写也照样是这样。
  2. 接口当中的常量,必须进行赋值;不能不赋值。
  3. 接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则)
  4. 一旦使用 final 关键字进行修饰,说明不可改变
  5. 访问接口当中的常量 接口名称.常量名称

(4)接口的多实现

一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。

public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
    
    
    // 覆盖重写所有抽象方法
}

注意:

  • 如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
  • 如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。
  • 如果实现类锁实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写。
  • 一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法。

(5)接口的多继承

类与类之间是单继承的。直接父类只有一个。
类与接口之间是多实现的。一个类可以实现多个接口。
接口与接口之间是多继承的。

public interface MyInterfaceA {
    
    
    public abstract void methodA();
    public abstract void methodCommon();
    public default void methodDefault() {
    
    
        System.out.println("AAA");
    }
}
public interface MyInterfaceB {
    
    
    public abstract void methodB();
    public abstract void methodCommon();
    public default void methodDefault() {
    
    
        System.out.println("BBB");
    }
}
/*
这个子接口当中有几个方法?答:4个。
methodA 来源于接口A
methodB 来源于接口B
methodCommon 同时来源于接口A和B
method 来源于我自己
 */
public interface MyInterface extends MyInterfaceA, MyInterfaceB {
    
    
    public abstract void method();
    @Override
    public default void methodDefault() {
    
    
    }
}
public class MyInterfaceImpl implements MyInterface {
    
    
    @Override
    public void method() {
    
    
    }
    
    @Override
    public void methodA() {
    
    
    }
    
    @Override
    public void methodB() {
    
    
    }
    
    @Override
    public void methodCommon() {
    
    
    }
}

注意:

  • 多个父接口当中的抽象方法如果重复,没关系,因为抽象方法没有方法体
  • 多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,【而且带着default关键字】。

3.2 多态

(1)多态的概述

多态是继封装、继承之后,面向对象的第三大特性。
extends继承或者implements 实现,是多态性的前提。

生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也 是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。

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

(2)多态的格式与使用

代码当中体现多态性,其实就是一句话:父类引用指向子类对象。

父类名称 对象名 = new 子类名称();
// 或者:
接口名称 对象名 = new 实现类名称();
public class Fu {
    
    
    public void method() {
    
    
        System.out.println("父类方法");
    }
    public void methodFu() {
    
    
        System.out.println("父类特有方法");
    }
}
public class Zi extends Fu {
    
    
    @Override
    public void method() {
    
    
        System.out.println("子类方法");
    }
}
public class Demo {
    
    
    public static void main(String[] args) {
    
    
        // 使用多态的写法
        // 左侧父类的引用,指向了右侧子类的对象
        Fu obj = new Zi();
        obj.method(); // 子类方法
        obj.methodFu(); // 父类特有方法
    }
}

注意:
左父右子就是多态,把子类当做父类来使用(一只猫被当作动物来看待),因为子类就是一个父类。

(3)多态中成员变量的使用

访问成员变量的两种方式:

  • 直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找。
  • 间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找。
public class Fu /*extends Object*/ {
    
    

    int num = 10;

    public void showNum() {
    
    
        System.out.println(num);
    }
    public void method() {
    
    
        System.out.println("父类方法");
    }
    public void methodFu() {
    
    
        System.out.println("父类特有方法");
    }

}
public class Zi extends Fu {
    
    

    int num = 20;
    int age = 16;

    @Override
    public void showNum() {
    
    
        System.out.println(num);
    }
    @Override
    public void method() {
    
    
        System.out.println("子类方法");
    }
    public void methodZi() {
    
    
        System.out.println("子类特有方法");
    }
}

public class Demo{
    
    

    public static void main(String[] args) {
    
    
        // 使用多态的写法,父类引用指向子类对象
        Fu obj = new Zi();
        System.out.println(obj.num); // 父:10
//        System.out.println(obj.age); // 错误写法!
        System.out.println("=============");

        // 子类没有覆盖重写,就是父:10
        // 子类如果覆盖重写,就是子:20
        obj.showNum();
    }

}

注意:
成员变量不可以覆盖重写,成员方法可以

(4)多态中成员方法的使用

在多态的代码当中,成员方法的访问规则是:看new的是谁,就优先用谁,没有则向上找。

口诀:编译看左边,运行看右边。

对比一下:
成员变量:编译看左边,运行还看左边。
成员方法:编译看左边,运行看右边。

public class Fu {
    
    
    public void method() {
    
    
        System.out.println("父类方法");
    }
    public void methodFu() {
    
    
        System.out.println("父类特有方法");
    }
}
public class Zi extends Fu {
    
    
    @Override
    public void method() {
    
    
        System.out.println("子类方法");
    }
    public void methodZi() {
    
    
        System.out.println("子类特有方法");
    }
}
public class Demo0{
    
    

    public static void main(String[] args) {
    
    
        Fu obj = new Zi(); // 多态

        obj.method(); // 父子都有,优先用子
        obj.methodFu(); // 子类没有,父类有,向上找到父类

        // 编译看左边,左边是Fu,Fu当中没有methodZi方法,所以编译报错。
//        obj.methodZi(); // 错误写法!
    }

}

(5)多态的好处在这里插入图片描述

3.3 引用类型转换

多态的转型分为向上转型与向下转型两种
在这里插入图片描述
(1)向上转型

多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的

父类类型 变量名 = new 子类类型(); 
// 如:Animal a = new Cat();

(2)向下转型

父类类型向子类类型向下转换的过程,这个过程是强制的

子类类型 变量名 = (子类类型) 父类变量名; 
// 如:Cat c =(Cat) a;
public abstract class Animal {
    
    
    public abstract void eat();
}
public class Cat extends Animal {
    
    
    @Override
    public void eat() {
    
    
        System.out.println("猫吃鱼");
    }
    // 子类特有方法
    public void catchMouse() {
    
    
        System.out.println("猫抓老鼠");
    }
}
/*
向上转型一定是安全的,没有问题的,正确的。但是也有一个弊端:
对象一旦向上转型为父类,那么就无法调用子类原本【特有】的内容。

解决方案:用对象的向下转型【还原】。
 */
public class Demo{
    
    

    public static void main(String[] args) {
    
    
        // 对象的向上转型,就是:父类引用指向之类对象。
        Animal animal = new Cat(); // 本来创建的时候是一只猫
        animal.eat(); // 猫吃鱼

//        animal.catchMouse(); // 错误写法!

        // 向下转型,进行“还原”动作
        Cat cat = (Cat) animal;
        cat.catchMouse(); // 猫抓老鼠

        // 下面是错误的向下转型
        // 本来new的时候是一只猫,现在非要当做狗
        // 错误写法!编译不会报错,但是运行会出现异常:
        // java.lang.ClassCastException,类转换异常
        Dog dog = (Dog) animal;
    }

}

(3)类型判断

如何才能知道一个父类引用的对象,本来是什么子类?

对象 instanceof 类名称

这将会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例

public class Demo02Instanceof {
    
    

    public static void main(String[] args) {
    
    
        Animal animal = new Cat(); // 本来是一只猫
        animal.eat(); // 猫吃鱼

        // 如果希望掉用子类特有方法,需要向下转型
        // 判断一下animal本来是不是Cat
        if (animal instanceof Cat) {
    
    
            Cat cat = (Cat) animal;
            cat.catchMouse();
        }
    }
}

3.4 接口多态综合案例

猜你喜欢

转载自blog.csdn.net/hu_wei123/article/details/125253745