多态
多态的理解
多态的格式
父类类型 变量名 = new 子类/实现类构造器;变量名.方法名();
多态的使用场景
如果没有多态,在下图中register方法只能传递学生对象,其他的Teacher和administrator对象是无法传递给register方法方法的,在这种情况下,只能定义三个不同的register方法分别接收学生,老师和管理员。
有了多态之后,方法的形参就可以定义为共同的父类Person。
要注意的是:
- 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
 - 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象(后面会学)。
 - 而且多态还可以根据传递的不同对象来调用不同类中的方法。
 

代码示例:
父类:public class Person {private String name;private int age;空参构造带全部参数的构造get和set方法public void show(){System.out.println(name + ", " + age);}}子类1:public class Administrator extends Person {@Overridepublic void show() {System.out.println("管理员的信息为:" + getName() + ", " + getAge());}}子类2:public class Student extends Person{@Overridepublic void show() {System.out.println("学生的信息为:" + getName() + ", " + getAge());}}子类3:public class Teacher extends Person{@Overridepublic void show() {System.out.println("老师的信息为:" + getName() + ", " + getAge());}}测试类:public class Test {public static void main(String[] args) {//创建三个对象,并调用register方法Student s = new Student();s.setName("张三");s.setAge(18);Teacher t = new Teacher();t.setName("王建国");t.setAge(30);Administrator admin = new Administrator();admin.setName("管理员");admin.setAge(35);register(s);register(t);register(admin);}//这个方法既能接收老师,又能接收学生,还能接收管理员//只能把参数写成这三个类型的父类public static void register(Person p){p.show();}}
多态的特点
前提【重点】
- 有继承或者实现关系
 - 方法的重写【意义体现:不重写,无意义】
 - 父类引用指向子类对象【格式体现】父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
父类引用指向子类对象指的是:
- 编译阶段:绑定父类的方法。
 - 运行阶段: 动态绑定子类型对象的方法。
 
 
多态的弊端
我们已经知道多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了。
class Animal{public void eat(){System.out.println("动物吃东西!")}}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("吃骨头");}}class Test{public static void main(String[] args){Animal a = new Cat();a.eat();a.catchMouse();//编译报错,编译看左边,Animal没有这个方法}}
引用类型转换
多态的写法就无法访问子类独有功能了。
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点”小麻烦”。所以,想要调用子类特有的方法,必须做向下转型。
回顾基本数据类型转换
- 自动转换: 范围小的赋值给范围大的.自动完成:double d = 5;
 - 强制转换: 范围大的赋值给范围小的,强制转换:int i = (int)3.14
 
多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。
向上转型
向上转型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。当父类引用指向一个子类对象时,便是向上转型。使用格式:
父类类型 变量名 = new 子类类型();如:Animal a = new Cat();
原因是:父类类型相对与子类来说是大范围的类型,Animal是动物类,是父类类型。Cat是猫类,是子类类型。Animal类型的范围当然很大,包含一切动物。所以子类范围小可以直接自动转型给父类类型的变量。
向下转型向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
子类类型 变量名 = (子类类型) 父类变量名;如:Aniaml a = new Cat();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(“看家”);
    }
}
```javapublic class Test {public static void main(String[] args) {// 向上转型Animal a = new Cat();a.eat(); // 调用的是 Cat 的 eat// 向下转型Cat c = (Cat)a;c.catchMouse(); // 调用的是 Cat 的 catchMouse}}
转型的异常
public class Test {public static void main(String[] args) {// 向上转型Animal a = new Cat();a.eat(); // 调用的是 Cat 的 eat// 向下转型Dog d = (Dog)a;d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】}}
instanceof关键字
为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:
变量名 instanceof 数据类型如果变量属于该数据类型或者其子类类型,返回true。如果变量不属于该数据类型或者其子类类型,返回false。
public class Test {public static void main(String[] args) {// 向上转型Animal a = new Cat();a.eat(); // 调用的是 Cat 的 eat// 向下转型if (a instanceof Cat){Cat c = (Cat)a;c.catchMouse(); // 调用的是 Cat 的 catchMouse} else if (a instanceof Dog){Dog d = (Dog)a;d.watchHouse(); // 调用的是 Dog 的 watchHouse}}}
instanceof新特性
JDK14的时候提出了新特性,把判断和强转合并成了一行
//新特性//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d//如果不是,则不强转,结果直接是falseif(a instanceof Dog d){d.lookHome();}else if(a instanceof Cat c){c.catchMouse();}else{System.out.println("没有这个类型,无法转换");}
综合练习

//动物类(父类)public class Animal {private int age;private String color;public Animal() {}public Animal(int age, String color) {this.age = age;this.color = color;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}public void eat(String something){System.out.println("动物在吃" + something);}}//猫类(子类)public class Cat extends Animal {public Cat() {}public Cat(int age, String color) {super(age, color);}@Overridepublic void eat(String something) {System.out.println(getAge() + "岁的" + getColor() + "颜色的猫眯着眼睛侧着头吃" + something);}public void catchMouse(){System.out.println("猫抓老鼠");}}//狗类(子类)public class Dog extends Animal {public Dog() {}public Dog(int age, String color) {super(age, color);}//行为//eat(String something)(something表示吃的东西)//看家lookHome方法(无参数)@Overridepublic void eat(String something) {System.out.println(getAge() + "岁的" + getColor() + "颜色的狗两只前腿死死的抱住" + something + "猛吃");}public void lookHome(){System.out.println("狗在看家");}}//饲养员类public class Person {private String name;private int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}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 void keepPet(Dog dog, String something) {System.out.println("年龄为" + age + "岁的" + name + "养了一只" + dog.getColor() + "颜色的" + dog.getAge() + "岁的狗");dog.eat(something);}//饲养猫public void keepPet(Cat cat, String something) {System.out.println("年龄为" + age + "岁的" + name + "养了一只" + cat.getColor() + "颜色的" + cat.getAge() + "岁的猫");cat.eat(something);}*///想要一个方法,能接收所有的动物,包括猫,包括狗//方法的形参:可以写这些类的父类 Animalpublic void keepPet(Animal a, String something) {if(a instanceof Dog d){System.out.println("年龄为" + age + "岁的" + name + "养了一只" + a.getColor() + "颜色的" + a.getAge() + "岁的狗");d.eat(something);}else if(a instanceof Cat c){System.out.println("年龄为" + age + "岁的" + name + "养了一只" + c.getColor() + "颜色的" + c.getAge() + "岁的猫");c.eat(something);}else{System.out.println("没有这种动物");}}}//测试类public class Test {public static void main(String[] args) {//创建对象并调用方法/* Person p1 = new Person("老王",30);Dog d = new Dog(2,"黑");p1.keepPet(d,"骨头");Person p2 = new Person("老李",25);Cat c = new Cat(3,"灰");p2.keepPet(c,"鱼");*///创建饲养员的对象Person p = new Person("老王",30);Dog d = new Dog(2,"黑");Cat c = new Cat(3,"灰");p.keepPet(d,"骨头");p.keepPet(c,"鱼");}}
final
1. 数据
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
- 对于基本类型,final 使数值不变;
 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
final int x = 1;// x = 2; // cannot assign value to final variable 'x'final A y = new A();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 ,方便调用方法。
 
小贴士:不加权限修饰符,就是默认权限
