聊聊Java的面向对象

面向过程与面向对象

说到面向对象,我们先来看看和面向对象相对的面向过程的概念。

什么是面向过程?面向过程,顾名思义“过程”是重点,可以理解为当我们要解决某个问题时,会把这个问题里的步骤进行拆分细化,然后按照指定的顺序来执行这些步骤。举例说明我想吃米饭,那么在面向过程中,我需要进行插秧,施肥,收获,放入电饭锅,最后才可以吃饭。假如在水稻种植过程中出现了病虫害,说不定都撑不到收获的季节,那么最后的米饭是不是就吃不上了。这样为了吃一口米饭是不是就显得很繁琐,那我宁愿饿死也不吃了。再假如第二天我又想吃面条了,那我就又得先去播种小麦,施肥,收割,制成面粉,制成面条,煮面,最后才吃上面条。从吃米饭和吃面条来看,支撑我生命活动的非常重要的进食过程是不是非常的麻烦,没有办法,这就是面向过程的着重“过程”之所在。

void 吃米饭(){
    插秧();
    施肥();
    收获();
    放入电饭锅();
    吃饭();
    
}

void 吃面条(){
    播种();
    施肥();
    收割();
    制成面粉();
    制成面条();
    开水煮面();
    吃饭();
}

再来看看面向对象,何为面向对象?面向对象即所有的事物都可以抽象成对象来表示。

  • 什么是对象呢?
    • 一个人,一滴水,一个星球等等,都可以看作为对象,也就是最常说的java中的万物皆对象。
  • 如何表示对象呢?
    • 将需要描述的对象的特征提取出来,定义为这个对象的属性,就可以描述一个对象。以一个人为例,可以使用姓名,性别,年龄,身份证号等属性来描述这个对象。

还是以支撑生命活动的进食操作为例,在面向对象中,可以抽象出以下几个对象,①将我抽象为对象,我有吃饭的能力。②将饭馆抽象为对象,他提供点餐的能力。③将粮油公司抽象为对象,他提供收购和销售的能力。④将农民抽象为对象,他提供种植和销售的能力。那么对于我而言,我想吃米饭还是想吃面条,就不需要我再去从水稻小麦的种植开始,亲力亲为。我只需要来到饭馆,使用饭馆提供的点餐功能,即可吃到我想吃的东西。虽然我是向饭馆点餐,但是饭馆也不需要关心水稻小麦是怎么种植的,他只需要从粮油公司手中购买所需的原材料即可。粮油公司也不需要知道水稻小麦的种植过程怎样,他只需要调取农民的销售功能即可获得收获来的水稻和小麦。

{
    吃饭(){
        饭馆.点餐(米饭);
    }
}

饭馆{
    点餐(食物){
        粮油公司.销售();
        制作食物(食物);
    }
    
    制作食物(食物);
}

粮油公司{
    收购(){
        农民.销售();
    };
    
    销售();
}

农民{
    种植(){
        播种();
        施肥();
        收割();
    }
    
    销售();
    
}

面向对象实现后,乍一看来似乎比面向过程更加复杂,加入了我以外的其他角色。但是仔细看下,其实面向对象比面向过程有优点的:

  1. 首先将粮食从种植到最终吃到嘴里的过程细化,由不同的对象来完成各自的事情,每个对象只需要干属于他的事情即可,这样可以使该对象更加专注他自己领域的事情,不需要关心其他对象干的事情,实现了解耦。每个对象自己干的事情很单一了,这也符合功能原子性的设计思想。
  2. 解耦后的维护及扩展更加便捷了。当其中某个环节出现问题时,只需要找到相应的对象,解决问题即可。

面向对象的三大特征

封装

将数据或方法隐藏到对象的内部,对外只提供公共的接口供调取所用。具体来说就是可以使用private关键字将对象的属性私有化,然后对外提供public修饰的getter,setter方法。

下面是访问修饰符的作用范围:

访问修饰符本类同一个包子类其他包
private×××
default××
protect×
public
继承

继承可以理解为父辈的资源传承给后代。例如父类是鸟类,他有羽毛,有飞翔的能力,那么继承了鸟类的后代如老鹰,它也就有了羽毛和飞翔的能力。同时子类自己还可以有其自己的属性,例如老鹰有超凡的视力。

继承有以下几点需要注意的地方:

  1. 继承只能是单继承,即子类只能继承一个父类。思考一下就很容易想清楚,一个孩子只能有一个亲身父亲,如果可以出现了多个亲生父亲,那么世间岂不乱套了
  2. 父类不能强转为子类,而子类默认可以隐式的转换为父类。这个可以这样理解,一个孩子学会了打游戏技能时,当他唯一的亲生父亲想要强行装嫩当儿子时,父亲其实是不会孩子的打游戏技能的,因此是没有办法装嫩的。
多态

多态是继承的一种体现,父类可以有很多的子类,因此父类的引用指向不同的子类实现。

以下图的刺激战场中的载具对象来说:

在这里插入图片描述

不管是跑车还是蹦蹦还会摩托,它们都有一个共同的父类–载具,载具有轮子的属性以及载人运输的能力。因此上图中的所有的车都有轮子,都可以载人跑毒。子类可以提供多种父类功能及属性的形态,这就称之为多态。

关于构造方法

  • 构造方法没有返回值,虽然称之为方法,但是它却不是真正的方法。它是一种创建对象的特殊的声明方式。
  • 使用new关键字调用构造方法,当对象创建成功后,构造方法有且仅有一次运行。
  • 构造方法是不能够继承的,也就是说父类的构造方法就是父类自己的,子类是无论如何也获取不到的。
  • 子类的构造方法调用时,一定会调用父类的构造方法。因为子类继承父类后,可以访问父类的属性,那么就必须在子类对象创建出来之前先获取到父类的属性,因此会先创建父类对象。子类的每一个构造方法的第一行都有一行隐式的super(),用于创建父类对象。

关于重载与重写

重载(overload)

方法名相同,方法参数不同称之为重载。与方法返回类型没有关系。

重写(override)

子类继承了父类,在子类中对继承自父类的方法进行覆盖,重新进行了实现,这一过程称之为重写。方法的名称,参数列表,返回类型必须和父类的一模一样,否则就不可以称之为重写。最常见的是抽象到极致的接口,当我们实现一个接口时,需要对它里面定义的所有的方法进行实现。

关于静态(static)

  • 静态只能用于类内部,可以修饰属性,方法,内部类。
  • 静态成员随着类的加载而加载,将先于对象创建。
  • 因为静态资源是优先于对象创建的,因此在静态方法中是不能访问当前对象的,也不可以使用super和this关键字。
  • 静态成员是类级别的成员,相当于全局资源,被所有对象所共享。

关于final

  • 被final修饰的类不可以被继承。
  • 被final修饰的方法不可以被重写。
  • 被final修饰的变量,只可以初始化一次,之后就不可以再修改。
  • 被final修饰的局部变量也是不可以被修改的。
  • 被final修饰的数组,它的元素可以修改内容,但是它的引用所指向的内存地址是不可以被修改的。

关于abstract

  • abstract可以修饰类和方法,也就是抽象类和抽象方法。
  • 当一个类中只要有一个方法是抽象方法时,这个类就必须使用abstract修饰。
  • 抽象类不可以实例化,即不可以使用new关键字创建。
  • 抽象类可以理解为不完全的接口。抽象类中可以有实现方法,也必须有未实现的抽象的方法,这个抽象方法需要由子类继承进行实现。
  • 抽象类也可以实现接口的方法。

关于接口(interface)

  • 类与类之间是继承关系,类与接口之间是实现关系,接口与接口之间是继承关系,并且只有接口可以进行多继承,即一个接口可以继承多个父接口。

关于内部类,匿名内部类,Java8的表示

内部类主要是用于描述在类内部的事物。这样定义可以减少外部类属性的对外暴露,并且在创建外部类时会自动创建内部类,因此只需要在需要时直接调用内部类即可。

匿名内部类可以理解为在类内部创建的,没有具体名称的类的实现。以创建线程(ps.只是一种表述方式,实际开发中不推荐)为例,可以在类中创建一个匿名的线程类,这样写是不是感觉非常的省事,不需要创建对象继承Thread类,然后再编写调用线程的方法。

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("before java 8");
    }
}).start();

Java8以后,对于匿名类的编写有了改变,通过引入lambda表达式,简化了匿名类的编写,如下:

new Thread(() -> {
    System.out.println("after java 8");
}).start();

关于代码块执行顺序

在java类中存在构造代码块,静态代码块,构造方法等,我们先看看下面代码的执行:

class A {
    public A() {
        System.out.println("Constructor A");
    }

    {
        System.out.println("This is A");
    }

    static {
        System.out.println("This is static block A");
    }

}

class B extends A {

    public B() {
        System.out.println("Constructor B");
    }

    {
        System.out.println("This is  B");
    }

    static {
        System.out.println("This is static block B");
    }

    public static void main(String[] args) {
        new B();
    }
}

执行结果是:

This is static block A
This is static block B
This is A
Constructor A
This is  B
Constructor B

由此可以看出,各代码块的执行顺序:静态代码块>构造代码块>构造方法

以下是关于代码块执行的总结:

  1. 构造方法执行前,一定会先去执行构造代码块。
  2. 构造代码块的前后位置不受构造方法的影响,即使构造代码块位于构造方法之后,但依旧先于构造方法执行。
  3. 多个构造代码块之间的执行,按照其对应的先后顺序执行,即排在前面的构造代码块先执行,排在后面的构造代码块后执行。
  4. 静态代码块由于被static关键字修饰,会在类加载时执行,但只会执行一次。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值