目录
1. 多态
1.1 多态的概念
1.2 多态实现条件
1.3 向上转型
1.3.1 直接赋值
1.3.2 方法传参
1.3.3 方法返回
1.3.4 向上转型的优缺点
1.4 重写
1.4.1 重写的条件
1.4.2 重写注意事项
1.4.3 重载与重写的区别
1.5 通过父类的引用,调用这个父类和子类重写的方法
2. 多态的应用——画图形
1. 多态
1.1 多态的概念
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。总的来说:同一件事情,发生在不同对象身上,就会产生不同的结果。比如吃饭这件事,猫吃猫粮,狗吃狗粮,人吃饭。再比如与人沟通这件事,中国人说中文,美国人说英语、法国人说法语。
在上一篇文章中(【JavaSE】继承那些事儿) ,提到过一句话,继承是对共性的抽取,实现代码的复用。就是对子类来说,它既有父类中的属性和方法,又有自己不同于其他子类,独特的属性和方法。而对父类里的方法来说,虽是共性,但依旧可以因为子类的不同,出现五花八门的形式,这就是多态。可以说,多态是针对父类而言的。
1.2 多态实现条件
在java中要实现多态,必须要满足如下几个条件,缺一不可:
1. 必须在继承体系下
2. 子类必须要对父类中方法进行重写
3. 通过父类的引用调用重写的方法多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。
1.3 向上转型
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
语法格式:父类类型 对象名 = new 子类类型()
Animal animal = new Cat("元宝",2);
写这么一个动物类,及其两个子类Cat类和Bird类:
class Animal{
protected String name;
protected int age;
protected double weight;
public Animal() {
}
public Animal(String name, int age, double weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public void eat(){
System.out.println(this.name+ " 在吃饭!");
}
public void sleep(){
System.out.println(this.name+" 在睡觉!");
}
}
class Cat extends Animal{
public Cat() {
}
public Cat(String name, int age, double weight) {
super(name, age, weight);
}
public void mew(){
System.out.println(this.name+" 喵~喵~喵~");
}
}
class Bird extends Animal{
public Bird() {
}
public Bird(String name, int age, double weight) {
super(name, age, weight);
}
public void fly(){
System.out.println(this.name+" 正在天空翱翔!");
}
}
那么该如何向上转型呢?有下面几种方式:
1.3.1 直接赋值
public class Text {
public static void main(String[] args) {
Animal animal1 = new Cat("Hello Kitty",1,7);
animal1.sleep();
animal1.eat();
//animal1.mew(); 编译报错
Animal animal2 = new Bird("AngryBird",3,5.5);
animal2.eat();
animal2.sleep();
}
}
按理说,等号两边的数据类型应该相同,否则会报错。但上述代码能运行的一个主要原因就是,两者有一个继承的关系。
1.3.2 方法传参
public class Text {
public static void function(Animal animal){
animal.eat();
animal.sleep();
}
public static void main(String[] args) {
Animal animal1 = new Cat("Hello Kitty",1,7);
function(animal1);
Animal animal2 = new Bird("AngryBird",3,5.5);
function(animal2);
}
}
1.3.3 方法返回
public class Text {
public static Animal function(String type){
if("鸟".equals(type)){
return new Bird("AngryBird",3,5.5);
}else{
return new Cat("Hello Kitty",1,7);
}
}
public static void main(String[] args) {
Animal animal1 = function("猫");
animal1.sleep();
animal1.eat();
Animal animal2 = function("鸟");
animal2.sleep();
animal2.eat();
}
}
向上转型导致的一个结果,就是父类的引用,是不能访问子类中父类所没有的属性及方法的,即子类特有的属性和方法。
1.3.4 向上转型的优缺点
向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。
1.4 重写
重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法的实现过程进行重新编写。重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
1.4.1 重写的条件
1. 方法名相同
2. 形参数目、顺序相同
3. 返回值相同
再次利用上面动物类及两个子类的例子:
class Animal{
......
......
public void eat(){
System.out.println(this.name+ " 在吃饭!");
}
public void sleep(){
System.out.println(this.name+" 在睡觉!");
}
}
class Cat extends Animal{
......
......
public void eat(){
System.out.println(this.name + " 在吃主人给的小零食~");
}
public void sleep(){
System.out.println(this.name+" 正在狗窝里呼呼大睡");
}
}
class Bird extends Animal{
......
......
public void eat(){
System.out.println(this.name+ " 叼着一只肥美的大青虫");
}
public void sleep(){
System.out.println(this.name+" 站在枝桠上睡大觉,沐浴着月光,微风吹拂着");
}
}
重写之后,我们的IDEA会出现以下的图标:
我们可以利用注解 @Override 来检查重写的语法规则是否正确:
@Override
public void eat(int a){
System.out.println(this.name + " 在吃主人给的小零食~");
}
像这样的代码,@Override 那行的代码就会有警告。
同样也可以让编译器自动生成:右击Generate -> Override Methods -> Animal 类中 -> eat()
1.4.2 重写注意事项
1. private 修饰的方法不能被重写
2. static 修饰的方法不能被重写
3. 子类重写方法的访问权限要大于等于父类被重写的方法
4. 被 final 修饰的方法不能被重写,此时这个方法被称作密封方法
5. 构造方法不能被重写
1.4.3 重载与重写的区别
区别 | 重写(override) | 重载(overload) |
参数列表 | 必须相同 | 必须不同 |
返回类型 | 必须相同【除非可以构成父子类关系】 | 可以不同 |
访问限定符 | 有一定的要求,见上文 | 可以不同 |
即:方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
【重写的设计原则】
对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容, 并且添加或者改动新的内容。例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅可以显示号码,还可以显示头像,地区等。在这个过程当中,我们不应该在原来老的类上进行修改,因为原来的类,可能还在有用户使用,正确做法是:新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我 们当今的需求了。
1.5 通过父类的引用,调用这个父类和子类重写的方法
接下来,让我们来看看,向上转型 -> 重写 之后,再一次使用父类引用调用重写的方法会怎么样:
public class Text {
public static void main(String[] args) {
Animal animal1 = new Cat("Hello Kitty",1,7);
animal1.sleep();
animal1.eat();
Animal animal2 = new Bird("AngryBird",3,5.5);
animal2.sleep();
animal2.eat();
}
}
输出:
Hello Kitty 正在狗窝里呼呼大睡
Hello Kitty 在吃主人给的小零食~
AngryBird 站在枝桠上睡大觉,沐浴着月光,微风吹拂着
AngryBird 叼着一只肥美的大青虫
向上转型 -> 重写 -> 父类引用调用子类重写父类的方法,这三个过程合在一起,会发生 动态绑定:编译的时候,会调用父类的方法,但是在运行的时候,帮我们调用了子类重写的方法。
查看编译完成之后的字节码文件,可以佐证我的说法:
只是动态绑定会帮我们调用子类的重写方法。
动态绑定是多态的基础!
动态绑定与静态绑定的区别:
静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。
2. 多态的应用——画图形
class Shape{
void draw(){
System.out.println("画图");
}
}
class Rectangle extends Shape{
void draw(){
System.out.println(" ⬜");
}
}
class Circle extends Shape{
void draw(){
System.out.println(" ⚪");
}
}
class Triangle extends Shape{
void draw(){
System.out.println(" 🔺");
}
}
class Flowers extends Shape{
void draw(){
System.out.println("~❀~");
}
}
public class Text {
public static void main(String[] args) {
Rectangle rect = new Rectangle();
Circle cir = new Circle();
Triangle tri = new Triangle();
Flowers flower = new Flowers();
Shape[] shapes = {flower,rect,flower,cir,flower,tri,flower,tri,flower,cir,flower,rect,flower};
for(Shape shape : shapes){
shape.draw();
}
}
输出
~❀~
⬜
~❀~
⚪
~❀~
🔺
~❀~
🔺
~❀~
⚪
~❀~
⬜
~❀~