Java面向对象专题(四)多态

多态

概述

同一个对象在不同时刻体现出来的不同状态。

举例:
猫可以是猫的类型。
Cat m = new Cat();
同时猫也是一种动物,也可以把猫称为动物。
Animal c = new Cat();

实现前提

  • 有继承或者实现关系
  • 有方法重写
  • 有父类或父接口引用指向子类对象
class Father{ 
	public void show(){
		System.out.println("Show Father");
	}
	
}
//继承关系
class Son extends Father{ 
	//方法重写
	public void show(){ 
		System.out.println("Show Son");
	}
}
class DtDemo{
	public static void main(String[] args){
		//要有父类引用指向子类对象
		Father f = new Son();//Show Son
	}
}

分类

  • 具体类多态
class Fu{ }
class Zi extends Fu{ 
	方法重写;	
}
Fu f = new Zi();//调用
  • 抽象类多态
abstract class Fu{ }
class Zi extends Fu{
	方法重写;
}
Fu f = new Zi();//调用
  • 接口多态
interface Fu{ }
class Zi implements Fu{ 
	方法重写;
}
Fu f = new Zi();//调用

成员访问特点

(左边) Father f = new Son();(右边)

  • 成员变量

    编译看左边,运行看左边。

  • 构造方法

    子类的构造都会默认访问父类构造。

  • 成员方法

    编译看左边,运行看右边。

    由于成员方法存在方法重写,覆盖了父类成员方法,所以运行看右边

  • 静态方法

    编译看左边,运行看左边。

    静态与类相关,算不上重写,所以访问还是左边的

口诀的具体说明看例子:

class Father{
	public int num = 100;
	
	public void show(){
		System.out.println("Show Father");
	}
	
	public static void function(){
		System.out.println("function Father");
	}
}
class Son extends Father{
	public int num = 1000;//与父类的变量重名
	public int num2 = 200; //子类自己的变量
	//方法重写
	public void show(){
		System.out.println("Show Son");
	}
	public void method() {
		System.out.println("Method Son");
	}
	
	public static void function(){
		System.out.println("function Son");
	}
}
class DtDemo{
	public static void main(String[] args){
		//要有父类引用指向子类对象
		Father f = new Son();
		
		System.out.println(f.num); //结果是:100 ,运行看左边
		//下面这句会编译出错,无法找到符号num2,编译看左边。
		//System.out.println(f.num2);
		f.show();  //结果是:Show Son,运行看右边
		//下面这句会编译出错,无法找到符号method,编译看左边。
		//f.method();
		
		f.function(); //结果是:function Father。运行看左边
	}
}

个人理解:编译看左边,指的是Father f = new Son();创建的对象仍然是Father引用类型,如果对象f 调用的是Father类中没有的成员,则编译报错。运行看左边或者右边这样理解:把父类看成C中的结构体,成员变量看成结构体中的数据,成员方法看成函数指针,指针指向父类函数,静态方法看成只能指向父类函数的指针;子类继承自父类,可以看成子类中包含了父类这个结构体,且含有自己的数据;此时这句“Father f = new Son();”便可看成一个小的结构体的指针指向一个大的结构体,因此能访问到的成员变量和静态方法只有Father结构体自身的,所以运行看左边;但子类中存在方法重写,也就意味着父类中的函数指针此时指向了子类的函数,所以运行看右边。
此理解仅供参考,具体Java是怎么处理的,可参考:https://www.cnblogs.com/crane-practice/p/3671074.html

内存图

程序
内存图

好处与弊端

  • 好处:
    • 提高代码的维护性(继承体现)
    • 提高代码的扩展性(多态体现)
  • 弊端:
    • 父类不能使用子类的特有功能。

    现象:子可以当作父使用,父不能当作子使用。
    解决方法:把父类的引用强制转换为子类的引用(向下转型)

转型问题

  • 向上转型:
    从子到父: Father f = new Son();
    (理解:一个小的结构体的指针指向一个大结构体,所以大结构体的部分内容对于该指针来说并不可见)
  • 向下转型
    从父到子: Son s = (Son) f;
    (理解:一个小的结构体的指针被强制转化成大结构体的指针,对大结构体的全部内容可见)

例子

class Animal{
	public void eat() {
		System.out.println("eat");
	}
	public void sleep() {
		System.out.println("sleep");
	}
}
//工具类
class AnimalTool{
	public static void userCat(Cat cat) {
		cat.eat();
		cat.sleep();
	}
	public static void userDog(Dog dog) {
		dog.eat();
		dog.sleep();
	}
	public static void userAnimal(Animal animal) {
		animal.eat();
		animal.sleep();
	}
}
class Dog extends Animal{
	public void eat() {
		System.out.println("狗吃肉");
	}
	public void sleep() {
		System.out.println("狗站着睡觉");
	}
	
}
class Cat extends Animal{
	public void eat() {
		System.out.println("猫吃鱼");
	}
	public void sleep() {
		System.out.println("猫趴着睡觉");
	}
}
class Pig extends Animal{
	public void eat() {
		System.out.println("猪吃白菜");
	}
	public void sleep() {
		System.out.println("猪侧着睡觉");
	}
public class DtDemo {
	public static void main(String[] args) {
		//一般模式
		Cat c = new Cat();
		c.eat();
		c.sleep();
		Cat c2 = new Cat();
		c.eat();
		c.sleep();
		Cat c3 = new Cat();
		c.eat();
		c.sleep();
		System.out.print("--------------------\n");
		
		//问题来了,每次创建对象之后调用的方法是相似的,仅对象名不一样
		//是否能改进呢?
		//改进方法:创建工具类
		//下面效果跟上面的效果一致
		AnimalTool.userCat(c);
		AnimalTool.userCat(c2);
		AnimalTool.userCat(c3);
		System.out.print("--------------------\n");
		
		//类似的,对于狗,可以先通过在工具类中创建工具,再将创建好的对象传进去
		Dog d = new Dog();
		Dog d2 = new Dog();
		Dog d3 = new Dog();
		AnimalTool.userDog(d);
		AnimalTool.userDog(d2);
		AnimalTool.userDog(d3);
		System.out.print("--------------------\n");
		//新的问题:当有新的类继承自Animal类,我们就要在工具类中写新的方法供新的类使用
		//我们能不能使用工具类的一个方法,让不同的类(继承自同一父类)都可以调用呢?
		//从而对以后新的动物,也能使用
		
		//解决方法:使用多态
		Pig p = new Pig();
		Pig p2 = new Pig();
		Pig p3 = new Pig();
		AnimalTool.userAnimal(p);
		AnimalTool.userAnimal(p2);
		AnimalTool.userAnimal(p3);
	}
}

小结:

  • 当应用中多次对相似的类调用一样的方法时,不妨将其封装成工具类
  • 当继承自同一父类的多个子类调用同一方法时,不烦考虑多态

抽象类

定义

把多个具有共性的东西提取到一个类中,即继承的做法,但这些共性在某些时候,虽然方法声明一致,但具体的方法实现不同。所以在定义这些共性的方法时,不能给出具体的方法体。
而一个没有具体方法体的方法即是抽象方法。
在一个类中,如果有抽象方法,该类必须定义为抽象类

特点

  • 抽象类和抽象方法必须使用关键字abstract修饰
  • 抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
  • 抽象类有构造方法,但不能实例化
  • 抽象类的子类可以是
    • 抽象类,可不重写抽象方法
    • 具体类,这个类必须重写抽象类中的所有抽象方法

成员特点

  • 成员变量

    既可以是变量,也可以是常量

  • 构造方法

    有。带不带带参数均可。
    用于子类访问父类数据的初始化

  • 成员方法

    既可以是抽象的,也可以是非抽象的

抽象类的成员方法特性:

  • 抽象方法:强制要求子类做的事
  • 非抽象方法:子类继承的事情,提高代码复用性

例子:

abstract class Animal {
	public int age = 10;
	public final String name = "Animal";

	// public abstract void eat() {} //空方法体,这个会报错,抽象方法不能有主体
	public abstract void eat();

	public void show() {
		System.out.println("Show");
	}

	public Animal() {
		// TODO Auto-generated constructor stub
	}

	public Animal(String name, int age) {
	}

}
//抽象类继承抽象类
abstract class Dog extends Animal {
}
//具体类继承抽象类
class Cat extends Animal {
	@Override
	public void eat() {
		// TODO Auto-generated method stub
		System.out.print("猫吃鱼");
	}

}
public class AbstractDemo {
	
	  public static void main(String[] args) { 
		  //Animal an = new Animal() ;
		  //Animal是抽象的,无法实例化 
		  Animal animal = new Cat(); 
		  animal.eat(); 
	  }
	 
}

注意事项

  • 抽象类有构造方法,不能实例化,那么构造方法有什么作用?

    用于子类访问父类数据的初始化

  • 抽象类的实例化是通过具体的子类实现的,以多态的方式

    例如:Animal a = new Cat();

  • 一个类如果没有抽象方法,却定义为抽象类,有什么作用?

    为了不让创建对象

  • abstract不能和哪些关键字共存?

    a:final 冲突
    b:private 冲突
    c:static 无意义

案例:

/**
 * 
 * @author Administrator
 *
 *	需求:假如开发一个系统时需要对员工类进行设计,员工包含有3个属性:姓名,工号以及工资。
 *		  经理也是员工,除了含有员工的属性外,还有一个奖金属性。
 *		 请使用继承的思想设计出员工类和经理类,要求类中提供必要的方法进行属性访问。
 *
 *	分析:
 *		普通员工类:
 *			成员变量:姓名,工号,工资
 *			成员方法:工作
 *		经理类:
 *			成员变量:姓名,工号,工资,奖金
 *			成员方法:工作
 *	
 	实现:
 		员工类:
 		普通员工类:
 		经理类:
 */
 //员工类
abstract class Employee{
	private String name;
	private String id;
	private int salary;
	public Employee() {
		// TODO Auto-generated constructor stub
	}
	public Employee(String name,String id,int salary) {
		// TODO Auto-generated constructor stub
		this.name = name;
		this.id = id;
		this.salary = salary;
	}
	public String getName() {
		return name;
	}
	public void setName(String name){
		this.name = name;
	}
	public String getId(){
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public int getSalary() {
		return salary;
	}
	public void setSalary(int salary) {
		this.salary = salary;
	}
	public abstract void work();
}
//普通员工类
class Programmer extends Employee{
	public Programmer() {
		// TODO Auto-generated constructor stub
	}
	public Programmer(String name,String id,int salary) {
		// TODO Auto-generated constructor stub
		super(name, id, salary);
	}
	@Override
	public void work() {
		// TODO Auto-generated method stub
		System.out.println("按照需求写代码");
	}
	
}
//经理类
class Manager extends Employee{
	private int bonus;
	public Manager() {
		// TODO Auto-generated constructor stub
	}
	public Manager(String name,String id,int salary,int bonus) {
		super(name, id, salary);
		this.bonus = bonus;
	}
	@Override
	public void work() {
		// TODO Auto-generated method stub
		System.out.println("跟客户谈需求");
	}
	public int getBonus() {
		return this.bonus;
	}
	public void setBonus(int bonus) {
		this.bonus = bonus;
	}
}
public class AbstractTest {
	public static void main(String[] args) {
		//普通员工测试
		Employee emp = new Programmer();
		emp.setName("Jame");
		emp.setSalary(8000);
		emp.setId("001");
		System.out.println(emp.getName()+"---"+emp.getId()+"---"+emp.getSalary());
		emp.work();
		System.out.println("---------------------");
		emp = new Programmer("Cherry", "003", 7500);
		System.out.println(emp.getName()+"---"+emp.getId()+"---"+emp.getSalary());
		emp.work();
		System.out.println("---------------------");
		
		//经理类测试
		//由于用到了向下转型,不建议
		emp = new Manager();
		emp.setName("Tom");
		emp.setId("005");
		emp.setSalary(8500);
		Manager manager = (Manager)emp;
		manager.setBonus(2000);
		System.out.println(manager.getName()+"---"+manager.getId()+"---"+manager.getSalary()+"---"+manager.getBonus());
		emp.work();
		System.out.println("---------------------");
		
		//由于子类有特有的功能,所以我们推荐下面方式
		Manager m = new Manager();
		m.setName("Alice");
		m.setId("007");
		m.setSalary(9000);
		m.setBonus(2500);
		System.out.println(m.getName()+"---"+m.getId()+"---"+m.getSalary()+"---"+m.getBonus());
		m.work();
		System.out.println("---------------------");
		
		m = new Manager("Helen", "009", 10000, 1500);
		System.out.println(m.getName()+"---"+m.getId()+"---"+m.getSalary()+"---"+m.getBonus());
		m.work();
		System.out.println("---------------------");
	}
}

运行结果:
运行结果

接口

定义

对于某些类,仅仅提供该类本身具有的功能和属性,但现实中,该类总是会有机会被赋予额外的功能
这种扩展功能,Java提供了接口表示。

特点

  • 接口用关键字interface 修饰:
    interface 接口名{ }
  • 类实现接口用implements 修饰:
    class 类名 implements 接口名{ }
  • 接口不能实例化,只能按多态方式实例
  • 接口的实现类可以是:
    • 抽象类,意义不大
    • 具体类,这个类必须重写接口中的所有抽象方法

例子:

//定义动物培训的接口
interface AnimalTrain{
	public abstract void jump();
}

//抽象类实现接口
abstract class Dog implements AnimalTrain{

}
//具体类实现接口
class Cat implements AnimalTrain{
	public void jump(){
		System.out.println("猫可以跳高了");
	}
}


class InterfaceDemo{
	public static void main(String[] args){
		//AnimalTrain是抽象的,无法实例化
		//AnimalTrain at = new AnimalTrain();
		//at.jump();
		AnimalTrain at = new Cat();
		at.jump();//猫可以跳高了
	}
}

成员特点

  • 成员变量

    只能是常量,并且是静态的。
    默认修饰符:public static final

  • 构造方法

    没有构造方法

  • 成员方法

    只能是抽象方法
    默认修饰符:public abstract

例子:

interface Inter{
	public int num = 10;
	public final num2 = 20;
	public static final int num3 = 30;
	
	//错误:需要标识符
	//public Inter(){}
	
	//接口中方法不能有主体
	//public void show(){}
	public abstract void show();
}

//接口名+Impl这种格式是接口的实现类格式
/*
class InterImpl implements Inter{
	public InterImpl(){
		super();
	}
	public void show(){}
}
*/
//等同于上面注释部分
/*
所有的类都默认继承自一个类:Object
类Object 是类层次结构的根类,每个类都使用Object作为超类
*/
class InterImpl extends Object implements Inter{
	public InterImpl(){
		super();
	}
	public void show(){}
}
//测试类
class InterfaceDemo2{
	public static void main(String[] args){
		//创建对象
		Inter i = new InterImpl();
		System.out.println(i.num);
		System.out.println(i.num2);
		//i.num = 100;
		//i.num2 = 200;
		//System.out.println(i.num);//无法为最终变量num分配值
		//System.out.println(i.num);//无法为最终变量num2分配值
		System.out.println(Inter.num);
		System.out.println(Inter.num2);
	}
	
}

类与类,类与接口,接口与接口的关系

  • 类与类
    继承关系:只能单继承或多层继承
  • 类与接口
    实现关系:可以单实现或多实现

一个类可以在继承另一个类的同时,实现多个接口

  • 接口与接口
    继承关系:可以单继承或多继承

例子:

interface Father{
	public abstract void show();
}
interface Mother{
	public abstract void show2();
}
interface Sister extends Father,Mother{
	
}

class Son extends Object implements Father,Mother{
	public void show(){
		System.out.println("Show Son");
	}
		
	public void show2(){
		System.out.println("Show2 Son");
	}
}
class InterdaceDemo3{
	public static void main(String[] args){
		Father f = new Son();
		f.show();
		//f.show2(); //报错
		
		Mother m = new Son();
		//m.show(); //报错
		m.show2();
	}
}

抽象类和接口的区别

成员区别

区别 抽象类 接口
成员变量 可以是变量,也可以是常量 只可以是常量
构造方法
成员方法 可以抽象,也可以非抽象 只可以是抽象

关系区别

区别 关系 说明
类与类 继承 只能单继承
类与接口 实现 可单实现或多实现
接口与接口 继承 可单继承或多继承

设计理念区别

  • 抽象类

    被继承体现的是:“is a”的关系。抽象类定义的是该继承体系的共性功能

  • 接口

    被实现体现的是:“like a”的关系。接口中定义的是该继承体系的扩展功能

案例:

/**
 * 		老师和学生案例,加入抽烟的额外功能
 * 		分析:从具体到抽象
 * 			老师:姓名,年龄,吃饭,睡觉
 * 			学生:姓名,年龄,吃饭,睡觉
 * 				
 * 			由于有共性功能,我们提出一个父类:人类
 * 
 * 			人类:
 * 				姓名,年龄
 * 				吃放();
 * 				睡觉(){ }
 * 			
 * 			抽烟的额外功能不是人一开始就具备的,所以把它定义为接口
 * 			抽烟接口
 * 			
 * 			部分老师抽烟:实现抽烟接口
 * 			部分学生抽烟:实现抽烟接口
 * 		
 * 		实现:从抽象到具体
 * 		使用:具体
 */
//定义抽烟接口
interface Smoking{
	//抽烟的抽象方法
	public abstract void smoke();
}
//定义抽象人类
abstract class Person{
	private String name;
	private int age;
	public Person() {
		// TODO Auto-generated constructor stub
	}
	public Person(String name,int age) {
		// TODO Auto-generated constructor stub
		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 abstract void eat();
	
	public void sleep() {
		System.out.println("睡觉了");
	}
}
//具体老师类
class Teacher extends Person{
	public Teacher() {
		// TODO Auto-generated constructor stub
	}
	public Teacher(String name,int age) {
		// TODO Auto-generated constructor stub
		super(name, age);
	}
	public void eat() {
		System.out.println("吃红烧肉");
	}
}
//具体学生类
class Student extends Person{
	public Student() {
		// TODO Auto-generated constructor stub
	}
	public Student(String name,int age) {
		// TODO Auto-generated constructor stub
		super(name, age);
	}
	public void eat() {
		System.out.println("吃鸡腿");
	}
}
//抽烟的老师
class SmokeTeacher extends Teacher implements Smoking{
	public SmokeTeacher() {
		// TODO Auto-generated constructor stub
	}
	public SmokeTeacher(String name,int age) {
		// TODO Auto-generated constructor stub
		super(name, age);
	}
	@Override
	public void smoke() {
		// TODO Auto-generated method stub
		System.out.println("抽烟的老师");
	}
	
}
//抽烟的学生
class SmokeStudent extends Student implements Smoking{
	public SmokeStudent() {
		// TODO Auto-generated constructor stub
	}
	public SmokeStudent(String name,int age) {
		// TODO Auto-generated constructor stub
		super(name, age);
	}
	@Override
	public void smoke() {
		// TODO Auto-generated method stub
		System.out.println("抽烟的学生");
	}
	
}
public class InterfaceDemo {
	public static void main(String[] args) {
		//测试学生
		SmokeStudent ss = new SmokeStudent();
		ss.setName("Tom");
		ss.setAge(18);
		System.out.println(ss.getName()+"---"+ss.getAge());
		ss.eat();
		ss.sleep();
		ss.smoke();
		System.out.println("---------------------");
		
		ss = new SmokeStudent("Alice", 16);
		System.out.println(ss.getName()+"---"+ss.getAge());
		ss.eat();
		ss.sleep();
		ss.smoke();
		System.out.println("---------------------");
		
		//测试老师
		SmokeTeacher st = new SmokeTeacher();
		st.setName("Helen");
		st.setAge(40);
		System.out.println(st.getName()+"---"+st.getAge());
		st.eat();
		st.sleep();
		st.smoke();
		System.out.println("---------------------");
		
		st = new SmokeTeacher("Amami", 42);
		System.out.println(st.getName()+"---"+st.getAge());
		st.eat();
		st.sleep();
		st.smoke();
		System.out.println("---------------------");
	}
}

多态中的形式参数和返回值问题

形式参数问题

方法中的形式参数为引用类型时,所需传入的实际参数为:

  • 具体类名:需要该类的对象(可用匿名对象)
  • 抽象类名:需要该类的子类对象
  • 接口名:需要该接口的实现类对象

例子:

//抽象类
abstract class Person{
	public abstract void study();
}
class PersonDemo{
	public void method(Person p){
		p.study();
	}
}
//具体类
class Student extends Person{
	public void study(){
		System.out.println("好好学习,天天向上");
	}
}
class StudentDemo{
	public void method(Student s){
		s.study();
	}
}
//接口
interface Love{
	public abstract void love();
}

class LoveDemo{
	public void method(Love l){
		l.love();
	}
}

class Teacher implements Love{
	public void love(){
		System.out.println("老师爱学生");
	}
}

class ParameterTest{
	public static void main(String[] args){
		//具体类测试
		//普通方式
		Student s = new Student();
		StudentDemo sd = new StudentDemo();
		sd.method(s);		
		//匿名方式
		new StudentDemo().method(new Student());
		
		//抽象类测试
		PersonDemo pd = new PersonDemo();
		Person p = new Student();
		pd.method(p);
		
		//接口测试
		LoveDemo ld = new LoveDemo();
		Love l = new Teacher();
		ld.method(l);
	}
}

返回值问题

方法中的返回值为引用类型时,所需返回的类型为:

  • 具体类名:返回该类的对象(可用匿名对象)
  • 抽象类名:返回该类的子类对象
  • 接口名:返回该接口的实现类对象

链式编程

每次调用方法完毕,返回的是一个对象,且该对象不再使用时,则可以考虑链式编程。

格式:对象.方法1().方法2()…方法n();
这种用法:

方法1()调用完毕后,返回的应该是一个对象;
方法2()调用完毕后,返回的应该是一个对象;
方法n()调用完毕后,返回可以是对象,也可以不是对象;

例子:

//抽象类
abstract class Person{
	public abstract void study();
}

class PersonDemo{
	public Person getPerson(){
		//Person p = new Student();
		//return p;
		return new Student();
	}
}
//具体类
class Student extends Person{
	public void study(){
		System.out.println("好好学习,天天向上");
	}
}

class StudentDemo{
	public Student getStudent(){
		//Student s = new Student();
		//return s;
		return new Student();
	}
}

//接口
interface Love{
	public abstract void love();
}

class LoveDemo{
	public Love getLove(){
		//Love l = new Teacher();
		//return l;
		//等同于下面
		return new Teacher();
	}
}

class Teacher implements Love{
	public void love(){
		System.out.println("好好学习天天向上");
	}
}


class ReturnTest{
	public static void main(String[] args){
		//具体类测试
		StudentDemo sd = new StudentDemo();
		Student s = sd.getStudent();//new Student(); 
		s.study();
		//链式调用
		sd.getStudent().study();
		
		//抽象类测试
		PersonDemo pd = new PersonDemo();
		Person p = pd.getPerson(); // new Student(); 多态
		p.study();
		//链式调用
		pd.getPerson().study();
		
		//接口测试
		LoveDemo ld = new LoveDemo();
		Love l = ld.getLove(); //new Teacher(); 多态
		l.love();
		//链式调用
		ld.getLove().love();
	}
}

猜你喜欢

转载自blog.csdn.net/JayFan_Ma/article/details/87099043