JAVA多态性进一步学习以及抽象类

对象的多态性

继承表示的是 is a 的语义
public class 汽车 extends 车库 {} 错误的
public class 学生 extends {} 正确的,因为任何一个学生都是人
强调的一个点:编程是对现实世界的模拟体现
class 动物 {}
class extends 动物 {}
x = new ();
动物 x = new (); 正确的 , 因为所有的猫都是动物,因为定义 class extends 动物 {}
x = new 动物 (); 错误,不是所有的动物都是猫,因为动物是猫的父类
一个对象 x 有两种形态,猫这类事物即具有猫的形态,又具有着动物的形态,这就是对象的多态性。简单说就是一个对象对应着不同类型
Object x=new Student();
Person x=new Student();
Student x=new Student();

多态在代码中的体现

父类或者接口的引用指向其子类的对象。例如:动物 x=new ()
public class Test1 {
public static void main ( String [] args ) {
动物 x1 = new 动物 ();
动物 x2 = new (); // extends 动物,表示猫具有动物的所有特性,同时猫具备一些自己
都有的特性
// x2.play(); 语法报错的原因是:编译器类型和运行时类型的问题
}
}
class 动物 {}
class extends 动物 {
public void play () {
System . out . println ( " 使劲的抓老鼠 ...." );
}
}
public class Test1 {
public static void main(String[] args) {
动物 x2 = new ();// extends 动物,表示猫具有动物的所有特性,同时猫具备一些自己
都有的特性
x2.play(); // 使劲的抓老鼠 ...
// 这里语法不报错的原因是动物类中定义了 play 方法,但是真正运行时系统则会发现实际上是
猫,所以需要调用的不是动物类中的方法,而是猫中定义方法
}
}
class 动物 {
public void play() {
System.out.println(" 使劲的睡觉 ....");
}
}
class extends 动物 {
public void play() {
System.out.println(" 使劲的抓老鼠 ...");
}
}
动物 dw = new ();
dw . play (); // 如果猫类中没有定义 play 方法,则执行的是从动物类中继承的 play 方法;如果猫类中也
定义 play 方法,则最终执行的是猫类中定义的方法
//dw. 抓苍蝇 () 语法错误,因为在编译时系统识别 dw 为动物类型,所以没有这个方法的定义
if ( dw instanceof ) {
(( ) dw ). 抓苍蝇 (); // 强制类型转换后则可以调用猫类中所定义的特殊方法,否则只能调用动物
类中定义的方法
}
  • 对象多态性可以使程序有良好的扩展,并可以对所有类的对象进行通用处理
public class Test1 {
public static void main ( String [] args ) {
需要使用动物的类 tt = new 需要使用动物的类 ();
tt . pp ( new 动物 ());
tt . pp ( new ());
}
}
class 需要使用动物的类 {
public void pp ( 动物 dongwu ){ // 假设定义方法时参数使用 pp( dongwu), 当前程
序就和猫类耦合了 , 如果需要切换到狗则必须修改源代码。但是参数类型为动物,则不管是猫还是
狗都能接收
dongwu . play ();
}
}
  • 多态引用时,构造子类对象时的构造方法的调用顺序
  1. 先调用超类的构造方法,多重超类首先调用最远超类的方法
  2. 然后再执行当前子类的构造方法

thissuper的用法

this 用于指代当前对象, super 用于指代父类的内存空间的标识

this关键字

this代表其所在函数所属对象的引用。换言之,this代本类的对象的引用

  • 当成员变量和局部变量重名,可以用关键字this来区分,this就是所在函数所属对象的引用
  • 对象调用了this所在的函数,this就代表哪个对象。一般方法调用默认加this
  • 通过this在构造函数中调用其他构造函数的时候,只能定义在构造函数的第一行,因为初始化动作要先执行,否则就会报错

super关键字

  • 当本类的成员和局部变量同名用this区分,当子父类中的成员变量同名用super区分父类
  • 当子父类中出现成员函数一模一样的情况,会运行子类的函数。这种现象,称为覆盖操作,这是函数在子父类中的特性。在子类覆盖方法中,继续使用被覆盖的方法可以通过super.函数名获取
  • this()表示调用当前类中的另外一个构造器,()中可以带有参数;super()表示调用父类中的某个构造器,()中可以带有参数
this() super()
this() 表示调用当前类中的另外一个构造器 ,() 中可以带有参数
super() 表示调用父类中的某个构造器, () 中可以带有参数

new 子类()的执行过程

首先上溯子类的所有祖先类,然后再从上向下逐步执行各个层次父类的构造器 Object— 爷爷的构造
父亲的构造器 ; 最后才执行子类自己的构造器
因为每个类的构造方法中默认第一句话为 super() 表示调用父类的无参构造器,除非编程 super
特殊情况 :
问题:父类已经定义了带参构造器后是否还有无参构造器?
没有,因为只有当不定义构造器时,系统才提供一个无参构造器。当自定义构造器时,系统不再提供无参构造器
因为子类的构造器中没有显式调用父类构造器,所以子类构造器方法中第一句是 super() 表示调用父类中的无参构造器,但是父类中没有无参构造器,所以报错
thissuper 的五种用法:
1 、从语义的角度上说, this 用于指代当前对象; super 用于指代父类对象
2 this() 表示调用另外一个构造器 ,super() 表示调用父类中的某个构造器, () 中的参数决定调用的是哪个
构造器
3 this. 成员属性用于表示当前对象的某个成员,一般用于局部变量和属性名称一致的场景下。 super.
员属性用于表示父类中定义的某个属性,一般用于子类中覆盖定义了某个父类属性的场景下。
4 this. 成员方法 () 用于表示当前对象的某个成员方法; super. 成员方法 () 用于表示当前类的父类中定义
的某个成员方法,一般用于覆盖定义时【就近原则】。
5 、在 static 方法中不允许使用 this/super 之类关键字

面向对象设计思想的要点

  • 认为客观世界由各种对象组成,任何事物都是对象,复杂的对象可以由比较简单的对象以某种方式组合而成
  • 把所有对象都划分成各种对象类,每个对象类都定义了一组数据和一组方法
  • 按照子类与父类的关系,把若干个对象类组成一个层次结构的系统
  • 对象彼此之间仅能通过传递消息互相联系

类间多态和类内多态

多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同操作

类间多态性

父子类之间的一种多态型,例如:动物 x = new ()
public class Test1 {
public static void main ( String [] args ) {
Animal a1 = new Cat ();
A a = new A ();
a . eee ( a1 );
}
}
class A {
public void eee ( Animal animal ){
animal . eat ();
}
}
class Animal {
public void eat (){
System . out . println ( "Animal.eat()" );
}
}
class Cat extends Animal {
@Override
public void eat () {
System . out . println ( " 就是爱老鼠 " );
}
}
class Dog extends Animal {
@Override
public void eat () {
System . out . println ( " 就是爱肉骨头 " );
}
}
  • 方法调用中的限制:
针对一个类对象有两种类型,一种称之为编译期类型,编译时系统识别对象的类型, 动物 x = new
()” 在编译时,系统识别 x 是动物类别的,所以只能调用动物类定义的方法,而不能调用猫中特殊
的方法。另外一种称之为运行时类型,也就是当程序运行系统识别的类型, new 谁就是谁
  • 覆盖的方法一定不能是private

类内多态性

在一个类对象上调用相同名称的方法,但是当参数不同时执行不同的动作
public class Test2 {
public static void main ( String [] args ) {
A2 a = new A2 ();
// a.pp();
// a.pp(12);
a . pp ( "shitou" );
}
}
class A2 {
public void pp () {
System . out . println ( "A2.pp()" );
}
public void pp ( Integer k ) {
System . out . println ( "A2.pp(int)" );
}
public void pp ( Object kk ) {
System . out . println ( "A2.pp(String)" );
}
public void pp ( int k1 , String k2 ){}
public void pp ( String k1 , int k2 ){}
}
方法名称相同,参数不同,和返回值类型无关
  • 参数个数不同
  • 参数类型不同
  • 参数顺序不同。注意不同参数名称顺序不同
public class Test1 {
public static void main ( String [] args ) {
Fa ff = new Fa ();
ff . pp ( new Integer ( 123 )); // 原则:最佳匹配原则
ff . pp ( 1.234 );
}
}
class Fa {
public void pp () {
System . out . println ( "Fa.pp()" );
}
public void pp ( Number kk ){
System . out . println ( "Fa.pp(Number)" );
}
public void pp ( Integer kk ){
System . err . println ( "Fa.pp(Integer)" );
}
}
不确定个数的参数
public class Test1 {
public static void main(String[] args) {
Fa ff = new Fa();
ff.pp(); // 最佳匹配原则
}
}
class Fa {
public void pp() {
System.out.println("Fa.pp()");
}
public void pp(int... pages) {// 不确定个数的参数
System.out.println("Fa.pp(int...)");
}
}

方法的重写和重载

要求:方法的名称一致。

方法的重写(覆盖)

要求:方法的名称一致
方法的重写 ( 覆盖 ) 一定发生在父子类之间
public class Test4 {
public static void main ( String [] args ) {
F4 f = new F4 (); f . pp ( 10 );
S4 s = new S4 (); s . pp ( 10 );
F4 fs = new S4 (); fs . pp ( 10 );
}
}
class F4 {
public void pp ( int k ) {
System . out . println ( "F4.pp(int)" );
}
}
class S4 extends F4 {
public void pp ( int k ) { // 子类中定义的同名同参数的方法覆盖了父类中的方法定义,如
果需要调用父类中的方法则需要使用 super.pp(k) 进行调用
System . out . println ( "S4.pp(int)" );
}
}
  • 执行规则:new谁运行谁的方法,和声明的类型无关
  • 方法的覆盖定义要求方法名称一致
@Override 注解可以使 IDE 工具在编译源代码时进行检查,如果有手写错误则 IDE 工具报错
  • 方法的参数一致(个数、类型、顺序),和参数名称无关
 
类型一致的问题
class Fa {
public void eat ( Integer kk ) {
System . out . println ( "Animal.eat()" );
}
}
class Son extends Fa {
@Override
public void eat ( Number kk ) { // 类型必须一致,即使父类类型都不可以, int
Integer 简单类型和包装类型也不可以。这里去除 @override 注解则不会有语法错误,这里不是
方法的重写,是方法的重载
System . out . println ( " 就是爱老鼠 " );
}
}
顺序一致的问题 , 系统识别方法依靠是方法名称和参数类型列表,和方法参数的名称无关。例如这里系统识别的方法为 ear(String,String) 。要识别顺序还得依靠类型的区别,例如 eat(int,double)
eat(double,int)
class Fa {
public void eat ( String s1 , String s2 ) {
System . out . println ( "Animal.eat()" );
}
}
class Son extends Fa {
@Override
public void eat ( String s2 , String s1 ) { // 系统不能识别变量名称
System . out . println ( " 就是爱老鼠 " );
}
}
  • 返回数据类型一致【面试】(因为如果返回类型不一致,则无法进行语法检查,例如父类返回 Double,而子类返回Integer,调用处语法检查是按照Double进行检查还是按Integer检查?允许父类中返回的是父类型,而子类中返回子类型,例如父类中返回的是Number类型,而子类中返回的Integer)
class Fa {
public Number eat ( double s1 , int s2 ) {
System . out . println ( "Animal.eat()" );
return 10. ;
}
}
class Son extends Fa {
@Override
public Integer eat ( double s2 , int s1 ) { // 允许子类中返回值类型是父类返回值
类型的子类型
System . out . println ( " 就是爱老鼠 " );
return 99 ;
}
}
  • 抛出异常一致,注意实际上允许子类抛出比父类更少的异常
class Fa {
public Integer eat(double s1, int s2) throws Exception {
System.out.println("Animal.eat()");
return 10;
}
}
class Son extends Fa {
@Override
public Integer eat(double s2, int s1) throws IOException {
System.out.println(" 就是爱老鼠 ");
return 99;
}
}
要求子类中的方法范围 >= 父类中方法范围
  • 静态方法覆盖和调用,用谁声明则调用谁的静态方法
只有静态方法覆盖定义父类中的静态,实际上系统不建议这种做法
//The method pp() of type Son must override or implement a supertype
method
public class Test1 {
public static void main ( String [] args ) {
// 直接使用具体类调用静态方法没有任何问题,使用哪个类就调用的是哪个类中定义
的静态方法
// Fa.pp();Fa...pp
// Son.pp();Son...pp
// 事实上静态方法也可以通过创建对象后,使用对象进行调用。声明变量的类型和具
体构建的类型一致,也不会有问题
// Fa ff=new Fa();
// ff.pp();
// 如果调用静态方法,则用谁声明调用谁的方法
Fa ff = new Son ();
ff . pp (); // 执行结果是 Fa...pp
}
}
class Fa {
public static void pp () {
System . out . println ( "Fa...pp" );
}
}
class Son extends Fa {
public static void pp () {
System . out . println ( "Son...pp" );
}
}

方法的重载

方法的名称相同,参数不同,和返回值类型无关。可以在一个类内或者父子类之间
调用规则:类型最佳匹配原则
class A5 {
public void pp (){
System . out . println ( "A5.pp()" );
}
public int pp (){} 语法错误,因为在系统中识别一个方法是用【方法名称 + 参数类型列表】
进行,系统会将这两个 pp 方法识别为同一个方法。注意:在一个类中不允许方法相同
public void pp ( int k ){
System . out . println ( "A5.pp(int)" );
}
}
class B5 extends A5 {
public void pp ( String k ){
System . out . println ( "B5.pp(String)" );
}
public void pp ( String k , int k1 ){}
public void pp ( int k , String k1 ){}
}
  • 要求:方法名称相同并且参数不同。参数不同有3种情况:参数个数不同、参数类型不同、参数顺序不同
和参数名称无关
  • 和返回类型无关
为什么返回类型不同判断不同的方法?
例如调用方法时不需要处理返回值 pp(10), 容易造成混淆
public double pp(int k){}
public int pp(int k){}
一个方法有返回值,调用处实际上可以不接收。假设系统可以按照返回类型将这两个方法识别为不同方法,则调用 pp(10) 就没办法判断到底执行哪个
  • 和范围无关

public void pp(){}
protected void pp(){}语法报错

  • 和方法抛出的异常无关
  • 方法的重载可以出现父子类之间,也可以是在一个类内。但是方法的覆盖一定是父子类之间,不能在一个类内实现方法的覆盖

抽象类

面向对象编程的核心是面向抽象编程,一般依赖抽象不依赖具体
public class A {
public void pp ( Pig pig ){} 如果进行切换类型,则必须进行修改源代码
}
-------------
public class A {
public void pp ( Animal animal ){} 这里可以任意更换 Animal 的子类
}
class Pig extends Animal {}
class Cat extends Animal {}
包含了抽象方法的类叫作 抽象类 ,所谓的抽象方法是指没有函数体的方法,抽象方法必须在子类中给出具体实现,而抽象类本身不能创建对象。 public abstract class A{}
  • 如果一个类中有抽象方法则这个类一定是抽象类
  • 抽象类中可以定义的内容与普通类一样,只是可以定义抽象方法
  • 抽象类中可以没有抽象方法
  • 普通类继承抽象类必须实现抽象类中所有的抽象方法
  • 抽象类一般是用来定义规范的,该规范的实现是通过子类来实现的

抽象类的特点

  • 方法只有声明没有实现时,该方法就是抽象方法【不是空实现方法体,没有{}】,需要被abstract饰,否则语法报错。抽象方法必须定义在抽象类中,该类必须也被abstract修饰
  1. 抽象方法只能被publicprotected或不加域修饰符修饰,抽象方法一定不能是private
  2. public void pp(){}不是抽象方法,只是方法的实现为空,有方法体
  3. public void pp(); 没有{}才是没有方法体,才是抽象方法,当然需要添加关键字abstract
  4. 不能定义抽象构造函数
  • 抽象类中可以定义构造器,也可以不定义构造器,使用系统默认提供的无参构造器,但是自定义构造器不能private
  • 抽象类不能是final class
  • 不能定义抽象静态方法
  1. 抽象类中可以有静态方法,但是必须有方法体,不能是抽象方法
  2. 允许抽象类直接调用静态方法
  • 抽象类不能直接创建对象,只能通过继承的方式由子类实现对应的抽象方法;
  1. 一般的使用方法为【动物 x=new ();
  • 所有抽象类的子类必须实现抽象父类中的所有抽象方法或者自己也声明成抽象类[没有实现所有的抽象方法]
  • 抽象类除了可以有抽象函数,也可以有非抽象函数
  1. 没有任何限制,允许属性、方法,也允许抽象方法

抽象类不可以被实例化

是因为调用抽象方法没有意义?
  • 抽象类必须有其子类覆盖了所有的抽象方法后,该子类才可以实例化,否则这个子类还是抽象类
强调:注意抽象类中可以包含构造器、析构器、抽象方法和方法以及静态方法等,也可以没有抽象方法

什么时候使用抽象类

  • 当一个类的一个或多个方法为抽象方法时
  • 当该类为一个抽象类的子类,并且没有为所有抽象方法提供实现细节或方法主体时
  • 当一个类实现一个接口,并且没有为所有抽象方法提供实现细节或方法主体时

抽象类和普通类的区别

  • 抽象类不能直接实例化,并且对抽象类使用 new 运算符是编译时错误
  • 抽象类允许(但不要求)抽象类包含抽象成员
  • 抽象类不能被密封
  1. 简单说就是被final修饰的类,密封类不能被继承,防止了恶意的派生

模板模式

定义抽象类的目的是提供可由其子类共享的一般形式,子类可以根据自身需要扩展抽象类

什么是模板模式

在模板模式 Template Pattern 中一个抽象类公开定义了总体的算法【算法骨架】,把没有办法在父类中实现的方法延迟到子类中具体实现。这种类型的设计模式属于行为型模式

何时使用

有多个子类共有的方法,且逻辑相同
重要的、复杂的方法,可以考虑作为模板方法

注意事项

为防止恶意操作,一般模板方法都加上 final 关键词

优点

  • 封装不变部分,扩展可变部分
  • 提取公共代码,便于维护
  • 行为由父类控制,子类实现。

缺点

每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

分析

冒泡排序算法固定可以定义在父类中,但是两个比较的算法不同确定,所以可以将具体比较算法延迟到 子类中实现。抽象父类中定义算法模板,具体的比较延迟到子类中进行实现
public abstract class BubbleSorter {
// 在父类中固化算法实现,没有办法实现的比较方法延迟到子类中提供实现
public final void sort ( Object [] arr ){ //final 表示这个方法是最终方法,不允许
子类覆盖
for ( int i = 1 ; i < arr . length ; i ++ ){
for ( int k = 0 ; k < arr . length - i ; k ++ ){
if ( bigger ( arr [ k ], arr [ k + 1 ])){
Object temp = arr [ k ];
arr [ k ] = arr [ k + 1 ];
arr [ k + 1 ] = temp ;
}
}
}
}
// 定义 protected 目的在于子类中提供实现
protected abstract boolean bigger ( Object obj1 , Object obj2 );
}

抽象类的作用

在面向对象方法中,抽象类主要用来进行类型隐藏。构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现 则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可 以是不允许修改的
通过从这个抽象体派生,也可扩展此模块的行为功能。为了能够实现面向对象设计的一个最核心的原则开闭原则 OCP ,抽象类是其中的关键所在
抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象
OOP 的核心是面向抽象编程
  • 在父类中固化算法骨架,在子类中实现特殊的方法
  • 依赖抽象

常见问题

  • 抽象类中有构造器吗?
  1. 有,用于给子类对象进行初始化
  2. new 子类时抽象父类的构造器一定会被执行
  • 抽象类可以不定义抽象方法吗?
  1. 可以的,但是很少见
  2. 目的就是不让该类创建对象
  • 抽象类一定是个父类吗?
  1. 是的
  2. 因为需要子类覆盖其方法后才可以对子类实例化
  • 创建对象时,加载对象的执行顺序
  1. 先加载父类的静态变量和静态初始化块
  2. 加载子类的静态变量和静态初始化块
  3. 加载父类的成员变量、初始化块
  4. 加载父类的构造方法
  5. 加载子类的成员变量、初始化块
  6. 加载子类的构造方法

类和类之间的耦合问题

OOP 要求类内高内聚、类间弱耦合 --- 客户需求变动
如何实现类和类之间松耦合 --- 使用抽象不用具体
public class A {
private BubbleSorter bs ; //PigSorter
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值