面向过程与面向对象
说到面向对象,我们先来看看和面向对象相对的面向过程的概念。
什么是面向过程?面向过程,顾名思义“过程”是重点,可以理解为当我们要解决某个问题时,会把这个问题里的步骤进行拆分细化,然后按照指定的顺序来执行这些步骤。举例说明我想吃米饭,那么在面向过程中,我需要进行插秧,施肥,收获,放入电饭锅,最后才可以吃饭。假如在水稻种植过程中出现了病虫害,说不定都撑不到收获的季节,那么最后的米饭是不是就吃不上了。这样为了吃一口米饭是不是就显得很繁琐,那我宁愿饿死也不吃了。再假如第二天我又想吃面条了,那我就又得先去播种小麦,施肥,收割,制成面粉,制成面条,煮面,最后才吃上面条。从吃米饭和吃面条来看,支撑我生命活动的非常重要的进食过程是不是非常的麻烦,没有办法,这就是面向过程的着重“过程”之所在。
void 吃米饭(){
插秧();
施肥();
收获();
放入电饭锅();
吃饭();
}
void 吃面条(){
播种();
施肥();
收割();
制成面粉();
制成面条();
开水煮面();
吃饭();
}
再来看看面向对象,何为面向对象?面向对象即所有的事物都可以抽象成对象来表示。
- 什么是对象呢?
- 一个人,一滴水,一个星球等等,都可以看作为对象,也就是最常说的java中的万物皆对象。
- 如何表示对象呢?
- 将需要描述的对象的特征提取出来,定义为这个对象的属性,就可以描述一个对象。以一个人为例,可以使用姓名,性别,年龄,身份证号等属性来描述这个对象。
还是以支撑生命活动的进食操作为例,在面向对象中,可以抽象出以下几个对象,①将我抽象为对象,我有吃饭的能力。②将饭馆抽象为对象,他提供点餐的能力。③将粮油公司抽象为对象,他提供收购和销售的能力。④将农民抽象为对象,他提供种植和销售的能力。那么对于我而言,我想吃米饭还是想吃面条,就不需要我再去从水稻小麦的种植开始,亲力亲为。我只需要来到饭馆,使用饭馆提供的点餐功能,即可吃到我想吃的东西。虽然我是向饭馆点餐,但是饭馆也不需要关心水稻小麦是怎么种植的,他只需要从粮油公司手中购买所需的原材料即可。粮油公司也不需要知道水稻小麦的种植过程怎样,他只需要调取农民的销售功能即可获得收获来的水稻和小麦。
我{
吃饭(){
饭馆.点餐(米饭);
}
}
饭馆{
点餐(食物){
粮油公司.销售();
制作食物(食物);
}
制作食物(食物);
}
粮油公司{
收购(){
农民.销售();
};
销售();
}
农民{
种植(){
播种();
施肥();
收割();
}
销售();
}
面向对象实现后,乍一看来似乎比面向过程更加复杂,加入了我以外的其他角色。但是仔细看下,其实面向对象比面向过程有优点的:
- 首先将粮食从种植到最终吃到嘴里的过程细化,由不同的对象来完成各自的事情,每个对象只需要干属于他的事情即可,这样可以使该对象更加专注他自己领域的事情,不需要关心其他对象干的事情,实现了解耦。每个对象自己干的事情很单一了,这也符合功能原子性的设计思想。
- 解耦后的维护及扩展更加便捷了。当其中某个环节出现问题时,只需要找到相应的对象,解决问题即可。
面向对象的三大特征
封装
将数据或方法隐藏到对象的内部,对外只提供公共的接口供调取所用。具体来说就是可以使用private关键字将对象的属性私有化,然后对外提供public修饰的getter,setter方法。
下面是访问修饰符的作用范围:
访问修饰符 | 本类 | 同一个包 | 子类 | 其他包 |
---|---|---|---|---|
private | √ | × | × | × |
default | √ | √ | × | × |
protect | √ | √ | √ | × |
public | √ | √ | √ | √ |
继承
继承可以理解为父辈的资源传承给后代。例如父类是鸟类,他有羽毛,有飞翔的能力,那么继承了鸟类的后代如老鹰,它也就有了羽毛和飞翔的能力。同时子类自己还可以有其自己的属性,例如老鹰有超凡的视力。
继承有以下几点需要注意的地方:
- 继承只能是单继承,即子类只能继承一个父类。思考一下就很容易想清楚,一个孩子只能有一个亲身父亲,如果可以出现了多个亲生父亲,那么世间岂不乱套了
- 父类不能强转为子类,而子类默认可以隐式的转换为父类。这个可以这样理解,一个孩子学会了打游戏技能时,当他唯一的亲生父亲想要强行装嫩当儿子时,父亲其实是不会孩子的打游戏技能的,因此是没有办法装嫩的。
多态
多态是继承的一种体现,父类可以有很多的子类,因此父类的引用指向不同的子类实现。
以下图的刺激战场中的载具对象来说:
不管是跑车还是蹦蹦还会摩托,它们都有一个共同的父类–载具,载具有轮子的属性以及载人运输的能力。因此上图中的所有的车都有轮子,都可以载人跑毒。子类可以提供多种父类功能及属性的形态,这就称之为多态。
关于构造方法
- 构造方法没有返回值,虽然称之为方法,但是它却不是真正的方法。它是一种创建对象的特殊的声明方式。
- 使用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
由此可以看出,各代码块的执行顺序:静态代码块>构造代码块>构造方法
以下是关于代码块执行的总结:
- 构造方法执行前,一定会先去执行构造代码块。
- 构造代码块的前后位置不受构造方法的影响,即使构造代码块位于构造方法之后,但依旧先于构造方法执行。
- 多个构造代码块之间的执行,按照其对应的先后顺序执行,即排在前面的构造代码块先执行,排在后面的构造代码块后执行。
- 静态代码块由于被static关键字修饰,会在类加载时执行,但只会执行一次。