多态

多态的理解

多态的格式

  1. 父类类型 变量名 = new 子类/实现类构造器;
  2. 变量名.方法名();

多态的使用场景
如果没有多态,在下图中register方法只能传递学生对象,其他的Teacher和administrator对象是无法传递给register方法方法的,在这种情况下,只能定义三个不同的register方法分别接收学生,老师和管理员。3.多态%26包%26final%26权限修饰符 - 图1
有了多态之后,方法的形参就可以定义为共同的父类Person。
要注意的是:

  • 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
  • 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象(后面会学)。
  • 而且多态还可以根据传递的不同对象来调用不同类中的方法。

3.多态%26包%26final%26权限修饰符 - 图2
代码示例:

  1. 父类:
  2. public class Person {
  3. private String name;
  4. private int age;
  5. 空参构造
  6. 带全部参数的构造
  7. getset方法
  8. public void show(){
  9. System.out.println(name + ", " + age);
  10. }
  11. }
  12. 子类1
  13. public class Administrator extends Person {
  14. @Override
  15. public void show() {
  16. System.out.println("管理员的信息为:" + getName() + ", " + getAge());
  17. }
  18. }
  19. 子类2
  20. public class Student extends Person{
  21. @Override
  22. public void show() {
  23. System.out.println("学生的信息为:" + getName() + ", " + getAge());
  24. }
  25. }
  26. 子类3
  27. public class Teacher extends Person{
  28. @Override
  29. public void show() {
  30. System.out.println("老师的信息为:" + getName() + ", " + getAge());
  31. }
  32. }
  33. 测试类:
  34. public class Test {
  35. public static void main(String[] args) {
  36. //创建三个对象,并调用register方法
  37. Student s = new Student();
  38. s.setName("张三");
  39. s.setAge(18);
  40. Teacher t = new Teacher();
  41. t.setName("王建国");
  42. t.setAge(30);
  43. Administrator admin = new Administrator();
  44. admin.setName("管理员");
  45. admin.setAge(35);
  46. register(s);
  47. register(t);
  48. register(admin);
  49. }
  50. //这个方法既能接收老师,又能接收学生,还能接收管理员
  51. //只能把参数写成这三个类型的父类
  52. public static void register(Person p){
  53. p.show();
  54. }
  55. }

多态的特点

前提【重点】

  1. 继承或者实现关系
  2. 方法的重写【意义体现:不重写,无意义】
  3. 父类引用指向子类对象【格式体现】父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

    父类引用指向子类对象指的是:

    • 编译阶段:绑定父类的方法。
    • 运行阶段: 动态绑定子类型对象的方法。

多态的弊端
我们已经知道多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了

  1. class Animal{
  2. public void eat(){
  3. System.out.println("动物吃东西!")
  4. }
  5. class Cat extends Animal {
  6. public void eat() {
  7. System.out.println("吃鱼");
  8. }
  9. public void catchMouse() {
  10. System.out.println("抓老鼠");
  11. }
  12. }
  13. class Dog extends Animal {
  14. public void eat() {
  15. System.out.println("吃骨头");
  16. }
  17. }
  18. class Test{
  19. public static void main(String[] args){
  20. Animal a = new Cat();
  21. a.eat();
  22. a.catchMouse();//编译报错,编译看左边,Animal没有这个方法
  23. }
  24. }

引用类型转换

多态的写法就无法访问子类独有功能了。
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点”小麻烦”。所以,想要调用子类特有的方法,必须做向下转型。
回顾基本数据类型转换

  • 自动转换: 范围小的赋值给范围大的.自动完成:double d = 5;
  • 强制转换: 范围大的赋值给范围小的,强制转换:int i = (int)3.14

多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。
向上转型

  • 向上转型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。当父类引用指向一个子类对象时,便是向上转型。使用格式:

    1. 父类类型 变量名 = new 子类类型();
    2. 如:Animal a = new Cat();

    原因是:父类类型相对与子类来说是大范围的类型,Animal是动物类,是父类类型。Cat是猫类,是子类类型。Animal类型的范围当然很大,包含一切动物。所以子类范围小可以直接自动转型给父类类型的变量。
    向下转型

  • 向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。

    1. 子类类型 变量名 = (子类类型) 父类变量名;
    2. 如:Aniaml a = new Cat();
    3. Cat c =(Cat) a;

    案例演示 ```java abstract class Animal {
    abstract void eat();
    }

class Cat extends Animal {
public void eat() {
System.out.println(“吃鱼”);
}
public void catchMouse() {
System.out.println(“抓老鼠”);
}
}

class Dog extends Animal {
public void eat() {
System.out.println(“吃骨头”);
}
public void watchHouse() {
System.out.println(“看家”);
}
}

  1. ```java
  2. public class Test {
  3. public static void main(String[] args) {
  4. // 向上转型
  5. Animal a = new Cat();
  6. a.eat(); // 调用的是 Cat 的 eat
  7. // 向下转型
  8. Cat c = (Cat)a;
  9. c.catchMouse(); // 调用的是 Cat 的 catchMouse
  10. }
  11. }

转型的异常

  1. public class Test {
  2. public static void main(String[] args) {
  3. // 向上转型
  4. Animal a = new Cat();
  5. a.eat(); // 调用的是 Cat 的 eat
  6. // 向下转型
  7. Dog d = (Dog)a;
  8. d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
  9. }
  10. }

instanceof关键字

为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:

  1. 变量名 instanceof 数据类型
  2. 如果变量属于该数据类型或者其子类类型,返回true
  3. 如果变量不属于该数据类型或者其子类类型,返回false
  1. public class Test {
  2. public static void main(String[] args) {
  3. // 向上转型
  4. Animal a = new Cat();
  5. a.eat(); // 调用的是 Cat 的 eat
  6. // 向下转型
  7. if (a instanceof Cat){
  8. Cat c = (Cat)a;
  9. c.catchMouse(); // 调用的是 Cat 的 catchMouse
  10. } else if (a instanceof Dog){
  11. Dog d = (Dog)a;
  12. d.watchHouse(); // 调用的是 Dog 的 watchHouse
  13. }
  14. }
  15. }

instanceof新特性
JDK14的时候提出了新特性,把判断和强转合并成了一行

  1. //新特性
  2. //先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
  3. //如果不是,则不强转,结果直接是false
  4. if(a instanceof Dog d){
  5. d.lookHome();
  6. }else if(a instanceof Cat c){
  7. c.catchMouse();
  8. }else{
  9. System.out.println("没有这个类型,无法转换");
  10. }

综合练习

3.多态%26包%26final%26权限修饰符 - 图3

  1. //动物类(父类)
  2. public class Animal {
  3. private int age;
  4. private String color;
  5. public Animal() {
  6. }
  7. public Animal(int age, String color) {
  8. this.age = age;
  9. this.color = color;
  10. }
  11. public int getAge() {
  12. return age;
  13. }
  14. public void setAge(int age) {
  15. this.age = age;
  16. }
  17. public String getColor() {
  18. return color;
  19. }
  20. public void setColor(String color) {
  21. this.color = color;
  22. }
  23. public void eat(String something){
  24. System.out.println("动物在吃" + something);
  25. }
  26. }
  27. //猫类(子类)
  28. public class Cat extends Animal {
  29. public Cat() {
  30. }
  31. public Cat(int age, String color) {
  32. super(age, color);
  33. }
  34. @Override
  35. public void eat(String something) {
  36. System.out.println(getAge() + "岁的" + getColor() + "颜色的猫眯着眼睛侧着头吃" + something);
  37. }
  38. public void catchMouse(){
  39. System.out.println("猫抓老鼠");
  40. }
  41. }
  42. //狗类(子类)
  43. public class Dog extends Animal {
  44. public Dog() {
  45. }
  46. public Dog(int age, String color) {
  47. super(age, color);
  48. }
  49. //行为
  50. //eat(String something)(something表示吃的东西)
  51. //看家lookHome方法(无参数)
  52. @Override
  53. public void eat(String something) {
  54. System.out.println(getAge() + "岁的" + getColor() + "颜色的狗两只前腿死死的抱住" + something + "猛吃");
  55. }
  56. public void lookHome(){
  57. System.out.println("狗在看家");
  58. }
  59. }
  60. //饲养员类
  61. public class Person {
  62. private String name;
  63. private int age;
  64. public Person() {
  65. }
  66. public Person(String name, int age) {
  67. this.name = name;
  68. this.age = age;
  69. }
  70. public String getName() {
  71. return name;
  72. }
  73. public void setName(String name) {
  74. this.name = name;
  75. }
  76. public int getAge() {
  77. return age;
  78. }
  79. public void setAge(int age) {
  80. this.age = age;
  81. }
  82. //饲养狗
  83. /* public void keepPet(Dog dog, String something) {
  84. System.out.println("年龄为" + age + "岁的" + name + "养了一只" + dog.getColor() + "颜色的" + dog.getAge() + "岁的狗");
  85. dog.eat(something);
  86. }
  87. //饲养猫
  88. public void keepPet(Cat cat, String something) {
  89. System.out.println("年龄为" + age + "岁的" + name + "养了一只" + cat.getColor() + "颜色的" + cat.getAge() + "岁的猫");
  90. cat.eat(something);
  91. }*/
  92. //想要一个方法,能接收所有的动物,包括猫,包括狗
  93. //方法的形参:可以写这些类的父类 Animal
  94. public void keepPet(Animal a, String something) {
  95. if(a instanceof Dog d){
  96. System.out.println("年龄为" + age + "岁的" + name + "养了一只" + a.getColor() + "颜色的" + a.getAge() + "岁的狗");
  97. d.eat(something);
  98. }else if(a instanceof Cat c){
  99. System.out.println("年龄为" + age + "岁的" + name + "养了一只" + c.getColor() + "颜色的" + c.getAge() + "岁的猫");
  100. c.eat(something);
  101. }else{
  102. System.out.println("没有这种动物");
  103. }
  104. }
  105. }
  106. //测试类
  107. public class Test {
  108. public static void main(String[] args) {
  109. //创建对象并调用方法
  110. /* Person p1 = new Person("老王",30);
  111. Dog d = new Dog(2,"黑");
  112. p1.keepPet(d,"骨头");
  113. Person p2 = new Person("老李",25);
  114. Cat c = new Cat(3,"灰");
  115. p2.keepPet(c,"鱼");*/
  116. //创建饲养员的对象
  117. Person p = new Person("老王",30);
  118. Dog d = new Dog(2,"黑");
  119. Cat c = new Cat(3,"灰");
  120. p.keepPet(d,"骨头");
  121. p.keepPet(c,"鱼");
  122. }
  123. }

final

1. 数据
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。

  • 对于基本类型,final 使数值不变;
  • 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。

    1. final int x = 1;
    2. // x = 2; // cannot assign value to final variable 'x'
    3. final A y = new A();
    4. y.a = 1;

    2. 方法
    声明方法不能被子类重写。
    private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。
    3. 类
    声明类不允许被继承

    权限修饰符

    在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,我们之前已经学习过了public 和 private,接下来我们研究一下protected和默认修饰符的作用。

  • public:公共的,所有地方都可以访问。

  • protected:本类 ,本包,其他包中的子类都可以访问。
  • 默认(没有修饰符):本类 ,本包可以访问。注意:默认是空着不写,不是default
  • private:私有的,当前类可以访问。public > protected > 默认 > private

不同权限的访问能力

public protected 默认 private
同一类中
同一包中的类
不同包的子类
不同包中的无关类

可见,public具有最大权限。private则是最小权限。
编写代码时,如果没有特殊的考虑,建议这样使用权限:

  • 成员变量使用private ,隐藏细节。
  • 构造方法使用public ,方便创建对象。
  • 成员方法使用public ,方便调用方法。

小贴士:不加权限修饰符,就是默认权限