面向对象编程三大特征---封装、继承、多态 ,我们已经讲解了两个,今天主要讲解多态机制。
https://www.jianshu.com/p/68ddb5484ca2
创建父类对象的多种不同的实现方式即为多态。
多态性是允许你将父对象设置成为一个或更多的他的子对象"相等"的技术。
Person类 (父类)
--- Student (子类)
--- Worker (子类)
父类类型 父类引用 = new 子类类型();
例如: Person per1 = new Student();
per1.eat();
Person per2 = new Worker();
per2.eat();
我们在程序中定义的引用变量所指向的具体类型和通过该引用变量的方法调用在编程的时候并不确定,
当处于运行期间才确定。就是这个引用变量究竟指向哪一个实例对象,在编译期间是不确定的,只有运行期才能确定,
这样不用修改源码就可以把变量绑定到不同的类实例上,让程序拥有了多个运行状态,这就是多态。
我们在程序中定义的引用变量所指向的具体类型和通过该引用变量的方法调用在编程的时候并不确定,
当处于运行期间才确定。就是这个引用变量究竟指向哪一个实例对象,在编译期间是不确定的,只有运行期才能确定,
这样不用修改源码就可以把变量绑定到不同的类实例上,让程序拥有了多个运行状态,这就是多态。
public class Person{
public void eat(){
System.out.println("Person --- eat方法");
}
}
public class Student{
//重写父类eat方法
public void eat(){
System.out.println("Student --- eat方法");
}
}
public class Work{
//重写父类eat方法
public void eat(){
System.out.println("Work --- eat方法");
}
}
public class Test(){
public static void main(String[] args){
// 创建对象
Person per1 = new Person();
Person per2 = new Student();
Person per3 = new Student();
//还未执行时,JVM编译代码通过,但是具体执行那个eat方法,不知道,只有在执行时,才会根据具体对象来执行对应的方法。
per1.eat();
per2.eat();
per3.eat();
}
}
允许将子类类型的引用赋值给父类类型的引用,把不同的子类对象都当作父类来看。
比如你家有亲属结婚了,让你们家派个人来参加婚礼,邀请函写的是让你爸来,但是实际上你去了,或者你妹妹去了,这都是可以的,因为你们代表的是你爸,但是在你们去之前他们也不知道谁会去,只知道是你们家的人。可能是你爸爸,可能是你们家的其他人代表你爸参加。这就是多态。
案例分析1:
母:"男朋友长得怎样?";
女:"有点像港台明星";
母:"哇,好!明天带回家看看";
女朋友把我带到家,丈母娘一看,疯了!原来是像"曾志伟"!
港台明星是表示一个大的范围 包括:影视明星 歌星
影视明星: 成龙 曾志伟 古天乐 刘青云 林正英 梁朝伟 舒淇
歌星 : 刘德华 黄家驹 黄小琥
具体是谁?在见到面之后才能确定 例如上面所说,男朋友像"曾志伟"
案例分析2:
我们系统有使用人,定义了一个人的对象person,然后实际登陆系统的,有几种情况:
一种是系统管理人员,
一种是客户,
一种是系统的用户。
我们在前面只定义一个人来使用系统。但是后面到后台又会具体判断使用系统的人到底是什么人,这就是多态的实际意义。
案例分析3:
著名长篇小说《西游记》 ----- 讲的是得道高僧金蝉子去西天取经,历经磨难的故事。
高僧 玄奘 = new 孙悟空();
表面上是玄奘在打妖怪,但是实际上是孙悟空。
高僧 玄奘 = new 猪八戒();
表面上是玄奘在打妖怪,但是实际上是猪八戒。
高僧 玄奘 = new 沙悟净();
表面上是玄奘在打妖怪,但是实际上是沙悟净。
引用数据类型的类型转换
一、子类转换为父类:自动转换
切记顺序: 右边赋值给左边
父类类型 父类对象/引用 = new 子类类型();
向上转型对象不能操作子类新增的成员变量和方法。
向上转型对象可以操作子类继承或重写的成员变量和方法
如果子类重写了父类的某个方法,向上转型对象调用该方法时,是调用的重写方法。
二、父类转换为子类:强制转换
1.格式 子类类型 子类对象 = (子类类型) 父类对象的引用;
Student stu = (Student)new Person(); 这么写是完全错误的
Person per1 = new Person();
Student stu1 = (Student)per1; 这么写也是错误的。
正确的写法:
Person per2 = new Student();
per2.eat();
Student stu2 =(Student) per2; 这么写才是正确的
2.必须先有对象的向上转型,才能继续后面的向下转型,否则容易出现类型转换异常 ClassCastException
三、总结
1.父类引用指向子类对象,我们称这个过程为向上转型,属于自动类型转换。
2.向上转型后的父类引用变量只能调用它编译类型(父类本类)的方法,不能调用它运行时类型(子类类型)的方法。
这时,我们就需要进行类型的强制转换,我们称之为向下转型!
3.多态最核心:就是方法的多态 (和属性无关)。
4.实现多态的三个要点:
继承,方法重写,父类引用指向子类对象。
5.多态的实现标志
父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。
代码
package com.bjsxt.oop5;
public class CastTest {
public static void main(String[] args) {
//1.创建对象的方式
Person per = new Person();
Person per1 = new Student();
Person per2 = new Worker();
//2.调用方法和属性
per.eat();
per1.eat();
per1.name="张三";
// per1.school = "西三旗校区";
// per2.work();
//3.测试
/*Student stu1 = (Student)new Person();
System.out.println(stu1);*/
/* Student stu1 = (Student) per;
System.out.println(stu1);*/
Student stu1 = (Student) per1;
System.out.println(stu1);
}
}
//父类
class Person{
String name;
int age;
public void eat(){
System.out.println("person --- eat");
}
}
//子类
class Student extends Person{
//独有的属性
String school;
public void eat(){
System.out.println("Student --- eat");
}
}
//子类
class Worker extends Person{
public void work(){
System.out.println("子类Worker独有的方法 --- 工作");
}
public void eat(){
System.out.println("worker --- eat");
}
}
在向下转型过程中,必须将引用变量(父类对象引用)转成真实的子类类型,否则会出现类型转换异常ClassCastException。
代码
package com.bjsxt.oop5;
/**
* 在向下转型过程中,必须将引用变量(父类对象引用)转成真实的子类类型,
* 否则会出现类型转换异常ClassCastException。
*
* 1.谨记在对象向下转型时,必须先进行对象的向上转型。
* 父类类型 父类引用 = new 子类类型(); --- 向上转型
* 子类类型 子类引用 = (子类类型)父类引用; --- 向下转型
* 2. 上一节课,讲解的是自定义类(父类:Person 子类:Student Workder)
* 在实际开发当中,不仅仅有自定义的类,来进行类型转换,也有JDK已经存在的类,进行类型转换。
* JDK已经存在的类:
* Object --- 万类之祖
* String --- 字符串类 Object类的子类
* StringBuffer --- 字符串类(在不更改地址值的情况下,可以对字符串内容进行修改)
* 3.instanceof关键字
* 在类型转换之前,先进行判断
* 格式: 对象 instanceof 数据类型
* 结果: true false
* 4.关于父类类型作为方法的参数
* public void play(父类类型 父类引用)
* public boolean equals(Object obj)
* public void shout(Animal a)
* 好处:多态机制
* 传递实参: 可以是父类类型本类的对象
* 可以是父类类型的子类对象
* 重写equals方法
* 如果方法的参数是父类类型,则可以减少方法编写的次数(方法的重载)
*
*
*
*
*/
public class CastTest2 {
public static void main(String[] args) {
//1.定义父类对象
Object obj = new String("abc");
/* StringBuffer str = (StringBuffer) obj;
System.out.println(str);*/
if (obj instanceof StringBuffer){
StringBuffer str = (StringBuffer) obj;
System.out.println("StringBuffer---->"+str);
}else if(obj instanceof String){
String str = (String)obj;
System.out.println("String----->"+str);
}
}
}
运行结果
父类类型作为方法的参数
代码
package com.bjsxt.oop5;
//测试类
public class TestPloym {
public static void main(String[] args) {
//创建对象 调用方法
Animal a = new Dog();
animalCry(a);
//创建对象 调用方法
Animal a2 = new Cat();
animalCry(a2);
//创建对象 调用方法
Dog dog=(Dog)a;
dog.seeDoor();
}
/**
如果没有父类类型作为方法的参数,可能会写很多重复的代码,很麻烦。
*/
static void animalCry(Animal a){
a.shout();
}
/*static void animalCry(Dog dog){
dog.shout();
}
static void animalCry(Cat cat){
cat.shout();
}*/
}
//父类
class Animal{
public void shout() {
System.out.println("叫了一声!");
}
}
class Dog extends Animal {
public void shout() {
System.out.println("旺旺旺!");
}
public void seeDoor() {
System.out.println("看门中....");
}
}
class Cat extends Animal {
public void shout() {
System.out.println("喵喵喵喵!");
}
}
运行结果
代码
package com.bjsxt.oop5;
/**
* 技能: 认识final关键字
*
* 1.含义
* 最后的 最终的
* 2.修饰符
* 1.修饰属性(变量)
* 如果修饰变量的话,那么此变量就会变成常量。
* 常量: 就是固定不变的值。
* 例如: Math类的PI PI不能再变化
* 2.修饰类 (被阉割的类)
* 例如: public final class Math{}
* 特点: 不能被继承 ,但是允许有父类。
* 生活: 太监
* 3.修饰方法
* 可以被重载,但是不能被子类重写。
*
*
*
*/
public class FinalTest {
public static void main(String[] args) {
//1.使用final修饰变量
int a = 10;
a = 20;
final int b = 10;
// b = 20;
}
//定义方法 --- 方法的重载
public final void play(){
System.out.println("使用final修饰的方法--- play");
}
public final void play(String wanju){
System.out.println("使用final修饰的方法--- 可以发生重载");
}
}
//子类
package com.bjsxt.oop5;
public class FinalTest2 extends FinalTest {
//父类的方法,如果使用final修饰 则子类无法重写
public final void play(){
}
}
运行结果
扩展: 笔试题 --- 请问final finally finallize 三者之间的区别?
抽象方法和抽象类
·抽象方法
使用abstract修饰的方法,没有方法体,只有声明。定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。
例如: public abstract void study();
·抽象类
包含抽象方法的类就是抽象类。通过abstract方法定义规范,然后要求子类必须定义具体实现。
通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。
抽象类天生就是作为父类的角色存在。
·抽象类和抽象方法的基本用法以及使用要点
1. 有抽象方法的类只能定义成抽象类
2. 抽象类不能直接实例化,即不能用new来实例化抽象类,只能间接实例化(对象的向上转型,通过子类为父类实例化对象)。
3. 抽象类可以包含属性、方法、构造方法。但是构造方法不能用来new实例,只能用来被子类调用。
4. 抽象类只能用来被继承。
5. 抽象方法必须被子类实现(方法的重写)。
代码
package com.bjsxt.oop2;
/**
* 发现: 父类的方法经常被子类重写,例如Animal类的shout()方法
* 创建父类对象时,一般情况下又都使用向上转型方式进行实例化父类对象
* Animal a = new Dog();
* 这样一来,在父类调用shout方法时,都是调用被子类重写之后的shout方法
* 父类的shout()方法的方法体内容好像没有多大作用!
* 解决:去掉大括号内的方法体,由普通方法改为抽象方法。
*
* 引出抽象方法
* 抽象方法的定义格式:
* [修饰符] abstract 返回值类型 方法名称(参数列表);
* 例如: public abstract void shout();
*
* 引出抽象类
*
* 抽象类的定义格式:
* [修饰符] abstract class 类名称{}
* Animal a = new Cat();
*
* 唐僧 --- 父类
* --- 打妖怪
* 孙悟空 --- 子类 --- 重写 打妖怪
*
* 唐僧 玄奘 = new 孙悟空();
* 玄奘.打妖怪();
*
*
*/
// Animal 父类
public abstract class Animal {
// 属性
String name;
int age;
//构造方法
public Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
//抽象方法
public abstract void shout();
//普通方法
public void eat(){
}
//定义静态方法
public static void main(String[] args) {
}
}
package com.bjsxt.oop2;
public class Cat extends Animal {
// 属性
String color; // 颜色
//构造方法
public Cat() {
}
public Cat(String name, int age, String color) {
super(name, age);
this.color = color;
}
//发生方法的重写
@Override
public void shout() {
System.out.println("Cat ---- 喵喵的叫");
}
}
package com.bjsxt.oop2;
//子类
public class Dog extends Animal{
// 属性
String Varieties; // 品种
//构造方法
public Dog(){
}
public Dog(String name, int age, String varieties) {
super(name, age);
Varieties = varieties;
}
//方法 --- 重写的方法 父类的shout方法
public void shout(){
System.out.println("Dog --- 汪汪的叫");
}
}
package com.bjsxt.oop2;
public class AnimalTest {
public static void main(String[] args) {
//1.创建对象
//Animal animal = new Animal();
/* Dog dog = new Dog();
Cat cat = new Cat();*/
Animal dog = new Dog();
Animal cat = new Cat();
//2.调用方法
// animal.shout();
dog.shout();
cat.shout();
}
}
package com.bjsxt.oop5;
/**
* 需求:编写程序实现乐手弹奏乐器,乐手可以弹奏不同的乐器从而发出不同的声音。
* 可以弹奏的乐器包括二胡,钢琴和琵琶
* 技能:抽象类+方法参数的传递(参数类型:父类)
*
*
* 抽象方法的定义格式:
* [修饰符] abstract 返回值类型 方法名称(参数列表);
*
* 普通方法定义格式
* [修饰符] 返回值类型 方法名称(参数列表){
*
* 方法体;
* }
*
*/
public class AbstractClassTest {
public static void main(String[] args) {
//1.创建对象
Instrument i1 = new Erhu(); // 对象的向上转型
// 调用方法
i1.makeSound(i1);
}
}
//父类 乐器
abstract class Instrument{
//属性
//方法
//构造方法
public Instrument(){
}
//抽象方法
public abstract void makeSound(Instrument i);
}
//子类 二胡
class Erhu extends Instrument{
@Override
public void makeSound(Instrument i) {
System.out.println("二胡 ---- 阿炳 《二泉映月》 ");
}
}
//子类 钢琴 Piano
class Piano extends Instrument{
@Override
public void makeSound(Instrument i) {
System.out.println("贝多芬 ---- 《命运交响曲》");
}
}
//子类 Violin
class Violin extends Instrument{
@Override
public void makeSound(Instrument i) {
System.out.println("张艺谋 --- 《十面埋伏》");
}
}
接口interface
接口就是规范(抽象方法),定义的是一组规则,体现了现实世界中“如果你是…则必须能…”的思想。
如果你是天使,则必须能飞。如果你是汽车,则必须能跑。
如果你是好人,则必须能干掉坏人;如果你是坏人,则必须欺负好人。
接口的本质是契约,就像我们人间的法律一样。制定好后大家都遵守。
接口定义格式
[修饰符] interface 接口名字{
//1 静态全局常量
// 2 抽象方法 (组合而成 --- 规则 /规范)
// 3.JDK8 版本及上 版本 可以定义默认的方法/ 静态方法
}
类的定义格式
[修饰符] class 类名称{
// 属性 --- 变量
//方法
}
接口的作用
接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。
全面地专业地实现了:规范和具体实现的分离。 (规范--- 抽象方法 实现---指的是子类重写抽象方法 (普通方法))
抽象类还提供某些具体实现,接口不提供任何实现,接口中所有方法都是抽象方法。
接口是完全面向规范的,规定了一批类具有的公共方法规范。
接口和实现类不是父子关系,是实现规则的关系。比如:我定义一个接口Runnable,Car实现它就能在地上跑,Train实现它也能在地上跑,飞机实现它也能在地上跑。就是说,如果它是交通工具,就一定能跑,但是一定要实现Runnable接口。
区别
q 普通类:具体实现 ( 普通方法 / 静态方法)
q 抽象类:具体实现,规范(抽象方法)
q 接口:规范!
如何定义和使用接口(JDK8以前)
声明格式:
[访问修饰符] interface 接口名 [extends 父接口1,父接口2…] {
常量定义;
方法定义;
}
定义接口的详细说明:
q 访问修饰符:只能是public或默认。
q 接口名:和类名采用相同命名机制。
q extends:接口可以多继承。
q 常量:接口中的属性只能是常量,总是:public static final 修饰。不写也是。
q 方法:接口中的方法只能是:public abstract。 省略的话,也是public abstract。
要点
q 子类通过implements来实现接口中的规范。
q 接口不能直接创建实例,但是可用于声明引用变量类型。
q 一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是public的。
q JDK1.8(不含8)之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。
q JDK1.8(含8)后,接口中包含普通的静态方法、默认方法。
(源码)接口的使用
//测试类
public class TestInterface {
public static void main(String[ ] args) {
Volant volant = new Angel();
volant.fly();
System.out.println(Volant.FLY_HIGHT);
Honest honest = new GoodMan();
honest.helpOther();
}
}
/**飞行接口*/
interface Volant {
int FLY_HIGHT = 100; // 总是:public static final类型的;
void fly(); //总是:public abstract void fly();
}
/**善良接口*/
interface Honest {
void helpOther();
}
/**Angel类实现飞行接口和善良接口*/
class Angel implements Volant, Honest{
public void fly() {
System.out.println("我是天使,飞起来啦!");
}
public void helpOther() {
System.out.println("扶老奶奶过马路!");
}
}
class GoodMan implements Honest {
public void helpOther() {
System.out.println("扶老奶奶过马路!");
}
}
class BirdMan implements Volant {
public void fly() {
System.out.println("我是鸟人,正在飞!");
}
}
运行结果
接口中定义静态方法和默认方法(JDK8以后)
JAVA8之前,接口里的方法要求全部是抽象方法。
JAVA8(含8)之后,以后允许在接口里定义默认方法和类方法(静态方法)。
1. 默认方法
Java 8及以上旧版本,允许给接口添加一个非抽象的方法实现,只需要使用 default 关键字即可,这个特征又叫做默认方法(也称为扩展方法)。
默认方法和抽象方法的区别是抽象方法必须要被实现,默认方法不是。作为替代方式,接口可以提供默认方法的实现,所有这个接口的实现类都会通过继承得到这个方法。
课堂代码
package com.bjsxt.oop5;
/**
* 非抽象方法: 有 方法体的方法,统称为非抽象方法。
* 例如: 1 普通方法
* 2 静态方法
* 3 构造方法
*
* JDK8版本
* 接口里面可以定义:
* 1.抽象方法
* 2.静态方法
* 3.默认方法
* 4.剩余的方法 --- 普通方法 构造方法 都不允许。
*
*/
public interface InterfaceTest2 {
//默认的方法 --- 修饰符 default
default void introduction(){
System.out.println("默认的方法");
show();
}
//抽象方法
public void show();
}
2. 静态方法
JAVA8以后,我们也可以在接口中直接定义静态方法的实现。这个静态方法直接从属于接口(接口也是"类",一种特殊的类),
可以通过接口名调用。
如果子类中定义了相同名字的静态方法,那就是完全不同的方法了,直接从属于子类。可以通过子类名直接调用。
课堂代码
package com.bjsxt.oop5;
public interface IntegerfaceTest {
// 静态全局常量
public static final int a = 0;
public static final String name = "张三";
//抽象方法
public abstract void shout(); // 抽象方法的权限修饰符必须是public 原因: 子类必须重写
//静态方法
public static void main(String[] args) {
String str = new String("abc");
System.out.println(str);
//a = 12; // 报错的原因: 因为a是一个静态全局常量
System.out.println(a);
}
}
3. 静态方法和默认方法
本接口的默认方法中可以调用静态方法。
面向接口编程
面向接口编程是面向对象编程的一部分。
为什么需要面向接口编程? 软件设计中最难处理的就是需求的复杂变化,需求的变化更多的体现在具体实现上。我们的编程如果围绕具体实现来展开就会陷入”复杂变化”的汪洋大海中,软件也就不能最终实现。我们必须围绕某种稳定的东西开展,才能以静制动,实现规范的高质量的项目。
接口就是规范,就是项目中最稳定的核心! 面向接口编程可以让我们把握住真正核心的东西,使实现复杂多变的需求成为可能。
通过面向接口编程,而不是面向实现类编程,可以大大降低程序模块间的耦合性,提高整个系统的可扩展性和和可维护性。
面向接口编程的概念比接口本身的概念要大得多。设计阶段相对比较困难,在你没有写实现时就要想好接口,接口一变就乱套了,所以设计要比实现难!
AOP:(AOP Aspect Oriented programming)面向切面编程。
老鸟建议
接口语法本身非常简单,但是如何真正使用?这才是大学问。我们需要后面在项目中反复使用,大家才能体会到。 学到此处,能了解基本概念,熟悉基本语法,就是“好学生”了。 请继续努力!再请工作后,闲余时间再看看上面这段话,相信你会有更深的体会。
巩固接口案例题
代码
运行结果
``
面向对象编程三大特征---封装、继承、多态
最新推荐文章于 2022-10-26 16:22:13 发布