第十三章 覆盖和多态

1 覆盖

1.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{

// 猫在移动的时候,我希望输出:“猫在走猫步”
}

 

1.2 构成方法覆盖的条件

回顾一下方法重载
什么时候考虑使用方法重载overload
当在一个类当中,如果功能相似的话,建议将名字定义为一样,这样代码美观,并且方便编程。
什么条件满足之后出能够构成方法重载overload
条件1:在同一个类当中
条件2:方法名相同
条件3:参数列表不同(个数、顺序、类型)

-------------------------------------------------------------------------------------

什么时候我们会考虑使用“方法覆盖”呢?
子类继承父类之后,当继承过来的方法无法满足当前子类的业务需求时,
子类有权利对这个方法进制重新编写,有必要进制“方法的覆盖”。

方法覆盖又叫做:
方法重写,英语单词叫做:Override, Overwrite, 都可以。
比较常见的:方法覆盖、方法重写、override

重要结论:
当子类对父类继承过来的方法进行“方法覆盖”之后,
子类对象调用该方法的时候,一定执行覆盖之后的方法。

当我们代码怎么编写的时候,在代码级别上构成了方法覆盖呢?
条件1:两个类必须要有继承关系
条件2:
重写之后的方法和之前的方法具有:
相同的返回值类型、
相同的方法名、
相同的形式参数列表
条件3:访问权限不能更低,可以更高。
条件4:重写之后的方法不能比之前的方法抛出更多的异常,可以更少。

注意事项:
注意1:方法覆盖只是针对方法,和属性无关。
注意2:私有方法无法覆盖。
注意3:构造方法不能被继承,所以构造方法也不能被覆盖。
注意4:方法覆盖只是针对于实例方法,静态方法覆盖没有意义。


public class OverrideTest02{
public static void main(String[] args){

Bird b = new Bird();
b.move();

Cat c = new Cat();
c.move();
}
}

class Animal{

}

class Bird extends Animal{

// 对move方法进行方法覆盖,方法迭重写,override
// 最好将父类中的方法原封不动的复制过来。(不建议手动编写)
// 方法覆盖,就是将继承过来的那个方法给覆盖掉了。
/*
public void move(){
System.out.println("鸟儿在飞翔");
}
*/

// protected表示爱保护的。没有public开放
// 错误:正在尝试分配更低的访问权限;以前为public
/*
protected void move(){
System.out.println("鸟儿在飞翔");
}
*/
}

class Cat extends Animal{

public void move(){
System.out.println("猫在走猫步");
}
}

 

1.3 方法覆盖经典案例


// 方法覆盖比较经典的案例
// 一定要注意:方法覆盖/重写的时候,建议将父类的方法复制粘贴,这样比较保险。

public class OverrideTest03{
public static void main(String[] args){

// 创建中国人对象
// ChinaPeople p1 = new ChinaPeople("张三"); // 错误原因:没有这样的构造方法
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(this.getName() + "speak English");
}
}

 

1.4 覆盖toString方法


/*
关于Object类中的toString()方法
1、toString()方法的作用是什么?
作用:将“java字符串”转换成字符串的形式。
2、Object类中toString()方法的默认实现是什么?
public String toString(){
return getClass().geName() + "@" + Integer.toHexString(hashCode());
}
toString: 方法名的意思是转换成String
含义:调用一个java对象的toString()方法就可以将java对象转换成字符串形式。
3、那么toString()方法给的默认实现够用吗?
*/

public class OverrideTest04{
public static void main(String[] args){
// 创建一个日期对象
MyDate t1 = new MyDate();
// 调用toString()方法(将对象转换成字符串形式。)
// 问:你对这个输出结果满意吗?希望输出:xxx年xxx月xxx日
// 什么时候使用方法覆盖?
// 重写MyDate的toString()方法之前的结果
// System.out.println(t1.toString()); // MyDate@4517d9a3

// 重写MyDate的toString()方法之后的结果
System.out.println(t1.toString());

// 当输出一个引用的时候,println方法会自动调用引用的toString()方法
System.out.println(t1);

MyDate t2 = new MyDate(2000, 2, 15);
System.out.println(t2);

// 创建学生对象
Student s = new Student(111, "xiaohong");
// 重写toString()方法之前
// System.out.println(s); // Student@548c4f57
// 重写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;
}

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;
}

// 从Object类中继承过来的那个toString()方法已经无法满足我业务需求了。
// 我在子类MyDate中有必要对父类的toString()方法进行覆盖/重写。
// 我的业务需求是:调用toString()方法进行字符串转换的时候,希望转化的结束是:xxx年xx月xx日
public String toString(){
return year + "年" + month + "月" + day + "日";
}
}

class Student{
int no;
String name;
public Student(int no, String name){
this.no = no;
this.name = name;
}

public String toString(){
return "学号: " + no + ", 姓名: " + name;
}
}

 

1.5 总结

1 什么时候考虑使用方法覆盖?
父类中的方法无法满足子类的业务需求,子类有必要对继承过来的方法进行覆盖。

2 什么条件满足的时候构成方法覆盖?
有继承关系
具有相同的方法名、返回值类型、形式参数列表
访问权限不能更低,只能更高
抛出异常不能更多。

3 关于Object类中toString()方法的覆盖?
toString()方法存在的作用是:将java对象转换成字符串形式。
大多数的java类toString()方法都是需要覆盖的。因为Object类中提供的toString()方法输出的是一个java对象的内存地址。

4 方法重载和方法覆盖有什么区别?
方法重载发生在同一个类当中。
方法覆盖是发生在具有继承关系的父子类之间。

方法重载是一个类中,方法名相同,参数列表不同。

方法覆盖是具有继承关系的父子类,并且重写之后的方法和必须和重写之前的方法一致:
方法名一致、参数列表一致、返回值类型一致。

 

 

2 多态

2.1 向上和向下转型、instanceof关键字

Animal.java


// 动物类:父类

public class Animal{
public void move(){
System.out.println("动物在移动");
}
}

Dog.java


// Dog并没有继承Animal
// Dog不是Animal的子类

public class Dog{

}

Cat.java


// 猫类,子类

public class Cat extends Animal{

// 移动的方法
public void move(){
System.out.println("猫在走在猫步");
}

// 猫除了move之外,应该有自己特有的行为,例如抓老鼠。
// 这个行为是子类型对象特有的方法。
public void catchMouse(){
System.out.println("正在抓老鼠");
}
}

Bird.java


// 鸟儿类

public class Bird extends Animal{

public void move(){
System.out.println("鸟儿在飞翔");
}
}

Test01.java


/*
多态的基础语法:
1、学习多态基础语法之前,我们需要普及两个概念:
第一个:向上转型
子 ---> 父(自动类型转换)
第二个:向下转型
父 ---> 子(强制类型转换,需要加强制类型转换符)

注意:
java中允许向上转型,也允许向下转型。
无论是向上转型,还是向下转型,两种类型之间必须有继承关系,没有继承关系编译器报错。

以后在工作过程中,和别人聊天的时候,要专业一些,说向上转型和向下转型,不要说自动类型转换,也不要说强制类型转换。
因为自动类型转换和强制类型转换是使用在基本数据类型方面的。
在引用类型转换这里只有向上和向下转型。


2、多态指的是:
父类型引用指向子类型对象。
包括编译阶段和运行阶段。
编译阶段:绑定父类的方法。
运行动态绑定子类型对象的方法。

3、java中只有“类名.”和“引用.”

4、什么必须使用“向下转型”?
不要随便做强制类型转换。
当你需要访问的是子类对象中“特有”的方法,此时必须进行向下转型。
*/

public class Test01{
public static void main(String[] args){
Animal a1 = new Animal();
a1.move();

Cat c1 = new Cat();
c1.move();

Bird b1 = new Bird();
b1.move();

// 代码可以这样写吗?
/*
1、Animal和Cat之间有继承关系吗?有的。
2、Animal是父类,Cat是子类。
3、Cat is a Animal,这句话能不能说通?能。
4、经过测试得知:java中支持这样的一个语法。
父类型的引用允许指向子类型的对象。
Animal a2 = new Cat();
a2就是父类型的引用。
new Cat()是一个子类型的对象。
允许a2这个父类型的引用指向子类型的对象。
*/
Animal a2 = new Cat();
Animal a3 = new Bird();

// 没有继承关系两个类型之间存在转型吗?
// 错误:不兼容的类型,Dog无法转换为Animal
// Animal a4 = new Dog();

// 调用a2和a3的move()方法
/*
什么是多态?
多种形态,多种状态。
分析:a2.move();
java程序分为编译阶段和运行阶段。
先来分析编译阶段:
对于编译器来说,编码器只知道a2的类型是Animal,所以编译器在检测语法的时候,会去Animal.class 字节码文件中找move()方法,找到了,绑定上move()方法,编译通过,静态绑定成功。(编译阶段属于静态绑定)
再来分析运算阶段:
运行阶段的时候,实际上在堆内存中创建java对象是Cat对象,所以move的时候,真正参与move的对象是一只猫,所以运行阶段会动态执行Cat对象的move()方法。这个过程属于运行阶段绑定。(运行阶段绑定属于动态绑定)

多态表示多种形态:
编译的时候一种形态。
运行的时候另一种形态。
*/
a2.move();
a3.move();

// ==================================================================================
Animal a5 = new Cat();  // 底层对象是一只猫.
// 分析这个程序能否编译和运算
// 分析程序一定要分析编译阶段的静态绑定和运行阶段的动态绑定。
// 只有编译通过的代码才能运行。没有编译,根本轮不到运行。
// 错误:找不到符号
// 因为编译器只知道a5的类型是Animal,去Animal.class文件中找catchMouse()方法,、
// 结果没有找到,所以静态绑定失败,编译报错,无法运行,语法不合法。
// a5.catchMouse();

// 假设代码写到这里,我非要调用catchMouse()方法怎么办?
// 这个时候就必须使用“向下转型了”(强制类型转换)
// 以下代码为啥没报错?a5是Animal类型,转成Cat,Animal和Cat之间存在继承关系,所以没有报错
Cat x = (Cat)a5;
x.catchMouse();

// 向下转型有风险吗?
Animal a6 = new Bird();  // 表面上a6是一个Animal,运行的时候实际上是一只鸟儿。
// Cat y = (Cat)a6;
/*
分析以下程序,编译报错还是运行报错?
编译器检测到a6这个引用是Animal类型,
而Animal和Cat之间存在继承关系,所以可以向下转型。
编译没毛病。

运行阶段,堆内存实际创建的对象是:Bird对象。
在实际运行过程中,拿着Bird对象转换成Cat对象就不行了,因为Bird和Cat之间没有继承关系。

运行时出现异常,这个异常和空指针异常一样非常重要,也非常经典。
java.lang.ClassCastException:类型转换异常。
*/
// y.catchMouse();

// 怎么避免ClassCaseException异常的发生?
/*
新的内容,运算符:
instanceof
第一:instanceof可以在运行阶段动态判断引用指向的对象的类型。
第二:instanceof的语法:
(引用 instanceof 类型)
第三:instanceof运算符的运算结果只能是:true/false
第四:c是一个引用,c变量保存了内存地址指向了堆中的对象。
假设(c instance of Cat)为true表示:
c引用指向的堆内存中的java对象是一个Cat。
假设(c instance of Cat)为false表示:
c引用指向的堆内存中的java对象不是一个Cat。

程序员要养成一个好习惯:
任何时候,任何地点,对类型进行向下转型时,一定要使用instanceof运算符进行判断。(java规范中要求的)
这样可以很好的避免:ClassCaseException
*/
System.out.println(a6 instanceof Cat);
if(a6 instanceof Cat){  // 如果a6是一只Cat
Cat z = (Cat)a6;  // 再进行强制类型转换
z.catchMouse();
}

}
}

 

2.2 为什么要使用instanceof

Test03.java


public class Test03{
public static void main(String[] args){
// main方法是程序员A负责编写。
AnimalTest at = new AnimalTest();
at.test(new Cat());
at.test(new Bird());
}
}

AnimalTest.java

public class AnimalTest{

// test方法是程序员B负责编写。
// 这个test()方法的参数是Animal
public void test(Animal a){
// 你写的这个方法别人会去调用
// 别人调用的时候可能给你test()方法传过来一个Bird,当然也可能传过来一个Cat。
// 对于我来说,我不知道你调用的时候给我传过来一个啥。
if(a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse();
}else if(a instanceof Bird){
Bird b = (Bird)a;
b.sing();
}
}
}
 

 

2.3 多态回顾

1 向上转型和向下转型的概念。
向上转型:子 ---> 父(upcasting)
又被称为自动类型转换:Animal a = new Cat();

向下转型:父 ---> 子(downcasting)
又被称为强制类型转换:Cat c = (Cat)a; 需要添加强制类型转换符。
什么时候需要向下转型:
需要调用或者执行子类对象中特有的方法。
必须进行向下转型,才可以调用。
向下转型有风险:
容易出现ClassCaseException异常
怎样避免这个风险?
instanceof运算符,可以在程序运行阶段动态的判断某个引用指向的对象是否为某一种类型。
养成好习惯,向下转型之前一定要使用instanceof运算符进行判断。

不管是向上转型还是向下转型,首先它们之间必须有继承关系,这样编译器才不会报错。


2 什么是多态
多种形态,多种状态,编译和运算有两个不同的状态。
编译期叫做静态绑定
运行期叫做动态绑定
Animal a = new Cat();
// 编译的时候编译器发现a的类型是Animal,所以编译器会去Animal类中找move()方法,找到了,绑定,编译通过。但是运行的时候和底层堆内存当中的实际对象有关。
// 真正执行的时候会自动调用“堆内存中真实对象”的相关方法。
a.move();

多态的典型代码:父类型的引用指向子类型的对象。(java中允许这样写代码)。


3 什么时候必须进行向下转型?
调用子类对象上特有的方法时。

 

2.4 多态在实际开发中的作用

非常重要
多态在开发中的作用是:
降低程序的耦合度,提高程序的扩展力。
public class master{
public void feed(Dog d){}
public void feed(Cat c){}
}
以上的程序中表示:Master和Dog以及Cat的关系很紧密。导致扩展力很差。

public class Master{
public void feed(Pet pet){}
}
以上的程序表示:Master和Dog以及Cat的关系就脱离了,Master关注的是Pet类,这样Master和Dog以及Cat耦合度就降低了,提高了软件的扩展性。


面向对象的三大特征:
封装、继承、多态
真的是一环扣一环。
有了封装,有了这种整体的概念之后。
对象和对象之间产生了继承。
有了继承之后,才有了方法的覆盖和多态。


这里提到了一个软件开发原则:
七大原则最基本的原则:OCP(对扩展开放,对修改关闭)
目的是:降低程序耦合度,提高程序扩展力。
面向抽象编程,不建议面向具体编程。

Dog.java


// 宠物狗类

public class Dog extends Pet{
// 吃
public void eat(){
System.out.println("狗狗喜欢啃骨头");
}
}

Parrot.java


// OCP: 对扩展开放,对修改关闭
public class Parrot extends Pet{
// 重写eat方法
public void eat(){
System.out.println("鹦鹉喜欢吃小虫子");
}
}

Cat.java


public class Cat extends Pet{

// 吃
public void eat(){
System.out.println("猫咪喜欢吃鱼");
}
}

Pet.java


// 所有宠物的父类
public class Pet{

// 吃的行为(这个方法可以不给具体的实现)
public void eat(){

}
}

Master.java


// 主人类

public class Master{

// 假设主人起初的时候只是喜欢养宠物狗狗
// 喂养宠物狗狗
/*
public void feed(Dog d){
d.eat();
}

// 由于新的需求产生,导致我们不得不去修改Master这个类的代码。
public void feed(Cat c){
c.eat();
}
*/

// 能不能让Master主人这个类以后不再修改了
// 即使主人又喜欢养其它宠物了,Master也不需要修改。
// 这个时候就需要使用:多态机制。
// 最好不要写具体的宠物类型,这样会影响程序的扩展性。
public void feed(Pet pet){  
// 编译的时候,编译器发现pet是Pet类,会去Pet类中找eat()方法,结果找到了,编译通过了
// 运行的时候,底层实际对应的对象是什么,就自动调用到该对象对应的eat()方法上。
// 这就是多态的使用。
pet.eat();
}
}

/*
注意这里的分析:
主人起初的时候只喜欢养宠物狗狗
随着时间的推移,主人又喜欢养“猫咪”
在实际的开发中这就表示客户产生了新需求。
作为软件的开发人员来说,必须满足客户的需求。
我们怎么去满足客户的需求呢?
在不使用多态机制的前提下,目前我们只能在Master类当中添加一个新的方法。

思考:软件在扩展新需求的过程当中,修改Master这个类有什么问题?
一定要记住:软件在扩展的过程当中,修改的越少越好。
修改的越多,你的系统的当前稳定性就越差,未知的风险就越多。
其实这里涉及到一个软件的开发原则:
软件开发原则有七大原则(不属于java,这个开发原则属于整个软件业):
其中有一条最基本的原则:OCP(开闭原则)

什么是开闭原则?
对扩展开放,对修改关闭。
在软件的扩展过程当中,修改的越少越好。

什么是软件扩展性?
假设电脑中的内存条部件坏了,我们可以买一个新的内存条插上,直接使用。
这个电脑的设计就考虑了“扩展性”。内存条的扩展性很好。

面向父类型编程,面向更加抽象进行编程,不建议面向具体编程。
因为面向具体编程会让软件扩展力很差。
*/

Test.java


/*
测试多态在开发中的作用
*/

public class Test{
public static void main(String[] args){
// 创建主人对象
Master zhangsan = new Master();

// 创建宠物对象
Dog heiBei = new Dog();
// 主人喂
zhangsan.feed(heiBei);

// 创建宠物对象
Cat xiaoHua = new Cat();
// 主人喂
zhangsan.feed(xiaoHua);

// 创建宠物对象
Parrot parrot = new Parrot();
// 主人喂
zhangsan.feed(parrot);
}
}

 

2.5 习题

/*
编写程序实现乐手弹奏乐器。乐手可以弹奏不同的乐器从而发出不同的声音。
可以弹奏的乐器包括二胡、钢琴和琵琶。
实现思路及关键代码:
1) 定义乐器类Instrument,包括方法makesound ()
2) 定义乐器类的子类:二胡Erhu、钢琴piano和小提琴Violin
3) 定义乐手类Musician,可以弹奏各种乐器play (Instrument i)
4) 定义测试类,给乐手不同的乐器让他弹奏
*/

public class Homework{
public static void main(String[] args){

// 创建各种乐器对象
Erhu erhu = new Erhu();
Piano piano = new Piano();
Violin violin = new Violin();

// 创建乐手对象
Musician musician = new Musician();
musician.play(erhu);
musician.play(piano);
musician.play(violin);
}
}

// 第一种写法
/*
// 乐手
class Musician{
// 实例变量
Instrument instrument;

// 构造方法
public Musician(){}
public Musician(Instrument instrument){
this.instrument = instrument;
}

// play方法
public void play(){
instrument.makeSound();
}
}
*/

// 第二种写法
// 乐手
class Musician{
public void play(Instrument i){
i.makeSound();
}
}

// 乐器父类
class Instrument{
// 乐器发声
public void makeSound(){

}
}

// 子类
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("小提琴的声音");
}
}

 

2.6 静态方法不存在方法覆盖

1、方法覆盖需要和多态机制联合起来使用才有意义。
Animal a = new Cat();
a.move();
要的是什么效果?
编译的时候move()方法是Animal的。
运行的时候自动调用到子类重写move()方法上。

假设没有多态机制,只有方法覆盖机制,你觉得有意义吗?
没有多态机制的话,方法覆盖可有可无。
没有多态机制,方法覆盖也可以,如果父类的方法无法满足子类业务需求的时候,子类完全可以定义一个全新的方法。


2、静态方法存在方法覆盖吗?
多态自然就和对象有关系了。
而静态方法的执行不需要对象
所以,一般情况下,我们会说静态方法“不存在”方法覆盖。


public class OverrideTest05{
public static void main(String[] args){
// 静态方法可以使用“引用.”来调用吗?可以
// 虽然使用“引用.”来调用,但是和对象无关。
Animal a = new Cat();  // 多态
// 静态方法和对象无关
// 虽然使用“引用.”来调用,但是实际运行的时候还是:Animal.doSome();
a.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方法执行");
}
}

 

2.7 私有方法不能覆盖

// 经过测试,私有方法不能覆盖
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
}
}

// 子类
class T extends OverrideTest06{
// 尝试重写父类中的doSome()方法
// 访问权限不能更低,可以更高
public void doSome(){
System.out.println("T's public doSome method execute");
}
}

 

2.8 在方法覆盖中,关于方法的返回值类型

什么条件满足之后,会构成方法的覆盖呢?
1 发生在具有继承关系的两个类之间
2 父类中的方法和子类重写之后的方法:
具有相同的方法名、相同的形式参数列表,相同的返回值类型。

学习了多态机制之后:
“相同的返回值类型”可以修改一下吗?
对于返回值类型是基本数据类型来说,必须一致。
对于返回值类型是引用数据类型来说,重写之后返回值类型可以变的更小(但意义不大,实际开发中没有人会这样写)



public class OverrideTest07{
public static void main(String[] args){
// 一般重写的时候都是复制粘贴,不要动,不要改。
}
}

class Animal{
/*
public double sum(int a, int b){
return a + b;
}

public int sum(int a, int b){
return a + b;
}
*/
}

class Cat extends Animal{
// 重写
// 错误: Cat中的sum(int,int)无法覆盖Animal中的sum(int,int)
/*
public int sum(int a, int b){
return a + b;
}

public double sum(int a, int b){
return a + b;
}

public long sum(int a, int b){
return a + b;
}
*/
}

// 父类
class Myclass1{
public Animal getAnimal(){
return null;
}
}

// 子类
class Myclass2 extends Myclass1(){
// 重写子类的方法
/*
public Animal getAnimal(){
return null;
}
*/

// 重写的时候返回值类型由Animal变成了Cat,变小了。(可以,java中允许)
/*
public Cat getAnimal(){
return null;
}
*/

// 重写的时候返回值类型由Animal变成了Object。变大了。(不行,java中不允许)
/*
public Object getAnimal(){
return null;
}
*/

}
 

 

 

 

 

posted @   路走  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示