面向对象编程
面向对象特点:
面向对象思想是一种更符合我们思考习惯的思想,它可以将复杂的事情简单化,并将我们从执行者变成了指挥者。 面向对象的语言中,包含了三大基本特征,即封装、继承和多态
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.成员变量名
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只能有一个,还必须是第一个。
总结:继承的三个特点
- Java只支持单继承,不支持多继承(一个类的直接父类只能有一个)
- Java支持多层继承(继承体系)
- 子类和父类是一种相对的概念
super关键字的用法有三种:
- 在子类的成员方法中,访问父类的成员变量。
- 在子类的成员方法中,访问父类的成员方法。
- 在子类的构造方法中,访问父类的构造方法。
super关键字用来访问父类内容,而this关键字用来访问本类内容。用法也有三种:
- 在本类的成员方法中,访问本类的成员变量。
- 在本类的成员方法中,访问本类的另一个成员方法。
- 在本类的构造方法中,访问本类的另一个构造方法。 在第三种用法当中要注意: 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 继承案例
群主发普通红包。某群有多名成员,群主给成员发普通红包。普通红包的规则:
- 群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取。
- 成员领取红包后,保存到成员余额中。
请根据描述,完成案例中所有类的定义以及指定类之间的继承关系,并完成发红包的操作。
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] 数据类型 常量名称 = 数据值;
注意:
- 接口当中的常量,可以省略
public static final
,注意:不写也照样是这样。 - 接口当中的常量,必须进行赋值;不能不赋值。
- 接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则)
- 一旦使用
final
关键字进行修饰,说明不可改变 - 访问接口当中的常量
接口名称.常量名称
(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();
}
}
}