JavaSE 进阶 (2) 多态

多态概述(polymorphism)

多态: 指的是同一个方法调用,由于对象不同可能会有不同的行为。

现实生活中,同一个方法,具体实现会完全不同。比如:同样是调用人的 休息 方法,张三是睡觉,李四是旅游,数学教授是做数学题;同样是调用人 吃饭 的方法,中国人用筷子吃饭,英国人用刀叉吃饭,印度人用手吃饭。


多态的要点:

  1. 多态是方法的多态,不是属性的多态(多态与属性无关)
  2. 多态的存在要有3个必要条件:有继承/实现关系,有方法重写,父类引用指向子类对象
  3. 父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了

之所以每类人的吃饭方法都不一样,是因为,在人这个类的下边,中国人子类,美国人子类,印度人子类都对吃这个方法进行了重写,所以每类人的吃饭方法不一样。多态是指方法的多态也是因为这样。

多态中成员访问特点

/images/java/JavaSE 进阶 (2) 多态/1.png
(图1)

多态中,成员变量的访问特点

  • System.out.println(a.weight);
  • 编译的时候看的是左边,看的是父类中有没有,如果父类中有,不会报错,如果父类中没有,就会报错
  • 成员变量,编译看左边,运行也看左边

成员方法的访问特点

  • 成员方法编译的时候看的也是左边,看的是父类中有没有,如果父类中有,不会报错,如果父类中没有,就会报错
  • 成员方法,编译看左边,运行看右边

为什么成员变量和成员方法的访问不一样呢?

因为成员方法有重写,而成员变量没有

多态的好处和弊端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Animal {
	public void eat() {
		System.out.printun("动物吃东西");
	}
}

public class Cat extends Animal {

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

public class Dog extends Animal {

	
	@Override
	public void eat() {
		System.out.println("狗吃骨头");
	}
}

//动物操作类
public class AnimalOperator {
	public void useAnimal(Cat c) {
		c.eat();
	}
	
	public void useAnimal(Dog d) {
		d.eat();
	}
}

public class AnimalDemo {
	public static void main(string[] args) {
		//创建动物操作类的对象,调用方法
		AnimalOperator ao = new AnimalOperator();
		Cat c = new Cat();
		ao.useAnimal(c);  //猫吃鱼
		
		Dog d = new Dog();
		ao.useAnimal(d);  //狗吃骨头
	}
}

用多态进行简化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class Animal {
	public void eat() {
		System.out.printun("动物吃东西");
	}
}

public class Cat extends Animal {

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

public class Dog extends Animal {

	
	@Override
	public void eat() {
		System.out.println("狗吃骨头");
	}
	
	public void LookDoor() {
		System.oUt.println("狗看门");
	}

}

//动物操作类
public class AnimalOperator {
	public void useAnimal(Animal a) {  //Animal a = new Cat();   Animal a = new Dog();
		a.eat();
		//a.LookDoor();  //编译看左边, Animal中没有LookDoor()方法, 报错
	}
}

public class AnimalDemo {
	public static void main(string[] args) {
		//创建动物操作类的对象,调用方法
		AnimalOperator ao = new AnimalOperator();
		Cat c = new Cat();
		ao.useAnimal(c);  //猫吃鱼
		
		Dog d = new Dog();
		ao.useAnimal(d);  //狗吃骨头
	}
}

多态的好处:提高了程序的扩展性,具体体现: 定义方法的时候,使用父类型作为参数,将来在使用的时候,使用具体的子类型参与操作

多态的弊端:不能使用子类的特有功能 (比如:某个方法只有子类有,父类没有,如果要使用,编译看左边,父类中没有,编译不通过,无法使用)

多态中的转型

为什么要学习多态的转型?

  • 多态的弊端是不能使用子类的特有功能,而通过转型,我们就可以使用子类的特有功能

向上转型

  • 从子到父
  • 父类引用指向子类对象

向下转型

  • 父类引用转为子类对象
  • 从父到子

父类引用指向子类对象,我们称这个过程为向上转型,属于自动类型转换

向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法。这时,我们就需要进行类型的强制转换,我们称之为向下转型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Animal {
	public void eat() {
		System.out.println("动物吃东西");
	}
}

public class Cat extends Animal {

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

public class AnimalDemo {
	public static void main(string[] args) {
		//向上转型
		Animal a = new Cat();
		a.eat();
		//a.playGame();  报错
		
		//我现在想使用playGame()怎么办
		//①创建Cat对象
		Cat c = new Cat();
		c.eat();
		c.playGame();
		
		//②向下转型
		Cat c = (Cat)a;  //父类引用转为子类对象
		c.eat();
		c.playGame();
	}
}

多态转型内存图解

/images/java/JavaSE 进阶 (2) 多态/2.png
(图2)
/images/java/JavaSE 进阶 (2) 多态/3.png
(图3)
/images/java/JavaSE 进阶 (2) 多态/4.png
(图4)

为了防止在向下转型时,出现类型转换异常,在向下转型中使用 instanceof,加入 instanceof 进行判断

instanceof 是二元运算符,左边是对象,右边是类;当左边对象是右面的类或右面子类所创建的对象时,返回 true;否则,返回 false

案例(猫和狗多态版)

需求:请采用多态的思想实现猫和狗的案例,并在测试类中进行测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class  Animal {
	private String name;
	private int age;

	public Animal() {
	}

	public Animal(String name,int age) {
		this.name name;
		this.age age;
	}
	
	public void eat() {
		System.oUt.println("动物吃东西")
	}
	
	
//set/get......
}

public class Cat extends Animal {
	public cat(){
	}

	public Cat(String name, int age) {
		super(name, age);
	}
	
    @Override
	public void eat() {
		System.out.println("猫吃鱼");
	}
}

public class Dog extends Animal {
	public Dog(){
	}

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

    @Override
	public void eat(){
		System.out.println("狗吃骨头");
	}
}

public class AnimalDemo {
	public static void main(String[] args) {
		//按照多态的方式创建对象并进行测试
		Animal a = new Cat();
		a.setName("加菲");
		a.setAge(5);
		System.out.println(a.getName()+ "," +a.getAge());
		a.eat();
	
		a = new Cat("加菲"5);
		System.out.println(a.getName() + "," + a.getAge());
		a.eat();
	}
}

抽象类概述

java 中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类

1
2
3
public abstract class Animal {
	public abstract void eat();
}

抽象类特点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//抽象类
public abstract class Animal {
	//抽象方法
	public abstract void eat();

	public void sleep() {
		System.out.println("睡觉");
	}
}

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

public class AnimalDemo {
	public static void main(String[] args) {
		Animal a = new Cat();  //抽象类多态
		a.eat();
		a.sleep();  //sleep()通过继承得到的
	}
}

(1) 抽象类和抽象方法必须使用 abstract 关键字修饰

  • public abstract class 类名{ }
  • public abstract void eat( );

(2) 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类

(3) 抽象类不能实例化,即不能用 new 来实例化抽象类(即不能直接用抽象类创建对象)

  • 抽象类如何实例化呢?参照多态的方式,通过子类对象实例化,这叫抽象类多态

(4) 抽象类的子类

  • 要么重写抽象类中的所有抽象方法
  • 要么本身也是抽象类 (如果不将继承的抽象方法重写,那么自己本身也是抽象类)

抽象类通过多态使用

抽象类只能用来被继承

抽象类成员特点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//抽象类
public abstract class Animal {
	private int age = 20;
	private final String name = "动物";
	
	public Animal() {
	}
	
	public Animal(int age) {
		this.age = age;
	}
	
	public void show() {
		age = 40;
		System.out.println(age);
		//name = "老虎";报错 name是常量不能被修改
		System.out.println(name);
	}
	
	
	public abstract void eat();
}

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

public class AnimalDemo {
	public static void main(String[] args) {
		Animal a = new Cat();  //抽象类多态
		a.eat();
		a.show();  //通过继承得到的  40  动物
	}
}

(1) 成员变量

  • 可以是变量
  • 也可以是常量

(2) 构造方法

  • 有构造方法,但是不能实例化
  • 那么,构造方法的作用是什么呢?用于子类访问父类数据的初始化

(3) 成员方法

  • 可以有抽象方法:限定子类必须完成某些动作
  • 也可以有非抽象方法:提高代码复用性

抽象类可以包含属性,方法,构造方法。但是构造方法不能用来 new 实例,只能用来被子类调用,用于子类访问父类数据的初始化

案例(猫和狗抽像类版)

需求:请采用抽象类的思想实现猫和狗的案例,并在测试类中进行测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public abstract class Animal {
	private String name;
	private int age;

	public Animal() {
	}

	public Animal(String name,int age) {
		this.name = name;
		this.age = age;
	}
	
	public abstract void eat();
	
	
	//set/get......
}

public class Cat extends Animal {
	public Cat() {
	}

	public Cat(String name, int age) {
		super(name, age);
	}
	
	@Override
	public void eat() {
		System.out.println("猫吃鱼");
	}
}

public class AnimalDemo {
	public static void main(String[] args) {
		//按照多态的方式创建对象并进行测试
		Animal a = new Cat();
		a.setName("加菲");
		a.setAge(5);
		System.out.println(a.getName() + "," + a.getAge());
		a.eat();
	
		a = new Cat("加菲"5);
		System.out.println(a.getName() + "," + a.getAge());
		a.eat();
	}
}

接口概述

  • 接口就是一种 公共的规范标准,只要符合规范标准,大家都可以通用
  • Java 中的接口更多的体现在 对行为的抽象

接口是类层次的一种结构

  • public class 类名
  • Public interface 接口名

父类 → 本质上是具有共同的内容

接口 → 本质上是提供功能(老鼠夹子和猫都具有抓老鼠的功能)


接口就是比 “抽象类” 还 “抽象” 的 “抽象类”,可以更加规范的对子类进行约束。全面地专业地实现了:规范和具体实现的分离

从接口的实现者角度看,接口定义了可以向外部提供的服务。

从接口的调用者角度看,接口定义了实现者能提供那些服务。


区别

  • 普通类:具体实现
  • 抽象类:具体实现,规范(抽象方法)
  • 接口:规范!

声明格式 (JDK8以前)

/images/java/JavaSE 进阶 (2) 多态/5.png
(图5)
  • 访问修饰符:只能是 public默认
  • 接口名:和类名采用相同命名机制
  • Extends:接口可以多继承
  • 常量:接口中的属性只能是常量,总是 public static final 修饰。不写也是
  • 方法:接口中的方法只能是:public abstract。省略的话,也是 public abstract
  • 接口中没有构造方法,因为接口主要是扩展功能的,而没有具体存在,一个类如果没有父类,默认继承自 Object 类,实现类中无参构造方法的 super(),访问的是 Object 类中的无参构造方法,Public class A implemrnts B{} 相当于 Public class A extends Object implements B{}
技巧

(1) 子类通过 implements 来实现接口中的规范。

(2) 接口不能创建实例,但是可用于声明引用变量类型。

(3) 一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是 public 的。

(4) JDK1.8(不含8)之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。

(5) JDK1.8(含8)后,接口中包含普通的静态方法、默认方法(default)。

接口特点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public interface Inter {
	public abstract void show();
}

public class InterImpl implments Inter {

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

public class InterDemo {
	public static void main(String[] args) {
		Inter i = new InterImpl();  //接口的多态
		i.show();
	}
}
1
2
public abstract class InterImpl implments Inter {
}

抽象类 InterImpl 可以实现接口,只不过到时候需要提供一个 InterImpl 的子类才能使用。


(1) 接口用关键字 interface 修饰

  public interface 接口名

(2) 类实现接口用 implements 表示

  public class 类名 implements 接口名

(3) 接口不能实例化

  接口如何实例化呢?参照多态的方式,通过实现类对象实例化,这叫接口多态

  多态的形式:具体类多态,抽象类多态,接口多态

  多态的前提:有继承或者实现关系;有方法重写;有父(类/接口)引用指向(子/实现)类对象

(4) 接口的实现类

  要么重写接口中的所有抽象方法

  要么是抽象类

接口成员特点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public interface Inter {
	public int num = 10;
	public final int num2 = 20;
	
	public abstract void show();
}

public class InterDemo {
	public static void main(String[] args) {
		Inter i = new InterImpl();
		System.out.println(i.num);  //10
		System.out.println(i.num2);  //20
		//i.num = 40;  报错,默认前边加了一个final,被final修饰,不能修改
		//i.num2 = 30;  报错,被final修饰,不能修改
	}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//public class InterImpl implements Inter{
public class InterImpl extends Object implements Inter {
	public InterImpl() {
		super();
	}
	
	@Override
	public abstract void show() {
		System.out.println("show...");
	}
}

如果没有父类,则默认继承 Object,那么类中的构造方法里的 super() 调用的则是 Object 类的构造方法。


(1) 成员变量

  只能是常量

  默认修饰符:public static final

(2) 构造方法

  没有,因为接口主要是扩展功能的,而没有具体存在

  一个类如果没有父类,默认继承自 Object

(3) 成员方法

  只能是抽象方法

  默认修饰符:public abstract

类和接口关系

  • 类和类的关系

   继承关系,只能单继承,但是可以多层继承

  • 类和接口的关系

   实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口

  • 接口和接口的关系

   继承关系可以单继承,也可以多继承 (接口之间可以多继承)

1
2
3
4
5
6
7
// 类和接口的关系
public class InterImpl extends Object implements Inter1,Inter2,Inter3{
}

// 接口和接口的关系
public interface Inter3 extends Inter1,Inter2{
}

抽象类和接口的区别

(1) 成员区别

  抽象类   变量,常量;有构造方法;有抽象方法,也有非抽象方法

  接口    常量;抽象方法

(2) 关系区别

  类与类    继承,单继承

  类与接口   实现,可以单实现,也可以多实现

  接口与接口  继承,单继承,多继承

(3) 设计理念区别

  抽象类   对类抽象,包括属性、行为

  接口    对行为抽象,主要是行为


对设计理念区别进行详细说明

门和警报的例子

门:都有 open()close() 两个动作,这个时候,我们可以分别使用抽象类和接口来定义这个抽象概念

/images/java/JavaSE 进阶 (2) 多态/6.png
(图6)

随着科技的发展,现在的门有了报警的功能,现在有两种方案,一种是都放在抽象类中,一种是都放在接口中

/images/java/JavaSE 进阶 (2) 多态/7.png
(图7)

如果都放在抽象类中,那么每个门都有报警的功能,这是不太现实的

如果都放在接口中,那么并不是每个东西都有开门、关门的功能,例如: 报警器

开门、关门属于门本身固有的行为,而报警属于延伸的行为

/images/java/JavaSE 进阶 (2) 多态/8.png
(图8)

抽象类是对事物的抽象,例如门

接口是对行为的抽象,例如警报

案例(木门和电动报警门综合案例)

需求:请采用面向对象的思想实现木门和电动报警门的案例,并在测试类中进行测试

/images/java/JavaSE 进阶 (2) 多态/9.png
(图9)
/images/java/JavaSE 进阶 (2) 多态/10.png
(图10)

0%