JavaSE学习之--继承和多态(二)+https://developer.aliyun.com/article/1413490
3.方法返回
方法的返回类型可以是类 ,通过将方法的返回值设置为父类引用,让返回的对象被父类引用引用!!!
// 父类 class Animal{ public void eat() { System.out.println("eating"); } }; class Cat extends Animal{ String name; int age; public Cat(String name, int age) { this.name = name; this.age = age; } @Override public void eat() { System.out.println("cat is eating!"); } } class Dog extends Animal{ String name; int age; public Dog(String name, int age) { this.name = name; this.age = age; } @Override public void eat() { System.out.println("dog is eating!"); } }; //class Cat extends Animal{}; public class Test { public static void eat() { System.out.println("我在吃饭"); } // 父类引用可以做返回值,还是让父类引用指向返回的对象 public static Animal buyAnimal(String var) { if("狗".equals(var)) { return new Dog("小狗",2); } else if ("猫".equals(var)) { return new Cat("小米",20); }else { return null; } } public static void main(String[] args) { Animal animal1 =buyAnimal("狗"); Animal animal2 =buyAnimal("猫"); animal1.eat(); animal2.eat(); }
3.向上转型的优点:
1.实现代码的通用性,实现多态性
通过父类引用子类对象,我们可以创建出更通用的代码,不需要去关注具体子类的类型
2.可以避免很多类型检查和强制类型转换
class Animal { public void makeSound() {}; } class Cat extends Animal { @Override public void makeSound() { System.out.println("喵喵叫!"); } } class Dog extends Animal { @Override public void makeSound() { System.out.println("汪汪叫!"); } } public class Test1 { public static void main(String[] args) { Animal[] animals = new Animal[2]; animals[0] = new Cat(); animals[1] = new Dog(); for (Animal animal: animals) { // 不适用向上转型 // 需要进行类型检查和强制类型转换 if (animal instanceof Dog) { Dog dog = (Dog) animal; dog.makeSound(); } else if (animal instanceof Cat) { Cat cat = (Cat) animal; cat.makeSound(); } } // 使用向上转型 // 不需要关注具体的子类对象,代码更通用 for (Animal animal2: animals) { animal2.makeSound(); } } }
对于这个代码,在 animals[0] = new Cat();实际上就发生了向上转型,animals[0]代表一个父类引用,new Cat()代表创建了一个新的Cat对象
4.向上转型的缺点:
无法使用子类特有的方法
如果想通过父类引用去使用子类特有的方法,就要进行“向下转型”,也就是使父类引用发生强制类型转换!
2.向下转型:
在很多时候子类有其特有的方法,而又不能通过重写来调用,这时候就需要通过向下转型来调用子类特有的方法,向下转型就是将父类引用强制类型转化为子类对象
class Animal{}; class Dog extends Animal{ public void bark() { System.out.println("汪汪叫!"); } } public class testdemo1 { public static void main(String[] args) { Animal animal1 = new Dog(); // 向下转型 Dog dog1 = (Dog)animal1; dog1.bark();// 输出汪汪叫! } }
但向下转型是一个很“危险”的操作,因为父类引用是一个较大的范围,你强制转化的类型是一个小范围,会出现意想不到的错误;同时,如果多个类继承于一个父类,要注意向下转型时不要转错类型!如果转换错误,编译器会报错:
所以,为了避免这种错误,需要利用关键字instanceof来检验当前引用所指向的类型
class Animal{}; class Dog extends Animal{}; class Cat extends Animal{}; public class Test { public static void main(String[] args) { // 向上转型 Animal animal1 = new Dog(); // 父类引用 引用子类类型 if(animal1 instanceof Dog) {// 利用instanceof运算符进行引用类型检查 // 强制类型转化 Dog dog = (Dog)animal1; System.out.println("类型检查成功"); }else { System.out.println("ClassCastException!!!"); } } }
3.动态绑定
动态绑定是面向对象编程非常重要的一个概念,也被叫做运行时绑定或运行多态性。他是指在运行时才确定对象的实际类型,并根据实际类型调用相应的方法,在通俗一点说就是只有在运行时我才知道引用的对象是谁,我该调用的方法是谁,这之前谁都不知道!!!
class Animal { public void makeSound() {}; } class Cat extends Animal { @Override public void makeSound() { System.out.println("喵喵叫!"); } } class Dog extends Animal { @Override public void makeSound() { System.out.println("汪汪叫!"); } } public class Test1 { public static void main(String[] args) { Animal animal = new Dog();// 动态绑定 animal.makeSound();// 实际调用的是Dog的方法 只有在运行时才知道实际对象是Dog } }
4.方法重写
重写(override):是子类对父类非static,final,private修饰,非构造方法的方法重写;通过重写,可以在子类内部实现属于子类的行为;
1.重写的规则:
1.被重写的父类方法不能被static,final,private修饰,不能是构造方法
2.重写的方法必须要和父类方法保持一致,即返回值,方法名,参数列表都要完全相同
3.子类的重写方法的权限要>=父类被重写方法的权限,比如父类方法的权限是public,那子类重写的方法就不能是protected!
4.子类中重写的方法可以被@Override 注解来显式指定,有了这个注解,可以多一层检验,比如父类的方法名是eat,而在子类中却写成ate,此时编译器就会报错(因为要求子类的方法名要和父类被重写方法的名称一致!)
2.重写与重载的区别:
重载是类方法多态性的体现,重写是子类与父类之间多态性的体现
方法重载是一种静态绑定,即在编译过程中就知道对应的方法(根据用户的传参)
方法重写是一种动态绑定,只有在运行时才能明确调用的方法
5.多态
再说回多态,以上内容都是为了理解多态而阐述的,所谓多态就是子类调用同一个父类方法时所产生的行为不同的思想,以下是多态的条件:
1.必须存在父类与子类的继承关系
2.必须有方法的重写
3.必须通过父类的引用来调用方法
多态的体现:在程序运行时,引用对象的不同,所产生的结果也不同!
1.多态的优点:
1.降低圆圈复杂度
圆圈复杂度是反应一段代码理解难易程度的表示,如果一段代码平铺直叙,那他的圆圈复杂度就很低,此代码易于理解,但如果代码中含有大量的条件语句,循环语句,那么此代码的圆圈复杂度就很高,不易于理解(要判断的东西太多)
class Shape { public void drawMap() {}; } class Rect extends Shape { @Override public void drawMap() { System.out.println("矩形"); } } class Flower extends Shape { @Override public void drawMap() { System.out.println("❀"); } } class Cycle extends Shape { @Override public void drawMap() { System.out.println("⚪"); } } public class Test2 { // 依次打印⚪矩形⚪矩形花 public static void main(String[] args) { // 1.不使用多态 要判断子类的具体类型 Rect rect = new Rect(); Flower flower = new Flower(); Cycle cycle = new Cycle(); String[] shapes = {"cycle","rect","cycle","rect","flower"}; for (String shape:shapes) { if(shape.equals("cycle")) { cycle.drawMap(); } else if (shape.equals("rect")) { rect.drawMap(); }else { flower.drawMap(); } } // 2.使用多态 Shape[] shapes = {new Cycle(),new Rect(),new Cycle(),new Rect(),new Flower()}; for (Shape shape: shapes) { shape.drawMap(); } } }
2.可拓展性强
比如对于shape类,现在有一个新的子类Triangle,可以直接新创建一个Triangle类,让其包含drawMap方法;如果不使用多态,则需要在打印时多添加一个if-else语句
2.多态的缺陷:代码运行效率低
3.多态的注意事项:
1.属性不具有多态性,通过父类引用只能访问到父类的属性,而无法访问到子类的属性
2.构造方法没有多态性
先来看如下代码:
class B { public B() { // do nothing func(); } public void func() { System.out.println("B.func()"); } } class D extends B { private int num = 1; @Override public void func() { System.out.println("D.func() " + num); } } public class Test3 { public static void main(String[] args) { D d = new D(); } } // 执行结果:D.func() 0
实例化d对象时,会先调用父类的构造方法,父类的构造方法中调用了func方法,此时会触发动态绑定,调用子类的func方法,而此时 private int num = 1;这段代码并未被执行,也就是说num还未被初始化,未被初始化则被赋值为0,所以打印:D.func() 0
结论: "用尽量简单的方式使对象进入可工作状态", 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触 发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.