Java基础-面向对象

面向对象与面向过程

在面向对象程序(OOP:Object Oriented Programming)设计方法出现之前,软件界广泛流行的是面向过程(POP:Process Oriented Programming)的设计方式。

面向过程的设计中,处理的对象多以变量形式存在,处理的过程之间也不存在约束关系,分析出解决问题所需的步骤,然后用函数把这些步骤一步一步实现,使用时一次调用即可。简单来说,面向过程以实现功能的函数开发为主。

面向对象的设计中,将构成问题的事务抽象成不同的对象,建立的对象不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。简单来说,面向对象要首先抽象出类、属性及其方法,然后通过实例化类、执行方法来完成功能。

面向对象的三大特性

  1. 封装:隐藏对象的属性和实现细节,对外只提供访问的接口,提高复用性和安全性;

    面向对象与面向过程都具有封装性,但是面向过程是封装的是功能,而面向对象封装的是数据和功能。
  2. 继承:定义父类之后,子类可以从基础类进行继承,提高了代码的复用率,重要的一点:类只能单继承;

    继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类/基类),得到继承信息的被称为子类(派生类)。
  3. 多态:父类或者接口定义的引用变量可以指向子类或具体实现类的实例对象,提高了程序的扩展性。

    分为编译时多态(方法重载)和运行时多态(方法重写)。要实现多态需要做两件事:一是子类继承父类并重写父类中的方法,二是用父类型引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为。

封装

封装是面向对象编程的核心思想,封装指的是将对象的属性和行为封装起来,这个将对象的属性和行为封装起来的载体是类。在面向对象中,抽象出来的实物就是类,属于同一类的对象实例拥有相同的属性(数据)及方法(行为),Java中,所有对象/类的共同父类都是Object。

具体来说,生物的划分有:界、门、纲、目、科、属、种等,这里的每一个级别在程序中都可以抽象成类。按人类的类别来看,我们属于动物界-脊索动物门-脊椎动物亚门-哺乳纲-真兽亚纲-灵长目-类人猿亚目-人科-人亚科-人族-人属-智人种-晚期智人亚种。具体的分类太专业,将其简单化一点:动物-脊椎动物-哺乳动物-灵长动物-人类。

所以人类可以抽象成人(Human)这个类,而我们每一个人,就是这个类里面的一个实例。

如果说我们人的类命名成Human,代码可以表示如下:

public class Human{

}

封装需要用到Java中的修饰变量(访问控制符):private、protected、default和public。这些修饰变量代表着不同的访问权限,其中:

  1. private:表示私有,只有自己类能访问
  2. default:表示没有修饰符修饰,只有同一个包的类能访问
  3. protected:表示可以被同一个包的类以及其他包中的子类访问
  4. public:表示可以被该项目的所有包中的所有类访问
修饰符 本类 同包 子类 所有类
private × × ×
default × ×
protected ×
public

在类中,属性需要被隐藏起来,只提供接口来对其进行操作。属性设置为隐藏的好处有:

  1. 设置为私有后,只能通过getter方法来获取属性以及setter方法来设置属性,可以通过只提供getter方法而不提供setter方法来将属性设置为只读;
  2. 因为只有getter/setter方法能获取到/设置实例对象的属性,所以可以在getter/setter方法里处理好这个属性的数据,增加一些检查和转化,可以避免一些属性错误的问题;
  3. 对于一些特殊的业务,甚至可以设置属性为protected等方便子类获取到父类的属性及方法,而又可以禁止其他的类获取这些属性和方法。
public class Human{
    // 名字
    private String name;
    // 构造方法
    Human(){            
    }
    // 设置名字
    public void setName(String name){
        this.name = name;
    }
    // 获取名字
    public String getName(){
        return this.name;    
    }
}

继承

继承是一种层次模型,层次结构的上层具有通用性,但是下层结构则具有特殊性。在继承的过程中子类(相对下层)则可以从父类(相对上层)继承一些属性和方法。提供继承信息的类被称为父类(超类/基类),得到继承信息的被称为子类(派生类)。

类除了可以继承这些方法和变量以外,同时还能够对这些属性和方法进行修改或者添加。从已有类得到继承信息创建新类的过程能够有效提高工作效率。简单来说,子类的对象拥有父类的全部属性和行为,同时可以增添自己的所特有的属性和行为。

按上面人类的例子,人继承了灵长动物,灵长动物还有其他的子类,例如:猿、猴、猩猩等。灵长类都拥有对生的拇指,所以人类继承灵长类的时候不需要重申这一点,但是人和其他灵长类的进食方式不同,所以可以对eat方法进行重写(override)。

灵长类:

public class Primates{
    // 有对生的拇指
    private static boolean hasOpposingThumbs = true;
    public void eat(){
        // 用手抓着吃
    }
}

人类:

public class Human extends Primates{
    @Override
    public void eat(){
        // 用刀叉筷子等工具吃
    }
}

多态

同一个行为具有多个不同表现形式或形态的能力就是多态。

Java种的多态分为两种:运行时多态(方法重写)和编译时多态(方法重载)。

这两种多态有一个共同点,就是本质上都是对象在有某种行为时,存在两个或以上的方法来实现这个行为。比如说灵长类的进食行为eat(),身为人类可以用手抓着吃,也可以用刀叉筷子等工具吃,也可以用搅拌机将食物打碎了喝着“吃”下去。

运行时多态(重写)

运行时多态的前提条件是继承或实现,当子类继承父类时或一个类实现一个接口时,需要对其中的方法进行重写。这种多态通过动态绑定(dynamic binding)技术来实现,是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。也就是说,只有程序运行起来,你才知道调用的是哪个子类的方法。

运行时多态的条件是:

  1. 继承:在多态中必须存在有继承关系的子类和父类。
  2. 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
  3. 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。继承也可以替换为实现接口。

用灵长类与人类的例子来看,就是人类继承灵长类时,可以对进食方法eat()进行重写,重写之后,人类这个子类的对象进食时就有了其他的实现方式;如果人类不重写eat()方法,则人类的实例对象在进食时还是只能采取手抓的方式。

情景1:
人类重写了eat()方法,则在向上转型时,如:Primates p = new Human(),p.eat()执行的是Human类中的方法,即用刀叉筷子等工具吃。

public class Primates{
    // 有对生的拇指
    private static boolean hasOpposableThumbs = true;
    public void eat(){
        // 用手抓着吃
    }
}
----------
public class Human extends Primates{
    @Override
    public void eat(){
        // 用刀叉筷子等工具吃
    }    
}

情景2:
人类未重写了eat()方法,则在向上转型时,如:Primates p = new Human(),p.eat()执行的是Primates类中的方法,即还是用手抓着吃,由于未重写父类的方法,所以情景2未体现出对多态的运用。

public class Primates{
    // 有对生的拇指
    private static boolean hasOpposingThumbs = true;
    public void eat(){
        //用手抓着吃
    }
}
----------
public class Human extends Primates{  
}

编译时多态(重载)

在同一个类中,对于同一个方法/行为有多种实现方式,就被成为重载,也叫编译时多态。对比重写,重写是在程序运行的时候才知道实际调用哪个类里面的方法,而重载则是在代码编译的时候就知道调用的时哪个具体的方式。因为在同一个类中,同名方法只能通过不同的参数形式来区分,所以不需要到运行的时候才知道实际调用的方法。

重载需要注意:

  1. 位于同一个类中
  2. 函数的名字必须相同
  3. 形参列表不同

重载方法时,必须要使得两个同名方法的形参列表不同,而不能通过返回值来区分两个同名方法。其原因是,方法在调用时可以仅调用而不接收方法返回值。因此,如果方法的形参列表一样且方法名相同,则编译器无法判断到底调用的是哪个方法。

public class Human {
    public static void main(String[] args) {
        Human human = new Human();
        // 接收返回值
        int isFull = human.eat();
        // 不接收返回值
        human.eat();
    }
    public int eat(){
        return 1;
    }
}

用上面人类的例子来实现eat这一行为的多态:如果没餐具,就用手抓着吃;有餐具,也可以用餐具吃;如果还有搅拌机,也可以将食物打碎喝下去,代码可以这样表现:

public class Human extends Primates{
    public static void main(String[] args) {
       Human human = new Human();
        // 用手抓着吃,重写父类方法
        human.eat(); 
        // 用刀叉等工具吃,重载
        human.eat(tableware);
        // 用搅拌机搅拌后,用餐具盛着喝,重载
        human.eat(tableware, blender, water); 
    }
    // 参数为空,没有任何工具,只能用手抓着吃
    @Override
    public void eat(){
        // 用手抓着吃
    }
    // 参数为餐具,可以用餐具吃
    public void eat(Tableware tableware){
        // 用刀叉筷子等工具吃
    }
    // 参数为餐具和搅拌机,可以用餐具喝
    public void eat(Tableware tableware, Blender blender, Water water){
        // 用搅拌机搅拌后,用餐具盛着喝
    }
}

总结

面向对象是一种“万物皆对象”的编程思想,在现实生活中的任何物体都抽象归纳成不同的类,而每一个个体都是一类事物的实例,也称为对象;

  1. 面向过程以实现功能的函数开发为主,而面向对象要首先抽象出类、属性及其方法,然后通过实例化类、执行方法来完成功能;
  2. 面向对象及面向过程都具有封装性,但是面向过程是封装的是功能,而面向对象封装的是数据和功能;
  3. 面向对象具有继承性和多态性,而面向过程没有继承性和多态性,所以面向对象优势很明显,主要在于减少代码冗余,代码有条理;
  4. 面向对象有三大特性:封装、继承、多态。对具体实物地封装可以使得继承更好实现,而继承又是多态的一个基础;
  5. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。在一个子类被创建的时候,首先会在内存中创建一个父类对象,然后在父类对象外部放上子类独有的属性,两者合起来形成一个子类的对象;
  6. 子类可以拥有自己属性和方法,也可以用自己的方式实现父类的方法。

你可能感兴趣的