JAVA中的多态与抽象类
说明:
运行环境: JDK8
1. 多态
1.1 多态的概念
通俗来说,就是指完成某个行为,当不同的对象去完成时就会产生出不同的状态;
(1)看个简单的例子:
同样是打印机打印,当彩色打印机去打印时,就会出现彩色的效果,当黑白打印机打印时,就是黑白效果;
(2)动物吃食物的行为:
都是吃饭,发生在不同对象身上,就会产生不同的行为;
1.2 多态的分类
多态分为(1)静态多态
(2)动态多态
静态多态:也称为早绑定,即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。
典型代表:函数重载;
动态多态:也称为晚绑定,即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法;
1.3 多态实现的条件
在java中要实现多态,必须要满足以下几个条件:
(1)必须在继承体系下
;
(2)子类必须要对父类中想要实现多态的方法进行重写
;
(3) 通过父类的引用调用被重写的方法
;
范例1:动物吃食物
//定义一个动物类
public class Animal {
protected String name;
protected String gender;
protected int age;
//定义一个构造方法
public Animal(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
// 定义一个吃的方法
public void eat(){
System.out.println(name+"吃东西");
}
}
//再定义一个猫类,来继承动物类
public class Cat extends Animal{
public Cat(String name, String gender, int age) {
super(name, gender, age);
}
// 对基类中的eat()方法进行重写
public void eat(){
System.out.println(name+"吃鱼");
}
}
//同样的,定义一个狗类,用来继承动物类
public class Dog extends Animal{
public Dog(String name, String gender, int age) {
super(name, gender, age);
}
// 对基类中eat()方法进行重写
public void eat(){
System.out.println(name+"吃骨头");
}
}
最后,写一个测试吃的代码,如下所示:
public class TestEat {
public static void eat(Animal a){
a.eat();
}
public static void main(String[] args) {
// New 两个对象
Dog dog =new Dog("小七","公",1);
Cat cat=new Cat("花花","女",1);
eat(dog);
eat(cat);
}
}
运行结果:
小七吃骨头
花花吃鱼
当编译器在编译代码时,并不知道要调用Cat
还是Dog
中的eat()
方法,只有在程序运行起来后,形参a
引用的具体对象确定后,才知道调用那个方法;
1.4 再次认识重写
重写(override):也称为覆盖,它是子类对父类非静态
、非private修饰
,非final修饰
,非构造方法
等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写
!
重写的优点: 子类可以根据自己的需要实现父类的方法;
方法重写的规则
(1)子类在重写父类的方法时,一般要与父类被重写的方法原型一致;
体现在:修饰符 返回值类型 方法名(参数列表) 要完全一致;如上面代码
Cat
和Dog
重写Animal
的eat()
方法一样;
(2)JDK7以后,被重写的方法返回值类型可以不同,但是必须是具有父子关系的;
public class Base {
Base a;
//返回值类型是:Base
public Base methodA(){
return a;
}
}
// Derived用来继承Base
public class Derived extends Base{
Derived c;
//返回值类型是:Derived
public Derived methodA() {
return c;
}
}
(3) 访问权限不能比父类中被重写的方法的访问权限低;
例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected;
(4)重写的方法, 可以使用 @Override
注解来显式指定,进行一些合法性校验;
(5) 父类被static
、private
、final
修饰的方法以及构造方法都不能被重写;
// 以下方法是不能被重写的
//被static修饰的
@Override
public static void methodB(){
System.out.println("Derived-methodB()");
}
//被private修饰的
@Override
private void methodC(){
System.out.println("Derived-methodC()");
}
//被final修饰的
@Override
public final void methodD(){
System.out.println("Derived-methodD()");
}
1.5 重写与重载的区别
参考如下表格:
【重写的设计原则】:
对于已经投入使用的类,尽量不要进行修改;
最好的方式:重新定义一个新类,来重复利用其中共性的内容,添加或者改动新的内容;
2. 向上转型与向下转型
2.1 向上转型
向上转型:就是让基类的引用去引用子类的对象;
语法格式:
父类类型 对象名 = new 子类类型()
Animal a1 = new Cat();
Animal a2 = new Dog();
Animal
是父类类型,可以引用一个子类对象;
原因:子类对象是一个父类对象,即可以将一个子类对象当成父类对象来应用。因此,向上转型是安全的,因为是从小范围向大范围的转换;
使用场景:
(1)直接赋值
Animal a1 = new Cat ("元宝","男",2);
}
(2)方法传参
public class TestEat {
// 2. 方法传参:形参为父类型引用,可以接收任意子类的对象
public static void eat(Animal a){
a.eat();
}
(3)方法返回
//作为方法的返回,返回任意子类对象
public static Animal buyAnimal(String name){
if(name.equals("狗")) {
return new Dog("狗狗", "公", 1);
} else if(name.equals("猫")){
return new Cat("猫猫", "男", 1);
}else{
return null;
}
}
public static void main(String[] args) {
Animal a1= buyAnimal("猫");
}
向上转型的优点:让代码实现更简单灵活;
向上转型的缺陷:不能调用到子类特有的方法;
2.2 向下转型
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入了 instanceof
关键字,如果该表达式为true
,则可以安全转换;
范例2:
public class Animal {
String name;
String gender;
int age;
public Animal(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public void eat(){
System.out.println(name+"吃东西");
}
}
//定义一个Cat类,用来继承Animal
public class Cat extends Animal{
public Cat(String name, String gender, int age) {
super(name, gender, age);
}
@Override
public void eat() {
System.out.println(name+"吃鱼");
}
public void mew(){
System.out.println(name+"喵喵喵~~");
}
}
//定义一个Dog类,用来继承Animal
public class Dog extends Animal{
public Dog(String name, String gender, int age) {
super(name, gender, age);
}
@Override
public void eat() {
System.out.println(name+"吃骨头");;
}
public void bark(){
System.out.println(name+"旺旺旺~~");
}
}
写一个Test类来测试
public class Test {
public static void testEat(Animal a) {
a.eat();
}
public static Animal buyAnimal(String name) {
if (name.equals("狗")) {
return new Dog("狗狗", "公", 1);
} else if (name.equals("猫")) {
return new Cat("猫猫", "男", 1);
} else {
return null;
}
}
public static void main(String[] args) {
Animal a1 = buyAnimal("猫");
a1.eat();
Animal a2 = buyAnimal("狗");
a2.eat();
//通过a1调用,编译器会在Animal中去查找
//a1.mew();// 编译报错
//实际上a1指向的是一个cat对象
//要调用则要
Cat cat = (Cat) a1; //进行强制转换
cat.mew();
//a1 实际指向的是一个Cat对象,但此处将其转换成Dog类的对象来使用
//代码运行时就会抛异常:java.lang.ClassCastException
// Dog d = (Dog) a1; // 抛异常
//d.bark();
//一般采用以下方式来使用
if (a1 instanceof Dog) {
Dog d1 = (Dog) a1;
d1.bark();
}
if (a2 instanceof Dog) {
Dog d2 = (Dog) a2;
d2.bark();
}
}
}
注:一般不用向下转型;
3. 使用多态的优点
(1)能够降低代码的 "圈复杂度", 避免大量使用 if-else
;
所谓圈复杂度:就是一种描述一段代码复杂程度的方式, 一段代码如果平铺直叙, 那么就比较简单容易理解, 而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂;
(2) 可扩展能力强
;
4. 抽象类
4.1 抽象类的概念
一个类中没有包含足够的信息来描述一个具体的对象,把这样的类称为抽象类;
4.2 抽象类的语法格式
在Java中,如果一个类被 abstract
修饰,就称为抽象类,public abstract class Test {...}
;
抽象类中被 abstract
修饰的方法称为抽象方法,抽象方法不用给出具体的实现体;
如下所示:
// 下面就是抽象方法
// 抽象方法不用添加方法体
abstract public void eat();
abstract void bark();
注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法;
范例3: 定义一个图形类
//抽象类
public abstract class Shape {
protected double area;
// 抽象方法
abstract public void draw();
//普通方法
public double getArea(){
return area;
}
}
4.3 抽象类的特性
(1)抽象类不能直接实例化对象;
public abstract class Animal {
string name;
public Animal(string name) {
this.name = name;
}
public static void main(String[] args) {
// Animal a=new Animal(); 报错,抽象类不能实例化对象
}
}
(2)抽象方法不能被
private
修饰;
abstract private void eat(); //报错
原因:要在子类中被重写,而private
修饰的,子类不能访问;
(3)抽象方法不能被
final
和static
修饰,因为抽象方法要被子类重写;
abstract final void drink(); //报错
原因:final
修饰的方法不能被子类重写
abstract static void eat(); //报错
原因:抽象方法不能是静态方法,其内部没有this
引用
(4)抽象类必须被继承,并且继承后子类要重写父类中所有的抽象方法,否则子类也是抽象类,必须要使用
abstract
修饰;
定义一个 Animal类
public abstract class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
//抽象方法
abstract public void eat();
abstract public void bark();
}
定义一个Dog类,用来继承Animal类
//Dog实现了Animal中的所有的抽象方法,
//Dog就是一个具体的类
//就可以创建对象
public class Dog extends Animal{
public Dog(String name) {
super(name);
}
public void eat(){
System.out.println(name+"吃骨头");
}
public void bark(){
System.out.println(name+"旺旺旺");
}
public static void main(String[] args) {
Dog dog=new Dog("小七");
dog.eat();
dog.bark();
}
}
运行结果:
小七吃骨头
小七旺旺旺
(5)抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类;