面向对象有三大特点:封装性、继承性和多态性。
一、封装
1、封装的含义
封装 (Encapsulation)是将描述某类事物的数据与处理这些数据的函数封装在一起,形成一个有机整体,称为类。
类所具有的封装性可使程序模块具有良好的独立性与可维护性,这对大型程序的开发是特别重要的。
类中的私有数据在类的外部不能直接使用,外部只能通过类的公共接口方法(函数)来处理类中的数据,从而使数据的安全性得到保证。
封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而仅需要通过外部接口,特定的访问权限来使用类的成员。
一旦设计好类,就可以实例化该类的对象。我们在形成一个对象的同时也界定了对象与外界的内外隔离。至于对象的属性、行为等实现的细节则被封装在对象的内部。外部的使用者和其他的对象只能经由原先规划好的接口和对象交互。
我们可用一个鸡蛋的三重构造来比拟一个对象,如下图所示。
属性(Attributes) 好比蛋黄,它隐藏于中心,不能直接接触,它代表的对象的状态(State)。
行为(Behaviors) 好比蛋白,它可以经由接口与外界交互而改变内部的属性值,并把这种改变通过接口呈现出来。
接口(Interface) 好比蛋壳,它可以与外界直接接触。外部也只能通过公开的接口方法来改变对象内部的属性(数据)值,从而使类中数据的安全性得到保证。
2、封装的实现
在Java中有四种访问权限:公有(public)、私有(private)、保护(protected)、默认(default)。但访问权限修饰符只有三种,因为默认访问权限没有访问权限修饰符。
默认访问权限是包访问权限,即在没有任何修饰符的情况下定义的类,属性和方法在一个包内都是可访问的。
访问权限控制符是对类外而言的,而在同一类中,所有的类成员属性及方法都是相互可见的,也就是说,它们之间是可以相互访问的。
3、总结
(1)在Java中,最基本的封装单元是类。
(2)类是基于面向对象思想编程语言的基础,程序员可以把具有相同业务性质的代码封装在一个类里,通过接口方法向外部代码提供服务,同时向外部代码屏蔽类里服务的具体实现方式。
(3)数据封装的最重要的目的是在于要实现“信息隐藏(Information Hidding)”。
(4)在类中的“数据成员(属性)”或者“方法成员”,可以使用关键字“public”、”private”、”protected”来设置各成员的访问权限。
(5)封装性是面向对象程序设计的原则之一。 它规定对象应对外部环境隐藏它们的内部工作方式。良好的封装可以提高代码的模块化程度,它防止了对象之间不良的相互影响。使程序达到强内聚(许多功能尽量在类的内部独立完成,不让外面干预),弱耦合(提供给外部尽量少的方法调用)的最终目标。
二、继承
1、继承的含义
对象(Object)是类(Class)的一个实例(Instance)。
如果将对象比作房子,那么类就是房子的设计图纸。所以面向对象设计的重点是类的设计,而不是对象的设计。
继承性是面向对象的第二大特征。
继承(Inheritance)是面向对象程序设计中软件复用的关键技术,通过继承,可以进一步扩充新的特性,适应新的需求。这种可复用、可扩充技术在很大程度上降低了大型软件的开发难度,从而提高软件的开发效率。
当我们说某一个新类A继承某一既有类B时,表示这个新类A具有既有类B的所有成员,同时对既有类的成员做出修改,或是增加了新的成员。
保持已有类的特性而构造新类的过程称为继承。在已有类的基础上新增自己的特性而产生新类的过程称为派生。
把既有类称为基类(base class)、超类(super class)或者父类(parent class),而派生出的新类,称为派生类(derived class)或子类(subclass)。
继承可以使得子类自动具有父类的各种属性和方法,而不需要再次编写相同的代码,从而达到类的复用目的。
继承的目的在于实现代码重用,对已有的成熟的功能,子类从父类执行“拿来主义”。而派生的目的则在于,当新的问题出现时,原有代码无法解决(或不能完全解决)时,需要对原有代码进行全部(或部分)改造。
2、继承的实现
在Java中,通过继承可以简化类的定义,扩展类的功能。在Java中支持类的单继承和多层继承,但是不支持多继承,即一个类只能继承一个类而不能继承多个类。
【语法】
class 子类名 extends 父类
extends 是Java中的关键词。Java继承只能直接继承父类中的公有属性和公有方法,而隐含的(不可见的)继承了私有属性。
3、继承的限制
(1)Java之中不允许多重继承,但是却可以使用多层继承。 一般情况下,在我们所编写的代码时,多层继承的层数之中不宜超过三层。
(2)从父类继承的私有成员,不能被子类直接使用。
子类在继承父类的时候会将父类之中的全部成员(包括属性及方法)继承下来,但是对于所有的非私有(private)成员属于显式继承,而对于所有的私有成员采用隐式继承(即对子类不可见)。 子类无法直接操作这些私有属性,必须通过设置Setter和Getter方法间接操作。
(3)子类在进行对象实例化时,从父类继承而来的数据成员需要先调用父类的构造方法来初始化,然后再用子类的构造方法来初始化本地的数据成员。
子类继承了父类的所有数据成员,同时子类也可以添加自己的数据成员。但是,需要注意的是,在调用构造方法实施数据成员初始化时,一定要“各司其职”,即来自父类的数据成员,需要调用父类的构造方法来初始化,而来自子类的数据成员初始化,要在本地构造方法中完成。
在调用次序上,子类的构造方法要遵循“长辈优先”的原则,先调用父类的构造方法(生成父类对象),然后再调用子类的构造方法(生成子类对象)。
(4)被final修饰的类不能再被继承。 final在Java之中称为终结器。通过在类的前面添加final关键字便可以阻止该类被继承。
4、构造方法调用顺序
首先介绍下默认构造器:
默认构造器,又叫无参构造器,是没有形式参数的构造器,它的作用是创建一个“默认对象”。
如果你写的类中没有构造器,则编译器会自动帮你创建一个默认构造器。 如下:
【示例1】:调用编译器创建默认构造器
package person;
class Person {
}
class Student extends Person {
Student() {
System.out.println("Student() constructor!");
}
}
public class TestPerson {
public static void main(String[] args) {
Student s1 = new Student();
}
}
【结果】
子类Student在进行对象实例化时,会先调用父类Person的构造方法,然而,在父类Person中没有提供构造器,则在Student对象s1调用构造方法Student()时,编译器会自动地创建一个默认构造器。
但是,如果在父类中自己定义了构造器,那么就调用自己定义的,如下:
【示例2】:调用自定义默认构造器
package person;
class Person {
Person() {
// 自己定义的默认构造器
System.out.println("Person() constructor!");
}
}
class Student extends Person {
Student() {
System.out.println("Student() constructor!");
}
}