Java 多态以及多态的实现

本文详细介绍了Java中的多态性,包括多态的基本概念、继承、多态的实现条件、重写、抽象类和抽象方法、接口、向上转型等。多态性允许父类引用指向子类对象,提供了代码的灵活性和可扩展性,但也存在不能使用子类特有功能的缺点。通过实例展示了如何实现和利用多态性。

编译环境

  • windows 10
  • JDK 1.8
  • eclipse



目录

   一、多态的基本概念
   二、继承
   三、多态的实现
   四、多态存在的三个必要条件
   五、重写
   六、抽象类和抽象方法
   七、接口
   八、向上转型实现多态
   九、多态的优缺点



前言

  多态,多态,多态到底是一个什么样的定义呢,充满着好奇心,决定去研究一番。



多态(Polymorphism)的基本概念

  多态意味着父类的变量可以指向子类对象。多态是同一个行为具有多个不同表现形式或形态的能力,就是同一个接口,使用不同的实例而执行不同操作。多态是基类型对象访问派生类重写的方法。



继承(Inheritance)

  一、简单说明
​  由于多态的很多内容都跟继承有相关,所以就先来介绍继承,然后再介绍多态。


  二、基本概念

​  继承是从已有的类中派生出新的类,新的类能具有已有类的数据和属性,并能扩展出新的能力。Java通常使用extends关键字来实现继承,比如说,class Parent{},class Children extends Parent{}。


  三、基类和派生类

​  我个人对于基类和派生类的理解是,基类就是父类派生类就是子类,比如说class A extends B{},其中的A就称为父类或者基类,B称为子类或者派生类。


  四、继承的特性

​ 1、子类拥有父类非private的属性和方法。

​ 2、子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。

​ 3、子类可以用自己的方式实现父类的方法。

​ 4、Java的继承不支持多继承,但是可以多重继承,通过单继承来实现类的继承,单继承是一个子类只能继承一个父类,多重继承是,比如说A继承B,B继承C。

​ 5、提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系约紧密,代码独立性越差)。


  五、构造器

​  子类是不继承父类的构造器的(构造方法或者构造函数),它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过super关键字调用父类的构造器并配以适当的参数列表。

  如果父类构造器没有参数,则在子类的构造器中不需要使用super关键字调用父类构造器,系统会自动调用父类的无参数构造器。


  六、代码实例

  1、继承的简单实现

public class Debug {
    /** Main method */
    public static void main(String[] args) {
        Children children = new Children();	//
        String string = children.testChildren();
        System.out.println(string);
    }
}

// 父类
class Parent {
    // 父类方法
    public String testParent() {
        return "Parent";
    }
}

// 子类继承父类
class Children extends Parent {
    // 子类方法
    public String testChildren() {
        return "Children visit " + testParent();
    }
}

输出:

Children visit Parent

解释说明:

​  通过上面的代码,可以简单的实现继承,子类就拥有了父类的所有属性和方法,除了使用private声明的属性和方法。


  ​2、继承中构造器的显式和隐式

public class Debug {
    /** Main method */
    public static void main(String[] args) {
        Children children = new Children();
    }
}

// 父类
class Parent {
    // 无参数构造器
    public Parent() {
        System.out.println("Parent");
    }
    
    // 有参数构造器
    public Parent(String string) {
        System.out.println(string);
    }
}

// 子类
class Children extends Parent {
    // 在子类自动调用super();
    // 如果要调用父类有参数的构造器, 需要在super的括号里添加对应参数
}

输出:

Parent

解释说明:

​ 通过上面例子可以知道,系统在子类会自动调用super();的方法叫做隐式调用,如果super中带有参数,则叫做显性调用。



多态的实现

1、重写

2、接口

3、抽象类和抽象方法



多态存在的三个必要条件

1、继承

2、重写

3、父类引用指向子类对象(向上转型



重写(Override)

​  一、基本概念

  重写是子类对父类的允许访问的方法的实现过程进行重新编写,返回值和形参都不能改变。外壳不变,核心重写。重写的好处在于子类可以根据需要,定义特定于自己的行为,也就是说子类能够根据细节要实现父类的方法。重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。


  二、方法重写规则

  1、参数列表与被重写方法的列表必须完全相同

  2、 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(Java 7 以上的版本)

  3、 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。

  4、父类的成员方法只能被它的子类重写。

  5、声明为final的方法不能被重写。

  6、声明为static的方法不能被重写,但是能够被再次声明。

  7、子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为prrivate和final的方法。

  8、子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非fianl方法。

  9、重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。

  10、构造方法不能被重写。

  11、如果不能继承一个类,则不能重写该类的方法。

类中成员的修饰符在同一类内可访问在同一包内可访问在子类内可访问在不同包可访问
public
protected×
default××
private×××

  三、代码实例

public class Debug {

	public static void main(String[] args) {
		PrintHello string1 = new PrintHello();
		PrintWorld string2 = new PrintWorld();

		string1.print(); // 调用父类的print方法
		string2.print(); // 调用重写后的print方法
        string2.test(); // 调用父类的test方法
	}
}

// 父类
class PrintHello {

	public void print() {
		System.out.println("Hello");
	}

	public void test() {
		System.out.println("Hello World");
	}
}

// 子类继承父类,并重写print方法
class PrintWorld extends PrintHello {

	@Override	// 重写print方法
	public void print() {
		System.out.println("World");
	}
}

输出

Hello
World
Hello World

解释说明

  重写的前提是存在继承,在上面的实例中,PrintWorld继承了父类PrintHello,具有父类的属性和方法,所以还可以使用父类的非重载方法,继承之后子类会优先调用子类重载的方法而不是父类的方法。如果使用的是eclipse,会看到这样的有这样的一个实心三角形,悬停在上面还会出现这样一句话:overrides PrintHello.print, 这就是代表重写的意思。

在这里插入图片描述



  四、延伸

  都知道重载和重写的关系,但是重载和重写也是有一定的区别的。重载是在同一个类里面,方法名字相同,而参数不同。返回的类型可以相同也可以不同,每个重载的方法都必须有一个独一无二的参数类型列表,最常用的地方就是构造器的重载。重写和重载的区别如下表:

区别点重载方法重写方法
参数列表必须修改不能修改
返回类型可以修改不能修改
异常可以修改可以减少或删除,但是不能抛出新的或者更广的异常
访问可以修改不能做更严格的限制(可以降低限制)



抽象类(Abstract class)和抽象方法(Abstract method)

  一、简单说明

  为什么先将抽象类和抽象方法,而不是讲接口呢,其实接口在Java中时一个抽象类型,时抽象方法的集合,所以先介绍抽象类和抽象方法后,可能对接口会更容易理解。


  二、抽象类的基本概念

  在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中,没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类除了不能实例化对象之外,类的其他功能依然存在,成员变量、成员方法和构造方法的的访问方式和普通类一样。由于抽象类不能实例化对象,所以抽象类必须被继承才能被使用。父类包含了子类集合的常见方法,但是父类本身时抽象的,所以不能直接调用父类使用这些方法。简单来说就是,定义一个抽象类,你不能直接访问这个抽象类的属性和方法,只能通过继承,通过子类来访问这些属性和方法

  抽象类表示的是一种继承关系,一个类只能继承一个抽象类,但是一个类却可以实现多个接口,抽象类使用abstract class 来定义


  三、抽象方法的基本概念

  抽象方法的具体实现由它的子类来决定,在父类中声明该方法为抽象方法,用abstract关键字来声明抽象方法,抽象方法只包含一个方法名,没有方法体;抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。


  四、抽象类的规定

  1、抽象类不能被实例化,如果被实例化,就会报错,编译无法通过,只有抽象类的非抽象子类可以创建对象。

  2、抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类。

  3、抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。

  4、构造方法,类方法(用static修饰的方法)不能声明为抽象方法。

  5、抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。


  五、代码实例

/* 定义一个抽象类, 直接调用抽象类 */

public class Debug {

	public static void main(String[] args) {
		Parent string = new Parent();
	}
}

// 抽象类
abstract class Parent {
	private String string;

	public Parent() {
		this.string = "";
	}

	public void setString(String string) {
		this.string = string;
	}

	public String getString() {
		return this.string;
	}
}

解释说明:

  如果是这样子使用的话,会发现编译器直接报错,因为不能直接访问抽象类的方法,我们只需要用一个子类来继承抽象类就可以了。

/* 定义一个抽象类, 子类继承抽象类 */

/* 
public class Debug {

	public static void main(String[] args) {
		Children string = new Children();
		string.setString("Test");
		System.out.println(string.getString());	// 输出
	}
}

// 抽象类
abstract class Parent {
	private String string;

	public Parent() {
		this.string = "";
	}

	public void setString(String string) {
		this.string = string;
	}

	public String getString() {
		return this.string;
	}
}

// 继承抽象类的子类
class Children extends Parent {

}
*/

// 或者可以这样子写
public class Debug {

	public static void main(String[] args) {
		Children string = new Children();
		string.setString("Test"); // 调用父类方法
		System.out.println(string.getString()); // 输出
	}
}

// 抽象类
abstract class Parent {
	protected String string;

	public Parent() {
		this.string = "";
	}

	abstract void setString(String string);

	abstract String getString();
}

// 继承抽象类的子类
class Children extends Parent {

	@Override	// 重写父类setString方法
	public void setString(String string) {
		this.string = string;
	}

	@Override	// 重写父类getString方法
	public String getString() {
		return this.string;
	}
}

输出:

Test



接口(Interface)

  一、基本概念

  一个类通过继承接口的方式,从而来继承的抽象方法,接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法,接口则包含类要实现的方法。接口无法被实例化,但是可以被实现,一个实现接口的类,必须实现接口内所描述的所有方法, 否则就必须声明为抽象类。使用interface关键字来创建接口。 一个类可以实现多个接口,一个接口可以继承一个或多个接口,但是只能继承一个类。接口和抽象类挺相似的,但是接口比抽象类更加灵活,因为一个子类只能继承一个父类,但是却可以实现任意个数的接口。

  接口是一种与类相似的结构,只包含常量和抽象方法,接口在许多方面都与抽象类很相近,但抽象类除了包含常量和抽象方法外,还可以包含变量和具体方法。(JDK 1.8 以后,接口里可以有静态方法和方法体了)


​  二、接口的说明

  1、接口与类相似点:

  • 一个接口可以有多个方法
  • 接口文件保存在后缀为.java的文件中,文件名使用接口名
  • 接口的字节码文件保存在后缀为.class的文件中
  • 接口相应的字节码文件必须在包名称想匹配的目录结构中

  2、接口与类的区别

  • 接口不能用于实例化对象
  • 接口没有构造方法
  • 接口中所有的方法必须是抽象方法
  • 接口不能包含成员变量,除了static和final变量
  • 接口不是被继承,而是被类实现
  • 接口支持多继承

  3、接口特性

  • 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为public abstract
  • 接口中可以含有变量,但是接口中的变量会被隐式的指定为public static final变量
  • 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法
  • 声明一个接口的时候,不必使用abstract关键字
  • 接口中的方法都是公开的

  4、抽象类和接口的区别

  • 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行,因为接口中的方法会被隐式指定为public abstract

  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的

  • 接口中不能含有静态代码块以及静态方法,而抽象类是可以有静态代码块和静态方法的


  三、代码实例

  1、接口回调实现多态:接口声明的变量指向其实现类实例化的对象,那么该接口变量就可以调用接口中的抽象方法。就好比说,使用列表时候是这样子定义的 List<Integer> list = new ArrayList<Integer>(); 其中的List就是接口,而ArrayList就是实现List的类。

/** 接口的创建和实现 */

public class Debug {
	/** Main method */
	public static void main(String[] args) {
		Animal dog = new Dog(); // 调用实现接口的类
		dog.run(); // 调用该类的方法
	}
}

// 接口
interface Animal {
	// 创建一个方法
	public void run();
}

// 实现接口
class Dog implements Animal {

	// 实现接口的方法
	@Override
	public void run() {
		System.out.println("Dog is running");
	}
}

输出:

Dog is running

解释说明:

​ 接口中的方法其实是抽象方法,通过类来实现。通过implements关键字来实现。



向上转型实现多态

  一、基本概念

  1、上转型对象:子类创建的对象赋值给父类声明变量,则称该对象为上转型对象,这个过程称为对象上转型。

  2、下转型对象:上转型对象再强制转化为创建改对象的子类类型对象,即将上转型对象还原为子类对象。

  3、向上转型:通过子类对象实例化父类对象。

  4、向下转型:通过父类对象实例化子类对象。


  二、代码实例

/** 向上转型和向下转型 */

public class Debug {
	/** Main method */
	public static void main(String[] args) {
		Dog dog = new Dog(); // 没有转型
		dog.eat();
		Animal animal = new Fish(); // 向上转型
		System.out.println(animal instanceof Animal);
		animal.eat();
		Fish fish = (Fish) animal; // 向下转型
		System.out.println(fish instanceof Fish);
		fish.eat();
	}
}

// 动物类
class Animal {

	public void eat() {
		System.out.println("Animal eat anything");
	}
}

// 鱼类
class Fish extends Animal {

	public void swim() {
		System.out.println("Fish can swim");
	}

	@Override
	public void eat() {
		System.out.println("Fish eat feed");
	}
}

// 狗类
class Dog extends Animal {

	public void run() {
		System.out.println("Dog can run");
	}

	@Override
	public void eat() {
		System.out.println("Dog eat bone");
	}
}

输出:

Dog eat bone
true
Fish eat feed
true
Fish eat feed

解释说明:

  调用Dog类是没有使用转型的,所以输出没有什么异议。但是在向上转型中,将Fish类转型为Animal类,就是子类转型为父类,在向上转型后,fish就属于Animal类了,但是输出却是Fish eat feed,理由是:在向上转型前,先调用的是new Fish(); Fish类先把父类的eat方法给重写覆盖了,所以调用的是Fish的方法。在输出结果也可以看到在向上转型后,fish是属于Animal类的,在向下转型后,fish又变回了Fish类了。向上转型用得比较多,向下转型用得比较少。(ps: 向上转型后,才能进行向下转型)



多态的优缺点

  一、多态的优点

  1、可替换性:多态对已存在的代码具有可替换性。

  2、可扩充性:增加新的子类不影响已经存在的多态性,继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。

  3、接口性:多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它来实现的。

  4、灵活性:它在应用中体现了灵活多样的操作,提高了使用效率。

  5、简化性:多态简化了对应用软件的代码编写和修改过程。


  二、多态的缺点

  1、不能使用子类的特有功能。



写在最后

  通过上面的了解,对多态还是有了比较浅显的认知,其实这只是对多态的很小的说明,多态在实际应用中非常广泛,需要慢慢去琢磨了解。

->回到顶部<-



参考

1、https://www.runoob.com/java/java-tutorial.html

2、https://lingcoder.gitee.io/onjava8/#/sidebar

3、https://blog.csdn.net/weixin_42867975/article/details/90300765

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值