java程序员从笨鸟到菜鸟之(八)多态

1 多态

 

 定义:一个事物在不同时刻体现出来的不同状态

 

   例如:

             水:固态、汽态 、液态

   Animal a = new Cat() ;

对多态的理解:

         多态:就是指程序中定义的引用所指向的对象类型和引用所发出的方法编译时并不确定,而是在程序运行期间才确定;即一个引用到底会指向哪个类的实例对象,该引用变量发出的方法到底调用是哪个类中实现的方法,必须在由程序运行期间才能决定。

多态优点:因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

 
 多态存在的三个前提条件
 
 * 1) 必须有继承关系(如果没有继承关系,谈不上多态!)
 * 2) 必须有方法重写:子类出现了父类一样的 方法声明
 * 3) 有父类引用指向子类对象(向上转型)
 * 注意:多态的三个条缺一不可!
 *
 *多态的成员访问特点:
 * 成员变量
 * 编译看左边,运行看左边
 * 非静态的成员方法
 * 编译看左,运行看右(由于存在方法重写,所以最终运行的就是子类的成员方法)ps:如果父类就没有此方法,编译必定出错
 * 静态成员方法(静态方法算不上方法重写,静态直接跟类有关系!)
 * 编译看左,运行看左(静态的特点)
 * 构造方法:对对象进行初始化,由于是一种继承关系,所以是分层初始化!

实例1

package ceshi;
/* 1)牢记多态的三要素
 * 
 * **/

     //父类
class Fu{
	public int num = 10 ;	
	//父类的成员方法
	public void show(){
		System.out.println("Fu的方法");
	}
	
	//静态方法
	public static void function(){
		System.out.println("Fu的静态方法");
	}
}
     //子类
class Zi extends Fu{
	int num = 20 ;	
	public void show(){
		System.out.println("Fu的非静态方法被重写了:Zi的非静态方法");
	}
	
	public static void function(){
		System.out.println("Fu的静态方法被重写了:Zi的静态方法");
	}
}

//测试类
public class DuoTaiDemo {
	public static void main(String[] args) {

		Fu f = new Zi() ;
		/*理解1:父类的引用指向子类的对象(多态):向上转型
		 *理解2:这里定义了一个Fu类型的f,它指向Zi对象实例,由于Zi是继承Fu,所以Zi可以自动向上转型为Fu,所以f是可以指向Zi实例对象的;
		 *这样做好处:
		 *在继承中我们知道子类是父类的扩展,它可以提供比父类更加强大的功能,如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能;
		 * */
		
		/*下面三行说明的问题
		 * 成员变量:编译看左,运行看做
		         成员方法(重写):编译在左,运行看右
		         静态方法:编译在左,运行看左
		*/
		System.out.println(f.num);
		f.show();    //调用非静态成员方法		
		f.function();//静态方法
	}
}

多态优点(特点):
 *
 * 1)  提高代码的维护性(由继承保证)
 * 2)  提高代码的扩展性(由多态保证)
 * java的开发原则:低耦合,高内聚(回头补充)

多态劣势:不能访问子类的特有功能

理解:多态只能使用子类中重写父类的方法,不能使用其余子类的成员,即不能访问子类特有的属性和方法(代码说明)

多态有点霸王硬上弓的感觉:参照李隆基,李瑁,杨玉环的关系;本身自己就有,看到别人的,抢过来作为自己的

那么问题来了,如何访问子类特有的属性和方法呢?

解决方案
   1:创建子类的具体对象,来访问自己的特有功能;虽然可以解决多态的弊端,但是从内存角度考虑,需要创建子类对象,那么必须在堆内存开辟空间,

         弊端:耗费内存,浪费空间!
  2:既然多态的第三个前提条件:父类引用指向子类对象,那么可不可以将子类的引用指向父类对象呢?
  当然可以:向下转型将父类的引用强制转换为子类的引用:前提必须有父类的引用存在
  向下转型必须依赖于向上转型!

        向下转型的弊端

      出现异常:java.lang.ClassCastException----类转换异常!

      属于OOM异常一种---OutOfMemory:内存溢出!

        解决方法:instanceof关键字判断      

实例2:

 

package 测试集;

class Animal {
	public void show() {
		System.out.println("Animal的方法");
	}
}

// 子类
class Cat extends Animal {
	public void show() {
		System.out.println("Animal的方法被Cat重写");
	}

	// 特有方法(功能)
	public void playGame() {
		System.out.println("猫玩毛线...");
	}
}

// 子类继承父类(与上面形成多层继承)
class Tiger extends Animal {
	public void show() {
		System.out.println("Animal的方法被Tiger重写了");
	}
}

public class DuoTaiDemo4 {
	public static void main(String[] args) {

		Animal a = new Cat(); // 向上转型:父类引用指向子类对象
		a.show();// 非静态方法:编译在左,运行在右[理解:可以调用什么方法,取决于引用所属的类型,方法的内容取决于引用所指向的对象的类型]
		/*
		 * a.playGame();//报错:不能访问子类特有功能 解决方案1:创建子类具体对象
		 */
		Cat temp = new Cat();
		temp.playGame();

		/*
		 * 解决方案2: 向下转型:父类的引用强制转换为子类的引用
		 *
		 **/
		Cat c = (Cat) temp;
		c.playGame();
		/*
		 * 向下转型弊端:父类的引用错误的强制转换为另一个子类(多层继承容易出现) Tiger
		 * b=(Tiger)temp;//报错:不能从Cat转换为Tiger
		 * instanceof:运算符是用来判断一个引用指向的对象所属的类是不是某个类 即:在运行时指出对象是否是特定类的一个实例
		 **/
		if (a instanceof Tiger) {
			System.out.println("a指向的对象是Tiger类的一个实例");
		} else {
			System.out.println("a指向的对象是Cat类的一个实例");
		}
	}
}

2  抽象类

 * 抽象类(从抽象和类的角度去理解)
 * 抽象类的引入
 * 我们都知道在面向对象的领域一切都是对象,同时所有的对象都是通过类来描述的,但是并不是所有的类都是来描述对象的。如果一个类没有足够的信息来描述一个具体的对象,而需要其他具体的类来支撑它,那么这样的类我们称它为抽象类。
 * 抽象类的概念:
 *   每一个子类的方法(功能)不一样,不应该把父类定义为一个具体类,而是仅仅给出一个声明(模板)
 * 问题简答
 * 问题1:一个抽象类中可以有非抽象方法吗? 
 * 答:一个抽象类中可以有抽象方法,也可以有非抽象的方法(作为一个判断题记忆!)
 * 当一个类中如果有抽象功能(抽象方法)的时候,那么这个类一定要定义为抽象类!
 * *********************************
 * 问题2:抽象类能不能实例化(直接创建对象)?如果不能该如何实例化?
 * 答:抽象类不能直接通过new关键字直接创建对象 
 * 抽象类是通过抽象类多态形式来实例化,即:父类的引用指向子类对象,通过子类进行初始化!
 * *********************************
 * 问题3:抽象类无法直接实例化的原因?
 *   宏观解释
 * 答:由于抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能实例化的。
 *   微观解释:
 * 答:当一个类实例化之后,就意味着这个对象可以调用类中的属性或者方法了,
 * 但在抽象类里存在抽象方法,而抽象方法没有方法体,没有方法体就无法进行调用(没有意义!)
 * 既然无法进行方法调用的话,又怎么去产生实例化对象呢!
 * *********************************
 * 问题4:可以在父类的非抽象方法调用子类的抽象方法吗?
 * 答:可以(下面的代码已经说明了问题)
 * *********************************
 * 问题5:抽象类可以用final声明么? 
        答:不能,用final修饰类表示类不能被继承,而抽象类必须有子类,必须有继承(多态存在三要素);抽象类的出发点就是为了继承
 * 问题6:抽象类有构造方法吗?
 * 答:由于抽象类里会存在一些属性,那么抽象类中一定存在构造方法,其存在目的是为了属性的初始化。 
                并且子类对象实例化的时候,依然满足先执行父类构造,再执行子类构造的顺序!
 * 名词解释
 * (1) 抽象类多态: 
 *    强制子类必须重写当前抽象的父类中所有的抽象方法 
 *    优点:可以提高代码的维护性(里面继承关系保证!)
 * (2) 抽象类的子类的特点:
 *    子类必须重写当前抽象的父类中所有的抽象方法,否则子类必须为抽象类!
 *    但我们一般是通过子类进行初始化的,如果子类都是抽象类了,那么就无法创建对象,就没有意义了!
 * (3) 抽象类组成:
 *    包含一般类所包含的所有特性;例如:属性、方法(抽象,非抽象)
 * (4)抽象类:
 *    1)必须用关键字abstract修饰
 * (5)抽象方法的特点:
 *    1)这些方法基本上是没有执行代码的方法体{},由派生于该类的的类(子类)提供执行的代码(方法体)
 *    2)不能被调用(因为方法是抽象的,没有方法体,调用没有意义的)
 *    3)不能被private修饰(因为此时对于子类是不可见的,此时不能被继承,进而方法不能重写,失去了抽象类的意义!)
 *    4)抽象方法必须为public或者protected,缺省情况下默认为public
 * (6)通俗理解抽象类多态:
 *    父类先把子类中的方法体补充到自己的抽象方法中后,父类的引用指向父类此时的对象,所以无法访问子类的非抽象方法
 * (7)开发原则:
 *    普通类尽量不要去继承另外一个普通类,而是去继承抽象类
 */
实例3

abstract class Animal {

	// 抽象方法:没有方法体的一个方法
	public abstract void eat();

	public abstract void sleep();

	// 具体方法
	public void show() {
		System.out.println("show Animal....");
		sleep();//通过父类的非抽象方法调用父类的抽象方法
	}
	//静态方法
	public static void method1(){
		System.out.println("不需要抽象类多态的形式调用父类的方法,直接通过类名+方法名调用静态方法");
	}
}


// 子类是具体类
class Cat extends Animal {

	@Override
	public void eat() {
		System.out.println("猫吃鱼");
	}

	@Override
	public void sleep(){ 
		System.out.println("猫趴着睡觉..");
	}
	//子类特有的方法
    public void method(){
	System.out.println("子类特有的方法");
    }
}

// 测试类
public class AbstractDemo {
	public static void main(String[] args) {
		/* 
		 Animal a = new Animal();
                        报错原因:当前Animal已经被abstract修饰了,Animal:抽象类不能直接实例化
		*/ 
		
		// 多态:父类引用指向子类对象
		Animal a = new Cat(); // Animal是抽象类,抽象类的实例化---->抽象类多态形式
		a.eat();
		a.sleep();
		a.show();//访问父类的非抽象方法
	//a.method();//报错:没有为类型 Animal定义方法 method(),即:父类没有此方法
		Animal.method1();//访问父类的静态方法
	}
}

接口

    1)接口的概念:
 *  接口是一种特殊的抽象类,体现的是一种:扩展功能;
 *  比如:猫经过训练可以跳高(并不是所有的猫都具有跳高功能)
 * 2)如何表示接口:
 *  接口的用法(默认为abstract):
 *  语法格式:interface 接口名{}
 * 3)接口的组成内容:抽象方法和常量
 * 4)接口的特点:不能直接实例化(不能直接创建对象),类比似抽象类(其实是一个特殊抽象类)
 * 5)接口的子实现类和接口的关系:implements:
 * 6)接口的实例化:通过接口的子实现类实现(实际开发中用的就是子类的对象进行初始化!)
               语法格式:class 子实现类名    implments(实现) 接口名{}
 * 7)接口子实现类的特点:接口的子实现类是非抽象类(接口的子实现类是抽象类,没有意义!)
分割线------------------------------------------------------------------------------------------------------------------------------------

 *   问题:
 *        (1)接口里面的方法可以是非抽象方法吗?
 *      答:不可以,只能是抽象方法(区别抽象类)
 *        (2)接口中能不能有构造方法?
 *      答:接口中不能有构造方法

实例4

//定义跳高的接口(方法扩展)
interface Jump{
	
	/* 说明2:
	 * 抽象类中不能有非抽象方法
	public void jump(){
		System.out.println("猫可以跳高了...");
	}
	*/
	public abstract  void  jump() ;//默认public abstract(建议自己添加)

	
	/* 说明3:
	 * 不能有构造方法(就不是一个类,何谈构造方法)
	public Jump(){
		
	}
	*/
	
	/* 说明4:
	 * 只能有常量且修饰符只允许使用“公用”、“静态”和“终态”
	 * 默认为public static final(永远建议自己给出默认修饰符))
	 * */
	public static final String name="杂技猫";
}


// 接口的子实现类实现接口
class Cat implements Jump{

	@Override
	public void jump() {
		System.out.println("猫可以跳高了...");
	}
	
}

//测试类
public class InterfaceDemo {
	public static void main(String[] args) {
		/*说明5:
		 *创建接口对象
		 *报错:接口不能实例化(不是一个类,而且接口是抽象)
		Jump j = new Jump() ;//
		那如何创建对象呢?
		通过接口多态:接口的引用指向子实现类对象
		*/
		Jump j = new Cat();//接口多态
		j.jump();
		/*说明6:
		 * 访问接口中的常量
		 * 方式1:通过多态接口,利用引用调用变量
		 * 方式2:接口.常量名(原因:常量被static修饰的)
		 * **/
		System.out.println("我是一只"+j.name);
		System.out.println("我是一只"+Jump.name);
	}
}

总结:

 * 重要简答题(面试题)

 * 接口和抽象类的区别?

1)语法角度(成员和关系):

      在语法层次,java语言对于抽象类和接口分别给出了不同的定义。抽象类方式中,抽象类可以拥有任意修饰的成员,同时也可以拥有自己的非抽象方法;但是接口方式中,它仅能够有静态、不能修改的成员变量(但是我们一般是不会在接口中使用成员数据),同时它所有的方法都必须是抽象的;在某种程度上来说,接口是抽象类的特殊化; 对子类而言,它只能继承一个抽象类(这是java为了数据安全而考虑的),但是却可以实现多个接口。

********************************************************************************************************************************************

即:{   (1) 成员的区别
  成员变量:
  抽象类:成员变量可以是常量,也可以是变量
  接口:成员变量只能是一常量:存在默认修饰符:public static final
  构造方法
  抽象类:可以有无参构造,有参构造方法,作用是通过子类进行数据初始化(通过子类创建对象)
  接口:没有构造方法的
  成员方法的区别:
  抽象类:可以有抽象方法,也可以有非抽象方法
  接口:只能是抽象方法:存在默认修饰符:public abstract

            (2)关系的区别
  类与类的关系:
  继承关系:extends,java中只支持单继承,不支持多继承,但是可以多层继承!
  类与接口的关系:
  实现关系:implements,并且,一个类在继承另一个类的同时,可以实现多个接口
  (class 子实现类名  enxtends Object implements 接口名1,接口名2...)
  接口与接口的关系:

  继承关系:extends,可以支持单继承,也可以多继承!

        }

*********************************************************************************************************************************

2)设计层次(理念)
      一般来说它们存在如下三个不同点:
      (1) 抽象层次不同:抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
      (2) 跨域不同:抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。我们知道抽象类是从子类中发现公共部分,然后泛化成抽象类,子类继承该父类即可,但是接口不同。实现它的子类可以不存在任何关系(共同之处)。例如猫、狗可以抽象成一个动物类抽象类,具备叫的方法。鸟、飞机可以实现飞Fly接口,具备飞的行为,这里我们总不能将鸟、飞机共用一个父类吧!所以说抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is-a" 关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的,体现的是一种"like a"的关系, 仅仅是实现了接口定义的契约而已。
      (3) 设计层次不同:对于抽象类而言,它是自下而上来设计的,我们要先知道子类才能抽象出父类,而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、什么时候怎么实现它一概不知。比如我们只有一个猫类在这里,如果你这是就抽象成一个动物类,是不是设计有点儿过度?我们起码要有两个动物类,猫、狗在这里,我们在抽象他们的共同点形成动物抽象类吧!所以说抽象类往往都是通过重构而来的!但是接口就不同,比如说飞,我们根本就不知道会有什么东西来实现这个飞接口,怎么实现也不得而知,我们要做的就是事前定义好飞的行为接口。所以说抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。

实例5

package java作业;

/**
 * 猫狗案例,加入跳高的额外功能 
 * *************************
 * 分析:具体到抽象 
 * 猫: 
 * 成员变量:姓名、年龄 
 * 构造方法:有参、无参 
 * 成员方法:setXXX()、getXXX()
 * eat()、sleep()、playGame() 
 * *************************
 * 狗:
 * 成员变量:姓名、年龄 
 * 构造方法:有参、无参 
 * 成员方法:setXXX()、getXXX()、eat()、sleep()、lookDoor()
 * 
 * 抽取一个独立的类:
 * 动物类:Animal类:
 * 抽象方法: eat()、sleep()
 * *******************************
 * 猫继承自Animal,狗继承自Animal
 * 
 * 部分猫具有跳高功能:
 * 接口实现:跳高的功能---jump()方法
 * 
 * 实现:抽象-->具体
 * 
 * *******************************
 * 目的:理解抽象类,接口,继承关系
 * 
 */
// 抽象类
abstract class Animal {
	private String name;
	private int age;

	// 构造方法
	public Animal() {
		/* 会利用快捷方式自动生成构造方法(有参和无参)
		 * 无参:shift+alt+s----c
		 * */

	}
                //有参:alt+shift+s-->o
	public Animal(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	/* 自动生成setXXX,getXXX方法
	 * 快捷键:shift+alt+s---r
	 * 
	 * */
	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 abstract void sleep();

}
// 抽象类与子类的关系:继承

class Dog extends Animal {

	// 构造方法
	public Dog() {
		super();

	}

	public Dog(String name, int age) {
		super(name, age);

	}

	// 重写
	@Override
	public void eat() {
		System.out.println("我是一只狗,爱啃骨头");

	}

	@Override
	public void sleep() {
		System.out.println("我是一只狗,爱睡懒觉");

	}

	public void lookDoor() {
		System.out.println("我是一只狗,可以看门");
	}
}


class Cat extends Animal{
	// 构造方法
		public Cat() {
			super();// 默认

		}

		public Cat(String name, int age) {
			super(name, age);

		}

		// 重写
		@Override
		public void eat() {
			System.out.println("我是一只猫,我爱吃鱼");

		}

		@Override
		public void sleep() {
			System.out.println("我是一只猫,爱睡懒觉");

		}
		// 非重写的方法
		public void playGame() {
			System.out.println("猫爱玩毛线");
		}

}
class JumpCat extends Cat implements CatInterface {   // 实现接口的子类

	
	// 重写接口方法
	@Override
	public void jump() {
		System.out.println("我是杂技猫,我能跳高");

	}

}

// 现在突然出现了一只杂技猫:会跳高
// 接口
interface CatInterface {
	public void jump();//跳高
}

// 测试类
public class Demo {
	public static void main(String[] args) {
		// 接口类多态
		CatInterface temp = new JumpCat();
		temp.jump();// 猫能跳高
		
		// 抽象类多态
		Animal temp1 = new JumpCat();
		temp1.eat();
		temp1.sleep();

		/* 方式1:
		 * temp1.playGame(); 
		 * 不能调用子类的方法 
		 * 解决思路:向上转型
		 */
		Cat cat = (Cat) temp1;
		cat.playGame();//子类特有的方法
		
		/*方式2:
		 *创建JumpCat的对象,调用猫的所有行为(不建议)
		 * ***/
		JumpCat jumpCat=new JumpCat();
		jumpCat.eat();
		jumpCat.jump();
		jumpCat.sleep();
	//狗同理可得	
	}
}

后续会补充......

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值