面向对象之向上转型
一、向上转型
把一个子类的引用转换为父类的引用
public class Test {
public static void main(String[] args) {
// Cat cat = new Cat();
// Animal animal = null;
//向上转型--把一个子类的引用转成父类的引用
// animal = cat;
//上面的代码可以合并到一起
// Animal animal = new Cat();
// Animal animal2 = new Bird();
//向上转型也可能发生在方法传参的过程中
//方法传参本质上也就是在进行“赋值”操作
func(new Cat());
//向上转型,也可能发生在方法返回的时候
Animal animal = func2();
}
public static void func(Animal animal){
}
//方法返回和方法传参,本质上都是“赋值”
public static Animal func2(){
// return new Cat();
//拆解
Cat cat = new Cat(); //new一个子类的实例
Animal animal = cat; //向上转型
return animal; //返回一个animal类型的引用
}
}
父类的引用,只能访问到父类中的属性和方法。
方法也是同理
二、动态绑定
1、如果父类中包含的方法在子类中有对应的同名同参数的方法,就会进行动态绑定。
2、此处的“静态”和“动态”分别指的是“编译期”和“运行期”,和static无关。
3、运行时决定调用那个方法
public class Animal {
public String name = "小动物";
public void est(String food){
System.out.println("Animal 正在吃 " + food);
}
}
public class Test {
public static void main(String[] args) {
animal.est("鱼");
}
}
如果eat方法只存在父类中,此时调用的eat就是父类的eat方法(此时不涉及动态绑定)
-
如果eat方法只在子类中存在,此时调用的eat就会编译报错(此时也不涉及动态绑定)
-
如果eat方法在父类和子类中都存在,并且参数都相同,此时就会涉及到“动态绑定”,即在程序运行时,看animal指向的目标是父类实例还是子类实例;若是指向父类实例就调用父类版本的eat,若是指向子类实例就调用子类版本的eat。
-
animal此引用指向的是父类还是子类实例,在运行时才可以确实。
-
若eat方法在父类和子类中都存在,并且参数不相同,此时调用eat不涉及“动态绑定”,此时相当于一个“方法重载”。此规则和前面最开始的两条类似–根据调用方法时传入的参数和类型判断父类中是否有匹配的方法,如果不存在就编译出错(编译器确定)
-
上述的动态绑定规则,是基于编译器和JVM实现,其另有术语,即:“方法重写—override”
-
方法限定符会影响到动态绑定。比如:子类的中的某个方法时private,这个时候外部根本看不到子类的方法,此时不涉及动态绑定。
-
重写涉及到的子类和父类方法和属性都相同。
-
重载是在同一个作用域中,方法相同,属性不同。
public class Animal {
public String name = "小动物";
public void eat(String food){
System.out.println("Animal 正在吃 " + food);
}
}
public class Cat extends Animal{
public String gender = "公猫";
private void eat(String food){
System.out.println("Cat 正在吃 " + food);
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Cat();
animal.eat("鱼");
}
}
报错提示
父类的方法eat是public
子类的方法eat是private(private的访问权限比public低)
一般情况下,在实际工作过程中,都是两个public的方法进行“动态绑定”/“方法重写”
1 方法重写与方法重载
方法重写:子类实现父类的重名方法,且参数、类型、个数完全相同。这种实现过程叫做重写/覆写/覆盖(override)实际调用过程中需要
方法重载:同一个作用域中,同名方法不同属性。实际调用过程中需要根据属性来调用方法。
2 多态
多态是一种程序设计的思想方法,具体的语法体现:向上转型、方法重写、动态绑定;通俗的讲,一个引用,对应到多种不同的形态(不同类型的实例)
public class Shape {
public void draw(){
//把当前的形状打印
}
}
public class Circle extends Shape{
public void draw(){
System.out.println("○");
}
}
public class Rect extends Shape {
public void draw(){
System.out.println("□");
}
}
public class Flower extends Shape {
public void draw(){
System.out.println("❀");
}
}
public class Test {
public static void main(String[] args) {
//多态
//创建若干子类实例
Shape shap1 = new Rect();
Shape shap2 = new Circle();
Shape shap3 = new Flower();
draw(shap1);
draw(shap2);
draw(shap3);
}
public static void draw(Shape shape){
shape.draw();
}
}
运行结果:
此时实现draw方法,不需要知道shape的类型。
- 1、多态的这种设计思想,是“封装”的更进一步。封装的目的是让使用者知道类的实现细节就可以使用,但是使用者必须知道此类的类型;
- 相反,使用多态的时候不需要知道此类的实现细节,更不需要知道此类的具体类型,只需要知道该类有一个draw方法即可。
- 2、方便拓展–未来若是需要新增一个形状,创建一个新的子类即可,并且让子类也去重写draw方法,类的调用者不需要做出较大修改。
public class Circle extends Shape{
public void draw(){
System.out.println("○");
}
}
public class Test {
public static void main(String[] args) {
//多态
//创建若干子类实例
Shape shap1 = new Rect();
Shape shap2 = new Circle();
Shape shap3 = new Flower();
draw(shap1);
draw(shap2);
draw(shap3);
}
public static void draw(Shape shape){
shape.draw();
}
}
新增Triangle类时,draw方法不用做过多修改。
运行结果:
- 3、减少分支语句(if / else switch / case)
若无多态,则上述代码需要用以下方法实现:
Shape[] shapes = {
new Rect(),
new Circle(),
new Flower(),
new Triangle(),
};
for(Shape shape : shapes){
if(shape.type == "圆形"){
//打印圆形
}
else if(shape.type == "方形"){
//打印方形
}
else if(shape.type == "小花"){
//打印小花
}
else if(shape.type == "三角形"){
//打印三角形
}
else {
//...
}
}