详解Java面向对象编程之多态
作者:哈哈怪.... 发布时间:2023-08-28 19:17:06
Java面向对象编程之多态
一.对于多态的理解:
通俗点理解,多态其实就是一词多义,就是一种方法的多种状态,即不同的类对象,调用同一个方法名,有不同的实现效果,如下面这段代码块:
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("豆豆");
Cat cat = new Cat("花花");
dog.eat();
cat.eat();
}
}
对象dog和cat看似都调用了eat方法,都没有传参,按理说输出的结果应该一样,但其实不是这样的,让我们来看一下输出的结果:
这就是多态的一种表现,所属不同类的不同对象调用同一个方法名,却有着不同的实现效果。
二.多态的实现方法
Java中通过 方法重写(也叫方法覆写)、方法重载和接口实现多态(主要依赖于继承机制+方法覆写)
1.方法重载
方法重载十分好理解,就是子类和父类的方法名相同,但是参数个数或类型不一样,返回值不作要求,这里不再赘述
2.方法重写
对于方法重写,通常结合向上转型和向下转型两种形式进行应用,其中向上转型更为常见,向下转型相对使用较少
(1)向上转型:就是子类向父类转,向上转型最大的好处就是可以实现参数统一化,向上转型可以表现在三个地方:
其一:产生对象时:
注意:用这种形式创建的实例化对象dog1,其能调用的方法范围由父类Animal决定,即只能调用Animal类中的方法,而不能调用子类独有的方法,只有当子类有对父类的方法重写时,才调用子类重写后的方法!!!
其二:方法参数的传递:
其三:方法返回值的传递
向上转型的最大好处就是——参数统一化,父类引用可以接收子类所有对象
看下面这个例子:
完整代码为:
public class Animal {
public String name;
public Animal(String a){
name = a;
}
public void eat(){
System.out.println("食物");
}
public static void fun(Animal animal){
animal.eat();
}
}
public class Dog extends Animal{
public Dog(String name){
super(name);
}
public void eat(){
System.out.println("骨头");
}
}
public class Cat extends Animal{
public Cat(String name){
super(name);
}
public void eat(){
System.out.println("鱼");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("豆豆");
Cat cat = new Cat("花花");
dog.fun(dog);
cat.fun(cat);
}
}
结果为:
拆开来分析:
fun方法的参数为Animal类的实例化对象
Animal的子类对象,可以直接传入
也就是说,对于Animal类的所有子类实例化对象,均可以直接向fun方法传参,避免了重复性的写诸如 public static void fun(Dod d){}, public static void fun(Cat a){}等方法
(2)向下转型:向上转型是子类向父类转,向下转型则是将转为的父类还原为子类,这里用 还原 这个词是因为能向下转型的前提是:先发生向上转型且我们需要使用子类独有的方法时,才使用向下转型,也很好理解,父类不一定是子类,只有由子类转成的才可以向下转型还原,向下转型的形式如下:
Animal animal = new Dog("豆豆");
Dog dog = (Dog)animal;
基本类似与强制类型转换
注意:向下转型是有风险的,可能无法强制转换成功,这里可以引用instanceof类,用if语句判断,避免报错
if(dog instanceof Dog){
}
(3)方法重写的几点注意要求:
只能覆写成员方法,不能重写static静态方法,但是方法重载是可以重载static方法的
子类进行方法重写,子类方法的权限修饰符>=父类方法的权限修饰符,同时,子类并不能覆写父类的private方法,对于父类的包访问权限修饰方法,在不同包下的子类也不能覆写
用final修饰的方法也不能覆写哦(JDK中的String类就是一个final类)
返回值必须相同,或是向上转型的,即覆写的方法的返回值可以是父类方法返回类型的子类
注意方法覆写与方法重载的区别,方法覆写:子类与父类的方法名一样,参数、返回值类型均一样,如果只返回值类型不一样编译会报错
可使用@override 检验方法覆写是否一样
(4)最后是一道例题,容易掉坑:
public class A {
public A(){
this.func();
}
public void func(){
System.out.println("A");
}
}
class B extends A{
private int num;
public B(int num){
this.num = num;
}
public void func(){
System.out.println("B的num==" + num);
}
public static void main(String[] args) {
B b = new B(100);
b.func();
}
}
分析运行后会输出什么呢?
仔细想想,小心掉坑,答案在文章末尾给出
3.抽象类
对于方法的覆写,一般的继承关系下,子类是可以选择覆写也可以选择不覆写的,但在一些场景下,我们想对子类作出强制性覆写要求,这就引出了抽象类的概念
(1)抽象类用abstract修饰,抽象类是普通类的超集,它只是在普通类的基础上多了抽象方法,抽象方法是没有方法体的,形式如下:
(2)抽象类必须有子类继承
(3)抽象类无法实例化对象,仅能用子类new相应的对象
(4)普通子类继承抽象类,必须覆写所有的抽象方法,当子类仍为抽象类时,可以选择不覆写,依旧保留抽象方法
(5)abstract修饰符不能和final同时使用,也不能和private同时使用
4.接口
上面讲的抽象类虽然能实现方法的覆写,但还是有缺陷的,比如抽象类还是遵循单继承原则,一个类也只能继承一个抽象类,同时,在语义上,只要继承,就是A is B 的意思,有时候并不符合逻辑,故而又引出了接口这个概念
(1)接口的定义与使用
我们用关键字interface来定义接口,子类用关键字 implements来实现接口,同时,通常,在命名接口时,我们会用大写的字母“I”开头命名以示区别,如下面一段代码的接口名为 IMessage ,而对于实现接口的子类命名我们通常用Impl作后缀
(2)接口的特点:
接口中只有全局常量和抽象方法(JDK8之前,JDK8又扩展了default方法,了解即可),如:
public interface IMessage {
public static final int a = 10;
public abstract void print();
}
接口中只有public权限,且全部为全局常量和抽象方法,故而,在接口内,public、static、final、abstract可以省略不写,默认即为这些关键字,故上一段代码可以直接写成下面这段:
public interface IMessage {
int a = 10;
void print();
}
接口是没有单继承限制的,子类可以implements多个父接口,父接口之间用逗号隔开,如:
public class CImpl implements IB,IMessage{ public void print(){ } public void printf(){ }}
同时,接口之间也可以多继承,一个接口可以extends多个父接口
接口同抽象类一样,是不能直接实例化对象的,必须通过实现它的子类进行实例化
如果一个子类既有继承的父类,也有实现的接口,则先继承父类再实现父接口
(3)常用的JDK内置的两大接口
a:Comparable接口
当使用Arrays.sort()方法排序时,当排序对象为自定义的类时,sort方法不知道应该按照对象的什么属性进行排序,故而待排序的自定义类需实现该接口,并将抽象方法compareTo覆写,形式如下:
import java.util.Arrays;
public class Person implements Comparable<Person> {
// 两个属性,name和age
private String name;
private int age;
// 有参构造
public Person(String name,int age){
this.name = name;
this.age = age;
}
// 定义输出
public String toString(){
return name + "的年龄是" + age;
}
// 覆写compareTo方法
public int compareTo(Person o){
return (this.age - o.age);
}
public static void main(String[] args) {
Person p1 = new Person("言希",18);
Person p2 = new Person("温衡",16);
Person p3 = new Person("思莞",17);
Person []p = new Person[]{p1,p2,p3};
// 用sort方法排序
Arrays.sort(p);
System.out.println(Arrays.toString(p));
}
}
输出结果(按年龄升序):
b: Cloneable接口
Cloneable接口位于java.lang包中,顾名思义,就是用于克隆,在代码中也就是复制新的对象,新对象的属性方法都是从原对象中拷贝过来的,在实现该接口时,只需要覆写Object类提供的clone方法,如下面示例:
//实现Cloneable接口
public class Animall implements Cloneable{
private String name;
// clone方法
protected Animall clone() throws CloneNotSupportedException {
return (Animall)super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException{
Animall a1 = new Animall();
a1.name = "豆豆";
// a2由a1克隆而来
Animall a2 = a1.clone();
// 输出a2,和a1一致
System.out.println(a2.name);
// 但是a1并不是a2
System.out.println(a1 == a2);
}
}
结果如下:
补充:
Cloneable接口是标记接口,即它本身并没有任何抽象方法,当一个类实现了该接口,就表示该类具备了克隆能力
clone方法的源代码为protected native object clone() throws CloneNotSupportedException;,其中native也是一个关键字,表明是本地方法,即调用了C++的同名方法故而,我们还可以发现,native修饰的方法也是没有方法体的
没有方法体的一定是抽象方法 ⅹ错误,因为native方法也是没有方法体的
好了,多态的内容基本就是这么多了,java中的多态主要依赖于继承和方法覆写,而对于那些需要强制性覆写的方法,我们又引出了抽象类,再鉴于抽象类有局限,我们又学习了接口,整体上内容就是这么多了,注意的细节比较多,多敲代码理解理解更棒。
最后,将文中那道例题答案奉上:
答案解析来啦
从main方法开始,执行的第一句为 B b = new B(100);,因为B继承自A,故而执行B 的构造方法要先去执行A的构造方法,public A(){ this.func(); },这里注意,虽然是A的构造,但对象是B的,故这里的this,func()实际是B.func(),public void func(){System.out.println(“B的num==” + num);}
因为这里其实还没给num赋值成功,所以num现在还是默认值0,所以输出了答案的第一句 B的num == 0,然后接着执行B的构造方法,将100赋值给了num,最后执行b.func,也就输出了答案的第二句 B的num ==100
来源:https://blog.csdn.net/m0_58652786/article/details/122371888


猜你喜欢
- 1、Spring的事务管理主要包括3个接口TransactionDefinition:封装事务的隔离级别,超时时间,是否为只读事务和事务的传
- 我们经常用简单数据类型,比如int作为泛型Dictionary<TKey,TValue>的key,但有时候我们希望自定义数据类型
- 一、概念Tomcat的虚拟目录即在服务器上另选择一个webapps之外的文件夹存放项目文件,通过配置Tomcat的属性,实现访问。注:未配置
- 题目从命令行读入两个数组的长度和数组的值,其中第一行两个数na和nb代表aa和bb数组的长度代码import java.util.Scann
- 实践过程效果代码public partial class Form1 : Form{ public Form1()
- 第一种方法string s=abcdeabcdeabcde;string[] sArray=s.Split('c') ;fo
- 前言因为最近的项目需要使用录音功能,开始的想法是Button+OnTouchListener+Dialog实现,在大部分手机中都没问题,只有
- 本文实例讲述了Hibernate批量处理海量数据的方法。分享给大家供大家参考,具体如下:Hibernate批量处理海量其实从性能上考虑,它是
- 前言一般生成的PDF文档默认的文档底色为白色,我们可以通过一定方法来更改文档的背景色,以达到文档美化以及保护双眼的作用。 以下内容提供了Ja
- we can custom min heap or max heap by override the method compare.pack
- 一、定义一个配置类,自定义RedisTemplate的序列化方式@Configurationpublic class RedisConfig
- 在Android中,使用摄像头拍照一般有两种方法, 一种是调用系统自带的Camera,另一种是自己写一个摄像的界面。
- Maven搭建springboot项目本文是基于Windows 10系统环境,使用Maven搭建springboot项目Windows 10
- 一、函数式接口概念函数式接口在Java中是指:有且仅有一个抽象方法的接口。 当然接口中可以包含其他的方法(默认,静态,私有)函数式接口,即适
- 作者:京东零售 张宾1.背景在后台开发中,会经常用到线程池技术,对于线程池核心参数的配置很大程度上依靠经验。然而,由于系统运行过程中存在的不
- 合理的使用规则引擎可以极大的减少代码复杂度,提升代码可维护性。业界知名的开源规则引擎有Drools,功能丰富,但也比较庞大。在一些简单的场景
- 创建新的项目的时候,文件名一直追加,不分层对于刚用idea的小白,这个问题困扰了我好几天了,幸好现在还不怎么敲代码,下面给一个详细的解决方案
- 全局配置文件为mybatis-config.xml1、properties标签<properties resource="d
- 目录基本用法基于接口的 * 基于类的 * 异步函数拦截Autofac 集成基于接口的 * 基于类的 * 异步函数拦截Castle 是 200
- 开篇我们还是和原来一样,讲一讲做爬虫的思路以及需要准备的知识吧,高手们请直接忽略。首先我们来缕一缕思绪,想想到底要做什么,列个简单的需求。需