目录
方法覆盖
方法覆盖初体验
方法覆盖经典案例
多态(重要)
多态基础语法:向上转型、向下转型、instanceof
多态在开发中的作用
小试牛刀
解决遗留问题
❤️回顾一下方法重载:
⭐️什么时候使用方法重载(overload)?
当在一个类当中,如果功能相似的话,建议将名字定义的一样,这样代码美观,并且方便编程。
⭐️什么条件满足之后能够构成方法重载(overload)?
条件一:在同一个类当中;
条件二:方法名相同;
条件三:参数列表不同(个数、顺序、类型);
❤️什么时候使用方法覆盖(override)?
⭐️子类继承父类之后,当继承过来的方法无法满足当前子类的业务需求时,子类有权利对这个方法进行重新编写,有必要进行“方法的覆盖”。
⭐️方法覆盖又叫做:方法重写,英语单词叫做:Override、Overwrite,都可以。⭐️重要结论:当子类对父类继承过来的方法进行“方法覆盖”之后,子类对象调用该方法的时候,一定执行覆盖之后的方法。
⭐️当我们代码怎么编写的时候,在代码级别上构成了方法覆盖(override)呢?
条件一:在两个类中,并且两个类必须要有继承关系。
条件二:重写之后的方法和之前的方法具有:
相同的返回值类型、
相同的方法名、
相同的形式参数列表。
条件三:访问权限不能更低,可以更高。(public->protected->private)
条件四:重写之后的方法不能比之前的方法抛出更多的异常,可以更少。
❤️注意事项:(这几个注意事项,当学习了下面的多态语法之后自然就明白了!)
注意1:方法覆盖只是针对于方法,和属性无关。
注意2:私有方法无法覆盖。
注意3:构造方法不能被继承,所以构造方法也不能被覆盖。
注意4:方法覆盖只是针对于“实例方法”,“静态方法覆盖”没有意义。
❤️例1:方法覆盖的引出
⭐️当前程序存在的问题(设计上的问题)?
鸟儿在执行move()方法的时候,最好输出的结果是:“鸟儿在飞翔”
但是当前的程序在执行move()方法的时候输出的结果是:"动物在移动!"
很显然Bird子类从Animal父类中继承过来的move()方法已经无法满足子类的业务需求。
public class OverrideTest01{
public static void main(String[] args){
// 创建鸟对象
Bird b = new Bird();
b.move(); // 让鸟儿移动
// 创建Cat类型对象
Cat c = new Cat();
c.move();// 让猫儿走猫步
}
}
// 父类
class Animal{
// 移动
public void move(){
System.out.println("动物在移动!");
}
}
// 子类--------------有继承关系
class Bird extends Animal{
// 子类继承父类中,有一些“行为”可能不需要改进,有一些“行为”可能面临着必须改进。
// 因为父类中继承过来的方法已经无法满足子类的业务需求。
//例如: 鸟儿在移动的时候希望输出鸟儿在飞翔!!!!
}
class Cat extends Animal{
//例如: 猫在移动的时候,我希望输出:猫在走猫步!!!!!!
}
❤️例2:使用方法覆盖
public class OverrideTest02{
public static void main(String[] args){
Bird b = new Bird();
b.move(); //----方法覆盖
b.sing(1000); //Animal sing....
Cat c = new Cat();
c.move(); //----方法重载
}
}
class Animal{
public void move(){
System.out.println("动物在移动!");
}
public void sing(int i){
System.out.println("Animal sing....");
}
}
//1.条件一:具有继承关系
class Bird extends Animal{
// 对move方法进行方法覆盖,方法重写,override
// 最好将父类中的方法原封不动的复制过来。(不建议手动编写)
// 方法覆盖,就是将继承过来的那个方法给覆盖掉了。继承过来的方法没了。
//2.条件二:重写之后的方法和之前的方法具有:相同的返回值类型、相同的方法名、相同的形式参数列表。
public void move(){
System.out.println("鸟儿在飞翔!!!");
}
//3.条件三:访问权限不能更低,可以更高
//protected表示受保护的。没有public开放。
// 错误:正在尝试分配更低的访问权限; 以前为public,此时只能为public或者更高
/*
protected void move(){
System.out.println("鸟儿在飞翔!!!");
}
*/
//4. 条件四:重写之后的方法不能比之前的方法抛出更多的异常,可以更少。
//错误:被覆盖的方法未抛出Exception,父类没有抛出异常!
/*
public void move() throws Exception{
System.out.println("鸟儿在飞翔!!!");
}
*/
// 分析:这个sing()和父类中的sing(int i)有没有构成方法覆盖呢?
// 没有,原因是,这两个方法根本就是两个完全不同的方法。
// 可以说这两个方法构成了方法重载吗?可以。
public void sing(){
System.out.println("Bird sing.....");
}
}
class Cat extends Animal{
// 方法重写
public void move(){
System.out.println("猫在走猫步!!!");
}
}
❤️例1
//一定要注意:方法覆盖/重写的时候,建议将父类的方法复制粘贴,这样比较保险。
public class OverrideTest03{
public static void main(String[] args){
// 创建中国人对象
// ChinaPeople p1 = new ChinaPeople("张三"); //err
// 错误原因:没有这样的构造方法,没有有参构造方法
ChinaPeople p1 = new ChinaPeople();// 能调用默认的无参构造方法!
p1.setName("张三");
p1.speak();
// 创建美国人对象
// AmericPeople p2 = new AmericPeople("jack");
// 错误原因:没有这样的构造方法
AmericPeople p2 = new AmericPeople();
p2.setName("jack");
p2.speak();
}
}
//------------------------人
class People{
private String name; // 属性
// 构造方法----不能继承
public People(){}
public People(String name){
this.name = name;
}
//setter and getter方法----能继承
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
// 人都会说话
public void speak(){
System.out.println(name + "....");
}
}
//----------中国人
class ChinaPeople extends People{ // 继承
//构造方法是不能被继承的,这里也没有写;默认是有一个无参的构造方法
// 中国人说话是汉语;所以子类需要对父类的speak()方法进行重写
public void speak(){
System.out.println(this.getName() + "正在说汉语");
}
}
//----------美国人
class AmericPeople extends People{
//构造方法是不能被继承的,这里也没有写;默认是有一个无参的构造方法
// 美国人说话是英语;所以子类需要对父类的speak()方法进行重写
public void speak(){
System.out.println(getName() + " speak english!");
}
}
❤️例2:覆盖toString方法
⭐️关于Object类中的toString()方法
1、toString()方法的作用是什么?
作用:将“java对象”转换成“字符串的形式”。2、Object类中toString()方法的默认实现是什么?
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
toString: 方法名的意思是转换成String
⭐️含义:调用一个java对象的toString()方法就可以将该java对象转换成字符串的表示形式。
public class OverrideTest04{
public static void main(String[] args){
// 创建一个日期对象
MyDate t1 = new MyDate();
// 调用toString()方法(将对象转换成字符串形式。)
// 问:你对这个输出结果满意吗?不满意,希望输出:xxxx年xx月xx日
//-------重写MyDate的toString()方法之前的结果
//System.out.println(t1.toString()); //MyDate@28a418fc
//-------重写MyDate的toString()方法之后的结果
System.out.println(t1.toString());
//-------直接输出一个引用的时候,println方法会自动调用引用的toString方法。
System.out.println(t1); //默认调用toString方法
MyDate t2 = new MyDate(2008, 8, 8);
System.out.println(t2); //2008年8月8日
//创建学生对象
Student s = new Student(1111, "zhangsan");
// 重写toString()方法之前
//System.out.println(s); //Student@87aac27
// 重写toString()方法之后
// 输出一个学生对象的时候,可能更愿意看到学生的信息,不愿意看到对象的内存地址。
System.out.println(s.toString());
System.out.println(s);//这两行结果是等价的
}
}
// 日期类
class MyDate {
private int year;
private int month;
private int day;
//构造方法
public MyDate(){
this(1970,1,1);
}
public MyDate(int year,int month,int day){
this.year = year;
this.month = month;
this.day = day;
}
//set和get接口方法
public void setYear(int year){
this.year = year;
}
public int getYear(){
return year;
}
public void setMonth(int month){
this.month = month;
}
public int getMonth(){
return month;
}
public void setDay(int day){
this.day = day;
}
public int getDay(){
return day;
}
// 调用toString()方法进行字符串转换的时候,
// 希望转换的结果是:xxxx年xx月xx日,这种格式。
public String toString() { //toString方法覆盖
return year + "年" + month + "月" + day + "日";
}
}
class Student{ //默认继承Object类,里面有toString方法
int no;
String name;
public Student(int no, String name){
this.no = no;
this.name = name;
}
// 重写,方法覆盖
public String toString() {
return "学号:" + no + ",姓名:" + name;
}
}
❤️小总结
⭐️关于Object类中toString()方法的覆盖?
(1) toString()方法存在的作用就是:将java对象转换成字符串形式。
(2)大多数的java类toString()方法都是需要覆盖的。因为Object类中提供的toString()
方法输出的是一个java对象的内存地址。⭐️方法重载和方法覆盖有什么区别?
(1)方法重载发生在同一个类当中。
(2)方法覆盖是发生在具有继承关系的父子类之间。
(3)方法重载是一个类中,方法名相同,参数列表不同。
(4)方法覆盖是具有继承关系的父子类,并且重写之后的方法必须和之前的方法一致:
方法名一致、参数列表一致、返回值类型一致。
多态的基础语法
❤️向上转型和向下转型的概念。⭐️向上转型:子--->父 (upcasting)
例如:Animal a = new Cat(); 又被称为自动类型转换⭐️向下转型:父--->子 (downcasting)
例如:Cat c = (Cat)a; 又被称为强制类型转换,需要添加强制类型转换符。
⭐️什么时候需要向下转型?
答: 需要调用或者执行子类对象中特有的方法。必须进行向下转型,才可以调用。
⭐️向下转型有风险吗?
答: 容易出现ClassCastException(类型转换异常)
⭐️怎么避免这个风险?
答:instanceof运算符,可以在程序运行阶段动态的判断某个引用指向的对象是否为某一种类型。向下转型之前一定要使用instanceof运算符进行判断。语法结构:(引用 instanceof 类型)
不管是向上转型还是向下转型,首先他们之间必须有继承关系,这样编译器就不会报错。❤️什么是多态?
⭐️多种形态,多种状态,编译和运行有两个不同的状态。编译期叫做静态绑定。运行期叫做动态绑定。
⭐️例如:Animal a = new Cat(); a.move();
(1)编译的时候编译器发现a的类型是Animal,所以编译器会去Animal类中找move()方法找到了,绑定,编译通过。(2)运行的时候和底层堆内存当中的实际对象有关;真正执行的时候会自动调用“堆内存中真实对象”的相关方法。
多态的典型代码:父类型的引用指向子类型的对象。
❤️什么时候必须进行向下转型?
⭐️调用子类对象上特有的方法时
❤️父类Animal
// 动物类:父类
public class Animal{
// 移动的方法
public void move(){
System.out.println("动物在移动!!!");
}
}
❤️子类Bird
// 鸟儿类:子类
public class Bird extends Animal{
//1. 继承move方法并重写父类的move方法
public void move(){
System.out.println("鸟儿在飞翔!!!");
}
//2. 子类型对象特有的sing方法。
public void sing(){
System.out.println("鸟儿在歌唱!!!");
}
}
❤️子类Cat
// 猫类,子类
public class Cat extends Animal{
//1. 继承move方法并重写父类的move方法
public void move(){
System.out.println("cat走猫步!");
}
//2. 子类型对象特有的catchMouse方法。
public void catchMouse(){
System.out.println("猫正在抓老鼠!!!!");
}
}
❤️测试Test01方法,程序的入口
知识点回顾和补充
⭐️两个概念:
(1)第一个:向上转型,子 ---> 父(自动类型转换)
例如:父类型的引用允许指向子类型的对象:Animal a2 = new Cat();
a2就是父类型的引用;new Cat()是一个子类型的对象;允许a2这个父类型引用指向子类型的对象。
(2)第二个:向下转型,父 ---> 子(强制类型转换,需要加强制类型转换符)⭐️注意:java中允许向上转型,也允许向下转型。
无论是向上转型,还是向下转型,两种类型之间必须有继承关系,没有继承关系编译器报错。我们平常说向上转型和向下转型,不要说自动类型转换,也不要说强制类型转换,因为自动类型转换和强制类型转换是使用在基本数据类型方面的,在引用类型转换这里只有向上和向下转型。
⭐️多态指的是: 多种形态;父类型引用指向子类型对象。包括编译阶段和运行阶段:
编译阶段:静态绑定父类的方法。
运行阶段:动态绑定子类型对象的方法。
⭐️什么时候必须使用“向下转型”?
当你需要访问的是子类对象中“特有”的方法。此时必须进行向下转型。
⭐️运行是出现异常: java.lang.ClassCastException:类型转换异常!
java.lang.NullPointerException:空指针异常!⭐️怎么避免ClassCastException异常的发生???
新的内容,运算符: instanceof (运行阶段动态判断)
第一:instanceof可以在运行阶段动态判断引用指向的对象的类型。
第二:instanceof的语法: (引用 instanceof 类型)
第三:instanceof运算符的运算结果只能是:true/false
第四:c是一个引用,c变量保存了内存地址指向了堆中的对象。
假设(c instanceof Cat)为true表示:
c引用指向的堆内存中的java对象是一个Cat。
假设(c instanceof Cat)为false表示:
c引用指向的堆内存中的java对象不是一个Cat。
好习惯:
任何时候,任何地点,对类型进行向下转型时,一定要使用
instanceof 运算符进行判断。(java规范中要求的。)
这样可以很好的避免:ClassCastException(类型转换异常)
public class Test01{
public static void main(String[] args){
//1.调用父类的move
Animal a1 = new Animal();
a1.move(); //动物在移动!!!
//2.调用Cat子类方法覆盖的move
Cat c1 = new Cat();
c1.move(); //cat走猫步!
//3.调用Bird子类方法覆盖的move
Bird b1 = new Bird();
b1.move(); //鸟儿在飞翔!!!
//4. 新知识点:多态:父类型的引用指向子类型的对象
Animal a2 = new Cat();
Animal a3 = new Bird(); //必须存在继承关系才可以这样写
/*
1、Animal和Cat之间有继承关系!
2、Animal是父类,Cat是子类。
3、Cat is a Animal,这句话能说通!
4、java中支持这样的一个语法:父类型的引用允许指向子类型的对象。
例如:Animal a2 = new Cat();
a2就是父类型的引用。
new Cat()是一个子类型的对象。
允许a2这个父类型引用指向子类型的对象。
5、假设存在一个Dog类,并不是继承父类Animal
例如:Animal a4 = new Dog(); // 没有继承关系的两个类型之间不存在转型
错误: 不兼容的类型: Dog无法转换为Animal
*/
//5. 调用a2的move()方法
/*
多态:多种形态,多种状态。
分析:a2.move();
java程序分为编译阶段和运行阶段。
(1)编译阶段:静态绑定
对于编译器来说,编译器只知道a2的类型是Animal,
所以编译器在检查语法的时候,会去Animal.class
字节码文件中找move()方法,找到了,绑定上move()
方法,编译通过,静态绑定成功。(编译阶段属于静态绑定。)
(2)分析运行阶段:动态绑定
运行阶段的时候,实际上在堆内存中创建的java对象是
Cat对象,所以move的时候,真正参与move的对象是一只猫,
所以运行阶段会动态执行Cat对象的move()方法。这个过程
属于运行阶段绑定。(运行阶段绑定属于动态绑定。)
多态表示多种形态:编译的时候一种形态;运行的时候另一种形态。
*/
a2.move(); //cat走猫步!
// 调用a3的move()方法
a3.move(); //鸟儿在飞翔!!!
// ======================================================================
6、Animal a5 = new Cat(); // 底层对象是一只猫。
//a5.catchMouse(); //这个方法实际上是子类特有的方法,不是继承过来的;静态绑定失败!
// 分析这个程序能否编译和运行呢?
// 分析程序一定要分析编译阶段的静态绑定和运行阶段的动态绑定。
// 只有编译通过的代码才能运行。没有编译,根本轮不到运行。
// 错误: 找不到符号
// why??? 因为编译器只知道a5的类型是Animal,去Animal.class文件中找catchMouse()方法;结果没有找到,所以静态绑定失败,编译报错。无法运行。(语法不合法。)
//7、 假设代码写到了这里,我非要调用catchMouse()方法怎么办?
// 这个时候就必须使用“向下转型”了。(强制类型转换)
// 以下这行代码为啥没报错????
// 因为a5是Animal类型,转成Cat,Animal和Cat之间存在继承关系。所以没报错。
Cat x = (Cat)a5;
x.catchMouse(); //猫正在抓老鼠!!!!
//8、 向下转型有风险吗?
Animal a6 = new Bird(); //表面上a6是一个Animal,运行的时候实际上是一只鸟儿。
//Cat y = (Cat)a6; //却要强转成猫儿
//y.catchMouse();
/*
分析以下程序,编译报错还是运行报错???
(1)编译器检测到a6这个引用是Animal类型,
而Animal和Cat之间存在继承关系,所以可以向下转型。
编译没毛病。
(2)运行阶段,堆内存实际创建的对象是:Bird对象。
在实际运行过程中,拿着Bird对象转换成Cat对象
就不行了。因为Bird和Cat之间没有继承关系。
运行是出现异常,这个异常和空指针异常一样非常重要,也非常经典:
java.lang.ClassCastException:类型转换异常。
java.lang.NullPointerException:空指针异常。这个也非常重要。
*/
//9、 怎么避免ClassCastException异常的发生???
/*
新的内容,运算符:
instanceof (运行阶段动态判断)
第一:instanceof可以在运行阶段动态判断引用指向的对象的类型。
第二:instanceof的语法:
(引用 instanceof 类型)
第三:instanceof运算符的运算结果只能是:true/false
第四:c是一个引用,c变量保存了内存地址指向了堆中的对象。
假设(c instanceof Cat)为true表示:
c引用指向的堆内存中的java对象是一个Cat。
假设(c instanceof Cat)为false表示:
c引用指向的堆内存中的java对象不是一个Cat。
程序员要养成一个好习惯:
任何时候,任何地点,对类型进行向下转型时,一定要使用
instanceof 运算符进行判断。(java规范中要求的。)
这样可以很好的避免:ClassCastException
*/
System.out.println(a6 instanceof Cat); //false
if(a6 instanceof Cat){ // 如果a6是一只Cat
Cat y = (Cat)a6; // 再进行强制类型转换
y.catchMouse();
}
}
}
❤️测试Test02方法:instanceof的常规使用
public class Test02{
public static void main(String[] args){
Animal x = new Bird();
Animal y = new Cat();
if(x instanceof Bird){
Bird b = (Bird)x;
b.sing();
} else if(x instanceof Cat){
Cat c = (Cat)x;
c.catchMouse();
}
if(y instanceof Bird){
Bird b = (Bird)y;
b.sing();
} else if(y instanceof Cat){
Cat c = (Cat)y;
c.catchMouse();
}
}
}
❤️测试Test03方法:为什么使用instanceof
public class Test03{
public static void main(String[] args){
// main方法是程序员A负责编写。
AnimalTest at = new AnimalTest();
at.test(new Cat());
at.test(new Bird());
}
}
public class AnimalTest{
// test方法是程序员B负责编写。
// 这个test()方法的参数是一个Animal
public void test(Animal a){ // 实例方法----只知道animal,但是不知道具体的调用方法
// 你写的这个方法别人会去调用。
// 别人调用的时候可能给你test()方法传过来一个Bird
// 当然也可能传过来一个Cat
// 对于我来说,我不知道你调用的时候给我传过来一个啥。
if(a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse();
}else if(a instanceof Bird){
Bird b = (Bird)a;
b.sing();
}
}
}
❤️起初程序---先不使用多态(先理解逻辑)
⭐️Dog类---宠物
//---宠物狗类
public class Dog{
public void eat(){
System.out.println("狗狗喜欢啃骨头,吃的很香!");
}
}
⭐️Master类---主人
// 主人类
public class Master{
//---只喜欢狗狗
public void feed(Dog d){
d.eat();
}
}
⭐️测试Test
//---测试程序
public class Test{
public static void main(String[] args){
//创建主人对象
Master master = new Master();
//创建狗狗对象
Dog dog = new Dog();
//主人喂---建立联系
master.feed(dog);
}
}
❤️中间程序---假如又喜欢其它宠物,增加程序
⭐️又喜欢猫咪,增加Cat类---宠物
//增加---宠物猫类
public class Cat{
public static void main(String[] args){
System.out.println("猫咪喜欢吃鱼,吃的很香!");
}
}
⭐️还要修改Master类---主人
// 主人类
public class Master{
//---只喜欢狗狗
public void feed(Dog d){
d.eat();
}
//---又喜欢猫咪了
//实际上写死了,每次有新需求都要改Master里面的代码,扩展性不好
public void feed(Cat c){
c.eat();
}
}
⭐️测试Test
//---测试程序,测试多态在开发中的作用
public class Test{
public static void main(String[] args){
//创建主人对象
Master master = new Master();
//创建狗狗对象
Dog dog = new Dog();
//主人喂---建立联系
master.feed(dog);
//创建猫咪对象
Cat cat = new Cat();
master.feed(cat);
}
}
❤️最终程序---使用多态,增加可扩展性;以后增加新宠物既不需要修改Master类代码!
⭐️思考:软件在扩展新需求过程当中,修改Master这个类有什么问题?
一定要记住:软件在扩展过程当中,修改的越少越好。修改的越多,你的系统当前的稳定性就越差,未知的风险就越多。⭐️其实这里涉及到一个软件的开发原则:
软件开发原则有七大原则:其中有一条最基本的原则:OCP(开闭原则)⭐️什么是开闭原则?
对扩展开放(你可以额外添加,没问题),对修改关闭(最好很少的修改现有程序)。软件的扩展过程当中,修改的越少越好。⭐️什么是软件扩展性?
假设电脑中的内存条部件坏了,我们可以买一个新的插上,直接使用。这个电脑的设计就考虑了“扩展性”。内存条的扩展性很好。
⭐️面向父类型编程,面向更加抽象进行编程,不建议面向具体编程。因为面向具体编程会让软件的扩展力很差。
⭐️不在写具体的宠物类,写一个抽象类Pet作为父类;其它具体宠物作为子类
public class Pet{
public void eat(){ //具体方法不用写,后面具体子类会覆盖整个方法
}
}
⭐️Dog子类和Cat子类
//---宠物狗类
public class Dog extends Pet{ //子类继承父类
public void eat(){ //方法的覆盖
System.out.println("狗狗喜欢啃骨头,吃的很香!");
}
}
//---宠物猫类
public class Cat extends Pet{//子类继承父类
public void eat(){//方法的覆盖
System.out.println("猫咪喜欢吃鱼,吃的很香!");
}
}
⭐️Master类---主人
// 主人类
public class Master{
public void feed(Pet pet){
//传过来一个抽象类(父类);假设是猫类:Pet pet = new Cat()
// 编译的时候,编译器发现pet是Pet类,会去Pet类中找eat()方法,结果找到了,编译通过
// 运行的时候,底层实际的对象是什么,就自动调用到该实际对象对应的eat()方法上。
// 这就是多态的使用。
pet.eat();
}
}
⭐️测试Test---测试代码不变
//---测试程序,测试多态在开发中的作用
public class Test{
public static void main(String[] args){
//创建主人对象
Master master = new Master();
//创建狗狗对象
Dog dog = new Dog();
//主人喂---建立联系
master.feed(dog);
//创建猫咪对象
Cat cat = new Cat();
master.feed(cat);
}
}
❤️总结多态在开发中的作用
⭐️多态在开发中的作用是:降低程序的耦合度,提高程序的扩展力。
public class Master{ public void feed(Dog d){} public void feed(Cat c){} } //以上的代码中表示:Master和Dog以及Cat的关系很紧密(耦合度高)。导致扩展力很差。
public class Master{ public void feed(Pet pet){ pet.eat(); } } //以上的代表中表示:Master和Dog以及Cat的关系就脱离了,Master关注的是Pet类。 这样Master和Dog以及Cat的耦合度就降低了,提高了软件的扩展性。
⭐️面向对象的三大特征:封装、继承、多态
(1)有了封装,有了这种整体的概念之后。
(2)对象和对象之间产生了继承。
(3)有了继承之后,才有了方法的覆盖和多态。
⭐️这里提到了一个软件开发原则:
七大原则最基本的原则:OCP(对扩展开放,对修改关闭)
目的是:降低程序耦合度,提高程序扩展力。
⭐️面向抽象编程,不建议面向具体编程!
编写程序实现乐手弹奏乐器。乐手可以弹奏不同的乐器从而发出不同的声音。
可以弹奏的乐器包括二胡、钢琴和琵琶。
实现思路及关键代码:
1)定义乐器类Instrument,包括方法makeSound()
2)定义乐器类的子类:二胡Erhu、钢琴Piano和小提琴Violin
3)定义乐手类Musician,可以弹奏各种乐器play(Instrument i)
4)定义测试类,给乐手不同的乐器让他弹奏
/*
编写程序实现乐手弹奏乐器。乐手可以弹奏不同的乐器从而发出不同的声音。
可以弹奏的乐器包括二胡、钢琴和琵琶。
实现思路及关键代码:
1)定义乐器类Instrument,包括方法makeSound()
2)定义乐器类的子类:二胡Erhu、钢琴Piano和小提琴Violin
3)定义乐手类Musician,可以弹奏各种乐器play(Instrument i)
4)定义测试类,给乐手不同的乐器让他弹奏
*/
public class Test{
public static void main(String[] args){
//4.测试
//-------------第一种方法
//创建乐器对象
Erhu erhu = new Erhu();
Piano piano = new Piano();
Violin violin = new Violin();
//创建乐手对象
Musician musician = new Musician();
//play
musician.play(erhu);
musician.play(piano);
musician.play(violin);
//-------------第二种方法---父类的引用指向子类的对象
//创建乐器对象
Instrument erhu1 = new Erhu();
Instrument piano1 = new Piano();
Instrument violin1 = new Violin();
//创建乐手对象
Musician musician1 = new Musician();
//play
musician1.play(erhu1);
musician1.play(piano1);
musician1.play(violin1);
musician.play(violin);
//-------------第三种方法
//创建乐手对象
Musician musician2 = new Musician();
//play
musician2.play(new Erhu());
musician2.play(new Piano());
musician2.play(new Violin());
}
}
//3. 乐手类
//---按照题目要求的方法
class Musician{
public void play(Instrument i){
i.makeSound();
}
}
/*
//----使用以前的方法
class Musician{
Instrument i;
//构造方法
public Musician(){ //无参构造
}
public Musician(Instrument i){ //有参构造犯
this.i = i;
}
//play方法
public void play(){
i.makeSound();
}
}
*/
//1. 乐器类---父类
class Instrument{
// 乐器发声 ---方法
public void makeSound(){
}
}
//2. 三个子类
class Erhu extends Instrument{
public void makeSound(){ //方法重写
System.out.println("二胡的声音!!!");
}
}
class Piano extends Instrument{
public void makeSound(){ //方法重写
System.out.println("钢琴的声音!!!");
}
}
class Violin extends Instrument{
public void makeSound(){ //方法重写
System.out.println("小提琴的声音!!!");
}
}
❤️静态方法不谈覆盖
⭐️私有方法无法覆盖。
⭐️方法覆盖只是针对于“实例方法”,“静态方法覆盖”没有意义。(这是因为方法覆盖通常和多态联合起来)
⭐️总结两句话: 私有不能覆盖;静态不谈覆盖。
⭐️方法覆盖需要和多态机制联合起来使用才有意义。
Animal a = new Cat(); a.move();
要的是什么效果?
(1)编译的时候move()方法是Animal的。
(2)运行的时候自动调用到子类重写move()方法上。
⭐️假设没有多态机制,只有方法覆盖机制,你觉得有意义吗?
(1)没有多态机制的话,方法覆盖可有可无。(2)没有多态机制,方法覆盖也可以没有,如果父类的方法无法满足
(3)子类业务需求的时候,子类完全可以定义一个全新的方法。
(4)方法覆盖和多态不能分开。⭐️静态方法存在方法覆盖吗?
(1)多态自然就和对象有关系了。
(2)而静态方法的执行不需要对象。
(3)所以,一般情况下,我们会说静态方法“不存在”方法覆盖;不探讨静态方法的覆盖。
public class OverrideTest05{
public static void main(String[] args){
Animal a = new Cat(); //多态
a.doSome(); //就等价于Animal.doSome()
//最终结果还是调用父类的doSome;因为静态方法和对象无关。
// 虽然使用“引用.”来调用。但是实际运行的时候还是替换成:Animal.doSome()
}
}
class Animal{
// 父类的静态方法
public static void doSome(){
System.out.println("Animal的doSome方法执行!");
}
}
class Cat extends Animal{
// 尝试在子类当中对父类的静态方法进行重写
public static void doSome(){
System.out.println("Cat的doSome方法执行!");
}
}
❤️私有方法不能覆盖
因为方法私有化,我们只能在本类中进行访问;所以我们new对象调用的还是本类中的方法,而不是重写后的方法!
// 私有方法不能覆盖。
public class OverrideTest06{
// 私有方法;私有的只有当前的类中才能访问
private void doSome(){
System.out.println("OverrideTest06's private method doSome execute!");
}
// 入口
public static void main(String[] args){
// 多态
OverrideTest06 ot = new T();
ot.doSome();
//OverrideTest06's private method doSome execute!
//对于私有的无法覆盖,打印的还是原来父类的doSome;如果把private改成public就可以覆盖
}
}
/*
//--------在外部类中无法访问私有的。
class MyMain{
public static void main(String[] args){
OverrideTest06 ot = new T();
//错误: doSome() 在 OverrideTest06 中是 private 访问控制
//ot.doSome();
}
}
*/
// 子类:私有方法也能继承过来,但是不能直接访问,可以使用setter和getter方法
//也可以进行重写,但是访问权限只能那更高
class T extends OverrideTest06{ //继承
// 尝试重写父类中的doSome()方法
// 访问权限不能更低,可以更高。
public void doSome(){
System.out.println("T's public doSome method execute!");
}
}
❤️方法覆盖的返回值类型
⭐️在方法覆盖中,关于方法的返回值类型。
什么条件满足之后,会构成方法的覆盖呢?
1、发生具有继承关系的两个类之间。
2、父类中的方法和子类重写之后的方法:
具有相同的方法名、相同的形式参数列表、相同的返回值类型!
⭐️学习了多态机制之后:
“相同的返回值类型”可以修改一下吗?
对于返回值类型是基本数据类型来说,必须一致。
对于返回值类型是引用数据类型来说,重写之后返回值类型可以变的更小。例如:本来返回值是父类型(Animal),我们重写后返回值可以是子类型(Cat)
public class OverrideTest07{
public static void main(String[] args){
// 一般重写的时候都是复制粘贴。不要动。不要改。
}
}
//----------父类
class Animal{
//----------------------------------1.对于基本数据类型
//对于基本数据类型:父类和子类返回类型必须保持一致!
//1.1
public double sum(int a, int b){
return a + b;
}
//1.2
public long sum(int a, int b){
return a + b;
}
}
//----------子类
class Cat extends Animal{
//1.1---父类double,子类返回double没问题
public double sum(int a, int b){// 重写
return a + b;
}
//1.1---父类double,子类返回int;从大到小---错误
// 错误: Cat中的sum(int,int)无法覆盖Animal中的sum(int,int)
public int sum(int a, int b){// 重写
return a + b;
}
//1.2---父类int,子类返回long;从小到大---错误
public long sum(int a, int b){
return a + b;
}
}
//----------------------------------2.引用数据类型
//对于引用数据类型,返回类型范围变小了可以;变大了不可以!
// 父类
class MyClass1{
public Animal getAnimal(){ //父类是Animal(父类)类型
return null;
}
}
// 子类
class MyClass2 extends MyClass1{
//2.1 重写父类的方法,返回还是Animal,肯定可以
public Animal getAnimal(){
return null;
}
//2.2 重写的时候返回值类型由Animal变成了Cat,变小了。(可以,java中允许)
public Cat getAnimal(){ //子类是Cat(子类)类型,变小了;可以!
return null;
}
//2.3 重写的时候返回值类型由Animal变成了Object。变大了。(不行,java中不允许)
public Object getAnimal(){ 子类是Object(老祖宗)类型,变大了;不可以!
return null;
}
}