面向对象之多态
1. 多态概述
什么是多态?
多种状态,同一对象在不同情况下表现出不同的状态或行为
Java中实现多态的步骤
- 要有继承(或实现)关系
- 要有方法重写
- 父类引用指向子类对象(is a关系)
案例:
- 代码演示
// 定义父类
public class Animal {
// 姓名
private String name;
// 空参构造
public Animal() {
}
// 全参构造
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 成员方法
public void eat() {
System.out.println("吃饭");
}
}
// 是Animal类的子类
public class Dog extends Animal{
// 需求:因为狗吃骨头,所以要优化父类的eat()方法
@Override
public void eat() {
System.out.println(getName() + "吃骨头");
}
}
/*
动物类案例:
已知父类Animal,成员变量为:姓名,成员方法为:eat()方法
其有一子类Dog类,请用该案例模拟多态
*/
public class Test {
public static void main(String[] args) {
// 需求:演示多态
/*
Java中实现多态的三个步骤:
1. 要有继承(或者实现)关系。
2. 要有方法重写
3. 要有父类引用指向子类对象
*/
// 多态
Animal an = new Dog();
// 测试成员方法的调用
// 结论:多态中调用成员方法时编译看左(左边的类型有没有这个成员),
// 运行看右(运行时具体用的是右边类中的该成员)
an.setName("哈士奇");
an.eat();
}
}
- 运行结果
父类引用指向子类对象的内存图
多态的效果演示
- 需求:父类型变量作为参数时,可以接收任意子类对象
- 分析:
A:定义方法,参数类型为父类型Animal:showAnimal(Animal animal)
B.分别创建Dog类和Mouse类的对象
调用showAnimal方法演示效果 - 代码演示
// 父类:动物类
public class Animal {
// 成员变量
private String name;
// 构造方法
public Animal() {
}
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 成员方法
public void eat() {
System.out.println("吃饭");
}
}
// 子类:狗类
public class Dog extends Animal{
@Override
public void eat() {
System.out.println(getName() + "吃骨头");
}
}
// 子类:老鼠类
public class Mouse extends Animal{
@Override
public void eat() {
System.out.println(getName() + "吃奶酪");
}
}
/*
已知父类Anima,成员变量为:姓名、成员方法为:eat()方法
它有两个子类Dog类,Mouse类,两个子类都重写了Animal类中的eat()方法
在测试类中,定义showAnimal()方法,用来测试Dog类和Mouse类
*/
public class Test {
public static void main(String[] args) {
// 用来测试Dog类和Mouse类
// 测试Dog类
Dog d = new Dog();
d.setName("哈士奇");
showAnimal(d);
// 测试Mouse类
Mouse m = new Mouse();
m.setName("Jerry");
showAnimal(m);
}
// 需求:在该类中定义showAnimal()方法
// 多态做法
// 多态的使用场景:父类型可以作为形参的数据类型
// 这样可接收其任意的子类对象
public static void showAnimal(Animal an) {
an.eat();
}
// 传统做法
/* public static void showAnimal(Dog d) {
d.eat();
}
public static void showAnimal(Mouse m) {
m.eat();
}*/
}
- 运行结果
- 多态的效果内存图
案例:多态关系中成员变量的使用
- 需求:子父类中定义了同名的成员变量,如何调用?
- 分析:
A:子父类中定义同名属性name并分别初始化值:String name;
B:在测试类中以多态的方式创建对象并打印name属性值:Animal animal = new Dog();
C:在测试类中以普通方式创建对象并打印name属性值:Dog dog = new Dog(); - 结论:
成员变量不能重写 - 代码演示
// 父类:动物类
public class Animal {
String name = "Animal";
}
// 子类:狗类
public class Dog extends Animal{
String name = "Dog";
}
/*
需求:测试多态关系,成员变量的使用,
结论:
多态关系中,成员变量是不涉及到重写的
简单记忆:
多态关系中,使用成员变量,遵循“编译看左,运行看左”
编译看左:意思是在编译期间看左边的类型有没有这个成员,没有就报错,有就不报错
运行看左,意思是在运行期间使用的是 左边的类型中的这个成员.
*/
public class Test {
public static void main(String[] args) {
// 通过多态的方式创建对象,然后测试成员变量的使用
// 多态:父类引用指向子类对象
Animal an = new Dog();
System.out.println(an.name);
// 通过普通方式创建对象,然测试
Dog dog = new Dog();
System.out.println(dog.name);
}
}
- 运行结果
- 多态关系中成员变量的内存图
2.多态的好处和弊端
多态的好处
- 可维护性:基于继承关系,只需要维护父类代码,提高了代码的复用性,大大降低了维护程序的工作量
- 可扩展性:把不同的子类对象都当做父类看待,屏蔽了不同子类对象间的差异,做出通用的代码,以适应不同的需求,实现了向后兼容
多态的弊端
不能使用子类特有成员
类型转换
当需要使用子类特有功能时,需要进行类型转换
- 向上转型(自动类型转换)
子类型转换成父类型
Animal animal = new Dog(); - 向下转型(强制类型转换)
- 父类型转换成子类型
- Dog dog = (Dog)animal;
注意事项
- 只能在继承层次内进行转换(ClassCastException)
- 将父类对象转换成子类之前,使用instanceof进行检查
案例
- 代码演示
// 父类:动物类
public class Animal {
public void eat() {
System.out.println("吃饭");
}
}
// 子类:狗类
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
// 狗类独有的方法,父类中是没有这个成员方法的
public void watch() {
System.out.println("狗会看家");
}
}
// 子类:猫类
public class Cat extends Animal{
}
public class Test {
public static void main(String[] args) {
// 需求:通过多态创建对象,调用子类中的成员
Animal an = new Dog();
// 调用eat()方法
an.eat();
// 调用watch()方法,属于子类独有的方法
// an.watch();
// 正确的写法
/*Dog dog = (Dog)an;
dog.watch();*/
// 不正常的转换
// Cat cat = (Cat)an;
// 优化后的方案:判断当前对象是否是Dog类的对象,如果是,再调用watch()方法
if (an instanceof Dog) { // 判断an是否是Dog类的对象
//能走到这里,说明条件满足
Dog dog = (Dog)an;
dog.watch();
}
}
}
- 运行结果
3. 抽象类概述
抽象的由来
抽象类的概念
包含抽象方法的类。用abstract修饰
抽象方法的概念
只有方法声明,没有方法体的方法。用abstract修饰
抽象方法的由来
当需要定义一个方法,却不明确方法的具体实现时,可以将方法定义为abstract,体局实现延迟到子类
案例1:
- 代码演示:
//父类:动物类(抽象类)
public abstract class Animal {
//抽象方法(特点:要求子类方法必须重写)
public abstract void eat();
}
//子类:狗类
public class Dog extends Animal{
//alt + enter:快捷键,自动帮你重写方法
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
//子类:老鼠类
public class Mouse extends Animal{
@Override
public void eat() {
System.out.println("老鼠吃奶酪");
}
}
public class Test {
public static void main(String[] args) {
//测试狗类
Dog dog = new Dog();
dog.eat();
//测试老鼠类
Mouse mouse = new Mouse();
mouse.eat();
System.out.println("---------------");
//通过多态进行测试
Animal an = new Dog();
an.eat();
}
}
- 运行结果:
4. 抽象类的特点
抽象类的特点
- 修饰符:必须用abstract关键字修饰
修饰符 abstract class 类名{}
修饰符 abstract 返回类型 方法名{} - 抽象类不能被实例化,只能创建子类对象
- 抽象类子类的两个选择
重写父类所有抽象方法
定义成抽象类
抽象类成员的特点
- 成员变量:
可以有普通的成员变量
也可以有成员常量(final) - 成员方法:
可以有普通方法,也可以有抽象方法
抽象类不一定有抽象方法,有抽象方法的类一定是抽象类(或接口) - 构造方法:
像普通类一样有构造方法,且可以重载
案例2:
//父类:动物类(抽象类)
public abstract class Animal {
//构造方法
public Animal() {}
public Animal(String name) {
this.name = name;
}
//成员变量,其值可变
String name = "哈士奇";
//成员常量,其值不能发生改变
final int AGE = 10;
//吃
public abstract void eat();
//睡
public abstract void sleep();
//叫
public void call() {
System.out.println("动物会叫");
}
}
//子类:狗类
public abstract class Dog extends Animal{
}
//子类:猫类
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void sleep() {
System.out.println("猫躺着睡");
}
}
/*
总结:
抽象类中的成员比普通类多一种:抽象方法
其它和普通类一样
*/
public class Test {
public static void main(String[] args) {
//抽象类不能new(抽象类不能实例化)
//Animal an = new Animal(); 这样写是错误的
//初始化抽象类
Animal an = new Cat();
System.out.println("-----------------------");
//抽象类的成员特点
an.name = "汤姆";
System.out.println(an.name);
//an.AGE = 50; 代码会报错,原因是常量的值不能发生改变
System.out.println(an.AGE);
}
}
- 运行结果
案例3:
- 代码演示
//父类:员工类
public abstract class Employee {
//成员变量
//姓名
private String name;
//工资
private double salary;
//工号
private String id;
//构造方法
public Employee() {
}
public Employee(String name, double salary, String id) {
this.name = name;
this.salary = salary;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
//成员方法
//工作
public abstract void work();
}
//子类:经理类
public class Manager extends Employee{
public Manager() {
}
public Manager(String name, double salary, String id, int bonus) {
super(name, salary, id);
this.bonus = bonus;
}
//奖金
private int bonus;
public int getBonus() {
return bonus;
}
public void setBonus(int bonus) {
this.bonus = bonus;
}
@Override
public void work() {
System.out.println("经理喝着茶看着程序员敲代码");
}
}
//子类:程序员类
public class Coder extends Employee{
//小细节,在实际开发中,子类一般都有两个构造方法
//子类的空参构造访问父类的空参构造
//子类的全彩构造访问父类的全参构造
public Coder() {
super();
}
public Coder(String name, double salary, String id) {
super(name, salary, id);
}
@Override
public void work() {
System.out.println("程序员要敲代码");
}
}
public class Test {
public static void main(String[] args) {
//测试员工类
Employee em = new Coder();
em.work();
//测试经理类
Employee em2 = new Manager();
em2.work();
System.out.println("--------------");
//扩展内容,快速实例化对象
//需求:创建一个姓名叫张三,工资为50000,工号为:研发部007的程序员
/* Coder c = new Coder();
c.setName("张三");
c.setSalary(500000);
c.setId("研发部007");*/
Coder c = new Coder("张三", 50000, "研发部007");
System.out.println("姓名" + c.getName());
System.out.println("工资" + c.getSalary());
System.out.println("工号" + c.getId());
//需求:创建一个名字叫:李四,工资为:400000,工号为:研发部01,奖金为100000
Manager m = new Manager("李四", 400000, "研发部01",100000);
System.out.println("姓名" + m.getName());
System.out.println("工资" + m.getSalary());
System.out.println("工号" + m.getId());
System.out.println("奖金" + m.getBonus());
}
}
- 运行结果
5. final关键字
final的概念
最终的、最后的
final的作用
用于修饰类、方法和变量
- 修饰类:该类不能被继承
String,System - 修饰方法:该方法不能被重写
不能与abstract共存 - 修饰变量:最终变量,即常量,只能赋值一次
不建议修饰引用类型数据,因为仍然可以通过引用修改对象的内部数据,意义不大
案例
- 代码演示
//员工类
public class Employee /*extends Person*/{
//成员变量
String name;
int age;
public final void show() {
System.out.println("这个是绝密文件");
}
}
//程序员类
public class Coder extends Employee{
/*@Override
public void show() {
System.out.println("这个是垃圾文件");
}*/
}
/*
final关键字:
final这个单词是”最终“的意思,在Java中是一个关键字,可以用来修饰类,成员变量,成员方法
修饰的类:不能被继承,但是恶意继承其他的类
修饰的方法:不能被重写
修饰的变量:是一个常量,值只能设置一次
*/
public class Test {
public static void main(String[] args) {
Employee em = new Coder();
em.show();
System.out.println("---------------");
//final修饰的变量:基本类型的变量,是值不能改变
final int NUM = 20;
//NUM = 30; 代码错误,常量值只能设置y9ici
System.out.println(NUM);
System.out.println("------------------");
//final修饰的变量:引用类型的变量,是地址值不能改变,但是属性值可以发生变化
final Employee em2 = new Employee();
//em2 = new Employee(); 代码报错,原因是只要new就会开辟新空间
em2.name = "张三";
em2.age = 23;
System.out.println("name属性值:" + em2.name);
System.out.println("age属性值" + em2.age);
System.out.println("------------");
em2.name = "李四";
em2.age = 25;
System.out.println("name属性值:" + em2.name);
System.out.println("age属性值" + em2.age);
}
}
- 运行结果
6. static关键字
static的概念
静态的
static的作用
用于修饰类的成员:
成员变量:类变量
成员方法:类方法
调用方式
类名,成员变量名;
类名,成员方法名(参数);
static修饰成员变量
- 特点:
被本类所有对象共享 - 需求:定义研发部成员类,让每位成员进行自我介绍
- 分析
A:研发部成员统称为开发者,定义类Developer
B.每位开发者所属部门相同,所以属性departmentName用static修饰:public static String departmentName = “研发部”;
C:Developer类的普通属性和行为:name,work,selfIntroduction();
D:在测试类中创建对象并使用
E:修改部门名称为“开发部”,测试效果 - 注意事项
随意修改静态变量的值时有风险的,为了降低风险,可以同时用final关键字修饰,即共有静态常量(注意命名的变化); - 代码演示
public class Developer {
//成员变量
//姓名
String name;
//工作内容
String work;
//部门名(公共的静态常量)
public final static String DEPARTMENT_NAME = "研发部"; //应该用static修饰
//成员方法
//自我介绍
public void selfIntroduction() {
System.out.println("我是" + DEPARTMENT_NAME + "的" + name + ",我的工作内容是" + work);
}
}
public class Test {
public static void main(String[] args) {
//需求:创建两个员工,然后测试
Developer d1 = new Developer();
d1.name = "张三";
d1.work = "写代码";
d1.selfIntroduction();
Developer d2 = new Developer();
d2.name = "李四";
d2.work = "鼓励师";
d2.selfIntroduction();
System.out.println("--------------");
//随着公司的发展,部门名字要进行调整,改为:开发部
//Developer.departmentName = "开发部";
d1.selfIntroduction();
d2.selfIntroduction();
}
}
- 运行结果
static修饰成员方法
- 静态方法:
静态方法中没有对象this,所以不能访问非静态成员 - 静态方法的使用场景
只需要访问静态成员
不需要访问对象状态,所需参数都由参数列表显示提供 - 需求:定义静态方法,反转数组中的元素
- 分析:
A:先明确定义方法的三要素:
方法名:reverse(反转)
参数列表:int[] arr
返回值类型:void
B:遍历数组,交换数组索引为i和length-1-i的元素:arr[i] <=> arr[arr.length-1-i]
C:当索引i >= (length - 1 - i)时,停止交换元素
D:在测试类中创建对象并使用 - 代码演示
public class ReverseArray {
int num1 = 10;
static int num2 = 20;
//静态方法中没有对象this,所以不能访问非静态成员
public static void show() {
System.out.println(num2);
}
//需求:定义静态方法,反转数组中的元素
public static void reverse(int[] arr) {
/*
这里只需要完成:交换元素的动作就可以了
假设数组中的元素值为:int[] arr = {11, 22, 33, 44, 55},
明确谁和谁进行交换
11和55交换 22和44交换 ...
arr[i] 和 arr[arr.length - 1 - i]交换
明确交换次数:数组的长度/2
*/
for (int i = 0; i < arr.length / 2; i++) {
//arr[i] 和 arr[arr.length - 1 - i]交换
int temp = arr[i];
arr[i] = arr[arr.length - 1 - i];
arr[arr.length - 1 - i] = temp;
}
}
}
public class Test {
public static void main(String[] args) {
//测试show()方法
ReverseArray.show();
//小需求:交换变量
int a = 10;
int b = 20;
int temp = a; //temp = 10;
a = b; //a = 20;
b = temp; //b = 10;
System.out.println("a:" + a);
System.out.println("b:" + b);
System.out.println("--------------------");
int[] arr = {1, 6, 4, 8, 2};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
System.out.println("---------------------");
//调用方法,反转数组
ReverseArray.reverse(arr);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
- 运行结果
7. 接口概述
接口的概念
接口技术用于描述类具有什么功能,但并不给出具体实现,类要遵从接口描述的统一规则进行定义,所以,接口是对外提供的一组规则、标准。
- 代码演示
//接口:表示抽烟的功能
public interface Smoking {
//成员方法
public abstract void smoke();
}
public class Teacher implements Smoking{
@Override
public void smoke() {
System.out.println("抽烟有害健康");
}
}
public class Test {
public static void main(String[] args) {
//多态
Smoking sm = new Teacher();
sm.smoke();
}
}
- 运行结果
接口的定义
- 定义接口使用关键字interface
interface 接口名{} - 类和接口是实现关系,用implements表示
class 类名 implements 接口名
接口创建对象的特点
- 接口不能实例化
通过多态的方式实例化子类对象 - 接口的子类(实现类)
可以是抽象类,也可以是普通类
接口继承关系的特点
- 接口与接口之间的关系
继承关系,可以多继承,格式:
接口 extends 接口1,接口2,接口3… - 继承和实现的区别
继承体现的是“is a”的关系,父类中定义共性内容
实现体现的是“like a”的关系,接口中定义扩展功能 - 代码演示
//接口:USB接口
public interface USB {
public abstract void open();
public abstract void close();
}
public interface A {
}
public interface B {
}
public interface C extends A,B,USB{
}
//子类:键盘类
public abstract class KeyBoard implements USB{
}
//子类:鼠标类
public class Mouse implements USB,A,B{
@Override
public void open() {
System.out.println("连接鼠标");
}
@Override
public void close() {
System.out.println("断开鼠标连接");
}
}
public class Test {
public static void main(String[] args) {
//测试鼠标类
USB usb = new Mouse();
usb.open();
usb.close();
}
}
- 运行结果
8. 接口成员的特点
接口成员方法的特点
- 成员方法
JDK7以前,共有的、抽象方法:
public abstract 返回值类型 方法名();
JDK8以后,可以有默认方法和静态方法:
public default 返回值类型 方法名(){}
static 返回值类型 方法名(){}
JDK9以后,可以有私有方法
private 返回值类型 方法名(){} - 构造方法
接口不能够实例化,也没有需要初始化的成员,所以接口没有构造方法 - 代码演示
public interface USB {
//成员常量
public static final int NUM = 10;
//成员方法
//jdk7及其以前的写法
public abstract void open();
public abstract void close();
//jdk8多了两种写法
public static void method1(){
System.out.println("我是jdk8的新特性");
}
public default void method2(){
System.out.println("我是jdk8的新特性");
}
//jdk9多了一种写法
/*private void method3(){
System.out.println("我是jdk9的新特性");
}*/
//构造方法
// public USB(){}
}
public class Test {
public static void main(String[] args) {
//测试接口中的成员变量
//USB.num = 20; 这样写代码会报错,常量的值不能修改
System.out.println(USB.NUM);
}
}
- 运行结果