Java abstract关键字【面向对象的第三大特征——多态】_java项目abstract(1)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

class Animal{
	void cry(){
	}
}

class Dog extens Animal{
	void cry(){
		System.out.println("汪汪~~");
	}
}

class Cat extens Animal{
	void cry(){
		System.out.println("喵喵~~");
	}
}

class test{
    public static void main(String[] args) {
		Animal animal;
		animal = new Dog();
		animal.cry();

		animal = new Cat();
		animal.cry();
    }
}

  • 从上述的小案例可以看出,对于继承,它可以实现代码的复用性,将父类中的方法给到继承的子类继续去使用,不需要再重写定义;而对于多态,它能够让类的调用这连这个类的类型都可以不必知道是什么,只需要这个对象具有哪个具体的方法即可,因为只需要去调用具体的方法,就可以通过父类的引用去调用子类对象然后访问子类重写的这个方法

2、要产生多态的条件

了解了多态的基本概念之后,我们要接下来说说要如何去实现多态

  • 要发生向上转型【父类引用 引用子类对象】
  • 子类和父类都要有同名的覆盖方法
  • 通过父类引用调用重写方法时形成运行时绑定

3、多态的好处

清楚了如何产生多态的条件,那我们就要去想,这个多态究竟有什么好处呢,为什么她是面向对象中最重要的一趴,接下来我们来说说这个

  • ①类调用者对类的使用成本进一步降低
  • ②能够降低代码的【圈复杂度】,避免使用大量的if-else
  • 由于这一点好处可能不太好理解,我们讲个具体的案例分析一下
class Geometry{
    void draw(){
        System.out.println("画一个几何图形");
    }
}
class Circle extends Geometry{
    @Override
    void draw() {
        System.out.println("画一个⚪");
    }
}
class Slice extends Geometry{
    @Override
    void draw() {
        System.out.println("画一个♦");
    }
}
class Triangle extends Geometry{
    @Override
    void draw() {
        System.out.println("画一个▲");
    }
}

public class test2 {
    public static void main(String[] args) {
        Circle circle = new Circle();
        Slice slice = new Slice();
        Triangle triangle = new Triangle();
        String[] shapes  = {"cycle","triangle","slice","cycle"};
        for(String shape : shapes)
        {
            if(shape.equals("cycle")){
                circle.draw();
            }else if(shape.equals("triangle")){
                triangle.draw();
            }else if(shape.equals("slice")){
                slice.draw();
            }
        }
    }
}

  • 从上述案例可以看出,我们是创建了一个几何图形类,然后用三个不同的几何图形类去继承这个父类,然后重写父类中的draw()方法,在主方法接口中我们可以看到,是通过一个遍历和判断的形式去访问这个Shapes对象数组中的值,通过equals()这个API去判断字符串是否符合,符合的话再用不同的对象去调用各自的方法
  • 从中我们可以看到,这个写法是比较冗杂又常规的,那有没有更好的方法呢?
  • 这个时候就可以使用我们多态的思想了,因为父类和子类中有同样且重名的方法,也进行了重写,我们可以利用将子类对象给到父类引用这个方法,去形成一个上转型对象,通过多态的形式来简化代码

OK,是时候展现真正的技术了😎

Geometry[] shapes = {new Circle(),new Triangle(),new Slice(),new Circle()};
for(Geometry shape : shapes){
    shape.draw();
}

  • 对,就是这么简便,我们可以直接在这个对象数组中放new出来的子类对象,然后在遍历这个数组的时候便发生了上转型对象,利用父类的引用shape去调用每一次传入进来的子类对象,就可以去独立地方法那个子类所重写的方法,这就很好地体现了多态的思想

③对于最后这一点,还是比较好理解的,就是可扩展能力强【无限地增加继承的子类】

  • 比如说你要加一个正方形类,只需要把这个类实现一下然后去继承一下Geometry父类即可,完全不需要改动任何的代码,这就是多态的优势之处

看完了上面这些,那您就算初步地了解了多态这个概念,但是并没有形成那个思维,只是一个引入,接下来我们便通过abstract这个关键字真正地进入多态的编程模式,感受面向抽象的编程思维🚶

三、abstract关键字【抽象类与抽象方法】

1、保姆级细致引入!!!

  • 首先我们来分析一下上面Geometry父类,大家有没有发现父类中的这个draw()方法写了和没写一样,因为完全就没有产生这么一个调用
class Geometry{
    void draw(){
        System.out.println("画一个几何图形");
    }
}

  • 为此我们就可以将此方法设置为抽象方法,在返回值类型前加上一个abstract关键字即可,然后抹去这个方法的方法体,因为抽象方法是不可以有方法体的
abstract void draw();

  • 但是这样写的话编译器马上就给我们报错了,说是一定要将这个类也抽象,这就形成了抽象类的概念,因为如果一个类中有抽象方法,那么这个类就必须是抽象类

在这里插入图片描述

2、注意事项

那这时候就有同学说,哇,这个关键字很厉害、很高级的感觉。是的,不然我不会前面铺垫那么多,才讲到这个关键字,厉害归厉害,但是在使用这个关键字的时候要注意的地方还是挺多的,让我们一起来看一下

1. 抽象类不能被实例化

  • 为什么不能被实例化呢?都说了它是抽象的嘛,怎么能会有一个具体的东西呈现给你呢,是吧。所以下面这步操作你不可以做👇
Geometry shape = new Geometry();

2. 类内的数据成员,和普通类没有区别

  • 此话怎讲呢?也就是这个抽象类,它除了不能对抽象方法做实现外,其他其实也和普通的类没什么区别,普通的成员变量和成员方法都是可以定义的
abstract class Geometry{
    private int num;
    public void func(){
        System.out.println("这是抽象类的一个实例方法");
    }
    abstract void draw();
}

3. 抽象类主要就是用来被继承的,所以不可以被final关键字修饰,抽象方法也是一样,需要被重写

  • 这个的话就要理解,因为抽象类的话已经不可以被实例化对象了,那你再不能继承它然后做一些操作的话,那这个类不就没用了吗,是吧😀,然后这个抽象方法的话,你在抽象类中没有重写它,在继承子类中也没有对其进行一个重写,也是很荒谬的一件事
  • 上一篇文章中我们有详细讲过final关键字,说到了final这个关键字如果去修饰方法的时候,那么这个方法就不可以被重写,如果去修饰类的话,那么这个类就不可以被继承,所以大家一定不要把abstract和final关键字写在一起,这是矛盾的

4. 如果一个类继承了这个抽象类,那么这个类必须重写抽象类中的抽象方法

  • 这个的话在上面也讲到过了,如果一个类去继承了一个抽象类,那么你就必须重写其中的抽象方法,否则的话要么你自己也要定成一个抽象类,然后继承你的子类就必须要重写你父类的方法,就如下面的代码一样
abstract class ff extends Geometry{
    
}

class gg extends ff{
    @Override
    void draw() {
        
    }
}

5. 不可以用staicprivate关键字修饰abstract方法

  • 因为static关键字指的是静态方法,是可以被所有同包下的类所调用的,然后其他类就可以重写这个方法自己用,这就违背了继承的特性,只有子类才可以重写父类的方法
  • 然后的话priavate关键字因为是私有的,那子类就是无法访问的,私有的成员变量还可以通过get()方法来访问一下,如果父类的方法都私有化了,那么子类是无法去访问的

6. 抽象类中也可以由构造方法,方便子类调用

  • 例如这里有个抽象类Animal,里面有个抽象方法eat(),可以看到对于抽象类来说和普通类其实也差不多,这一点上面也说到过,我在里面也提供了构造方法,这是为了子类可以直接方便调用
public abstract class Animal {
    private String name;
    private int age;

    public Animal(String name, int age){
        //提供构造方法,方便子类初始化
        this.name = name;
        this.age = age;
    }
    abstract void eat();        //抽象方法

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

}

  • 然后这里有个Rabbit类继承了这个抽象类,并且重写了抽象类里面的抽象方法,因为Rabbit类继承了动物类,所以可以看到直接很方便地调用了父类中的有参构造方法
public class Rabbit extends Animal{
    public Rabbit(String name, int age)
    {
        super(name, age);
    }

    @Override
    void eat() {
        System.out.println(this.getName() + " 不吃窝边草");
    }
}

  • 然后通过测试可以看到继承了抽象类的普通类也很好地实现了对应的功能
public class Test {
    public static void main(String[] args) {
        Rabbit rabbit = new Rabbit("Black Rabbit", 100);
        System.out.println("这只兔子的姓名为:" + rabbit.getName());
        System.out.println("这只兔子的年龄为:" + rabbit.getAge());
        rabbit.eat();
    }
}

在这里插入图片描述

3、具体案例

讲了这么多有关abstract关键字的注意事项,现在就让我们到实战中来看看它是具体怎么应用的吧👈

女朋友类(doge)

public abstract class GirlFriend {
    abstract void speak();
    abstract void cooking();
}


中国女朋友类(doge)

public class ChinaGirlFriend extends GirlFriend{
    @Override
    void speak() {
        System.out.println("你好");
    }

    @Override
    void cooking() {
        System.out.println("会做水煮鱼");
    }
}

美国女朋友类(doge)

public class AmericanGiralFriend extends GirlFriend{
    @Override
    void speak() {
        System.out.println("Hello");
    }

    @Override
    void cooking() {
        System.out.println("Can make roast beef");
    }
}


男孩类

public class Boy {
    GirlFriend girlFriend;
    void setGirlFriend(GirlFriend f){
        girlFriend = f;
    }
    void showGirlFriend(){
        girlFriend.speak();
        girlFriend.cooking();
    }
}


测试类

public class test {
    public static void main(String[] args) {
        GirlFriend girlFriend = new ChinaGirlFriend();
        Boy boy = new Boy();
        System.out.println("中国女朋友");
        boy.setGirlFriend(girlFriend);
        boy.showGirlFriend();

        girlFriend = new AmericanGiralFriend();
        System.out.println("--------------");
        System.out.println("美国女朋友");
        boy.setGirlFriend(girlFriend);
        boy.showGirlFriend();
    }
}

运行结果
在这里插入图片描述

  • 好,在看完这段代码后我们来讲解一下,可以看到,首先是创建了一个抽象的女朋友类,然后定义了两个抽象方法,一个是说话,一个是做饭
  • 然后一个中国女朋友类和美国女朋友类分别去继承这个父类,重写里面的方法,然后通过一个男孩类作为一个过渡(doge),设置一个方法去接收两个女孩,形成一个上转型对象,以此来实现多态

四、面向抽象的编程思维

在看完abstract关键字的应用之后,您对多态有没有形成一个概念了呢,其实要实现多态还是要有一个面向抽象的编程思维,这一点是很重要的🔑

  • 我们在企业开发中经常是使用abstract类,其原因是abstract类只关心操作,不关心这些操作的具体实现,可以使程序的设计者把主要精力放在程序的设计上,而不必拘泥于细节的实现(将这些细节交给子类的设计者),这样就可以使得整体项目框架设计者不必把大量的时间和精力花费在具体的算法上
  • 举个很简单的现实生活中的例子,一家出版世界地图的厂家,当设计师在设计一块国家区域的时候,不必去考虑诸如城市中的街道牌号等细节,细节应当由抽象类的非抽象子类去实现,这些子类可给出具体的实例,从而去完成程序功能的具体实现。
  • 所以当我们在设计一个小程序时,可以通过在abstract类中声明若干个abstract方法表明这些方法在整体系统设计中的重要性,方法体的内容细节由它的非abstract子类去完成

———— 以上选段摘自耿祥义《Java2实用教程》

五、多态的经典案例剖析

了解了面向抽象的编程思维,以及看了这么多的有关多态的小案例,接下来就让我们到实战中感受一下多态所带来的魅力吧🍁

1、几何体的体积计算

(1)整体代码展示
//构造一个抽象几何形状类 —— 实现不同子类几何形状面积的求解
public abstract class Gemotrey {
    public abstract double getArea();
}

//柱类 —— 面向抽象类Gemotrey,为具体底面几何图形提供总抽象类接口
public class Pillar {
    Gemotrey bottom;        //底面几何图形对象
    double height;          //柱体的高

    //传入具体的底面几何图形和柱体的高
    public Pillar(Gemotrey bottom, double height) {
        this.bottom = bottom;
        this.height = height;
    }

    //对外获取柱体体积
    public double getVolume(){
        if(bottom == null){
            System.out.println("没有底,无法计算面积");
            return -1;
        }
        return bottom.getArea() \* height;
        //通过具体的几何图形去重写抽象父类的获取面积方法
    }
}


//圆类,继承自抽象类Gemotrey
public class Circle extends Gemotrey{
    double r;

    public Circle(double r) {
        this.r = r;
    }

    @Override
    public double getArea() {
        return 3.14 \* r \* r;
    }
}


//矩形类,继承自抽象类Gemotrey
public class Rectangle extends Gemotrey{
    int a,b;

    public Rectangle(int a, int b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public double getArea() {
        return a \* b;
    }
}

//测试类
public class test {
    public static void main(String[] args) {
        Gemotrey bottom;    //几何形状底面对象
        Pillar pillar;      //柱类对象
        int height = 50;

        //1.无底的对象
        bottom = null;
        pillar = new Pillar(bottom,height);
        System.out.println("-----------");
        System.out.println("无底的对象面积为:" + pillar.getVolume());

        //2.圆形底对象
        bottom = new Circle(10);
        pillar = new Pillar(bottom,height);
        System.out.println("-----------");
        System.out.println("圆形底对象面积为:" + pillar.getVolume());

        //3.矩形底对象
        bottom = new Rectangle(20,15);
        pillar = new Pillar(bottom,height);
        System.out.println("-----------");
        System.out.println("矩形底对象面积为:" + pillar.getVolume());
    }
}

在这里插入图片描述

(2)详细分析
  • 对于上述求求解几何体的体积,是多态的众多案例中非常经典的一道,因此我拿出来做讲解
  • 我们知道,要求一个柱体的体积,则要知道其底面面积和高,对于高,就是一个具体的数字,相乘即可,但是对于底面,却是【千奇百怪】,有圆底、方底、三角底甚至是椭圆底,那对于不同的形状类,我们要怎么去分别计算其底面积呢
  • 在没学习多态之前,相信大家一定都是这样做的,每一个底面都定义成一个类,然后这个类中有它们各自的计算面积的方法,在主方法中传入对应的底面边长和高,然后去调用每个几何类中的getArea()方法,但是大家有没有发现这个getArea()方法是大家都会用到的,而且如果你要增加一种柱体,还要再重新写这个getArea()方法,需要大幅度得改动代码
  • 这时就需要用到我们多态的思想,可以将这个getArea()方法封装成一个几何体抽象类,将这个getArea()方法设置为抽象方法,然后每一个几何形状类去继承这个类,重写他们各自获取面积的方法,但是这还不够,因为你不知道此时是哪个柱体,因为你需要在定义一个柱体类,在这个柱体类中声明一个抽象类Geometry的对象作为其成员变量,然后在主方法中分别传入对应几何形状类的对象即可,这就可以使得不同的柱体所获取到的底面是不同的,而去调用不同类的获取面积的方法,形成了一个上转型对象,也自然地体现了多态的思想

2、手机里的SIM卡

(1)整体代码展示

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

,可以将这个getArea()方法封装成一个几何体抽象类,将这个getArea()方法设置为抽象方法,然后每一个几何形状类去继承这个类,重写他们各自获取面积的方法,但是这还不够,因为你不知道此时是哪个柱体,因为你需要在定义一个柱体类,在这个柱体类中声明一个抽象类Geometry的对象作为其成员变量,然后在主方法中分别传入对应几何形状类的对象即可,这就可以使得不同的柱体所获取到的底面是不同的,而去调用不同类的获取面积的方法,形成了一个上转型对象,也自然地体现了多态的思想

2、手机里的SIM卡

(1)整体代码展示

[外链图片转存中…(img-rDAGm22d-1715325890057)]
[外链图片转存中…(img-nfGhcCda-1715325890057)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值