什么是面向对象
面向对象(Object Oriented)是一种抽象的思考方法,并不拘泥于计算机技术。
面向过程(C语言就是面对过程的语言),关注的是执行的过程;而面向对象,则是关注具备执行该过程的对象。从面向过程到面向对象,是在思想上从执行者到指挥者的转变。
以上的概念可能不好理解,让我们用一个例子来说明:
小明开了一家杂货铺,这时有人来买东西
面向过程:购买者说想要一包薯片,小明说价格,付钱,小明给他薯片。
面向对象:小明招聘一个收银员,教他如果购买者想要买一包薯片,就说出薯片的价格,付了钱之后给他薯片
此时看来,面向对象似乎比面向过程更复杂,但是当我们更改一下例子,小明开了一家连锁杂货铺,分布在全国各地,开了数百家,这时小明还能面向过程吗?
在代码的世界也是如此,在少量的代码时,面向过程更简单,但是当代码量变大,重复过程变多的时候,面向对象则是更好的选择。
面对对象的思想从概念上可以分为三类:
- OOA:面对对象分析,Object Oriented Analysis
- OOD:面对对象设计,Design
- OOP:面对对象编程,Programming
面对对象也有三个主要特征:封装性,继承性,和多态性(会在以后小节中详细讲解)
类与对象
类(class)表示一个共性的产物,是一个综合特征的集合;而对象(object)则是一个单独的个体,对象的特征是个体的特征。
让我们用一个例子来说明:人类即是一个类,用来描述所有的人,你可以说人类是哺乳动物,是灵长类动物,大多有一双眼睛,两条腿,这些就是人类的特征。而人类中的一个对象就是一个单独的人,小明就是人类的一个对象,小明符合人类的特征,但可能还有自己特殊的特征,例如小明长得很帅,小明身高八尺有余,这些都是小明自己的特征,而不是人类的特征。
一个类必须通过对象才可以使用,但是对象的所有操作都需要在类中定义。类由属性和方法组成,属性就相当于类的特征,方法则相当于对象可以进行的行为。
让我们同样用人类来举例:人类的属性就是人是哺乳动物,是灵长类动物,方法则包括吃饭,说话,睡觉。当你想要使用这些方法时,你不能直接用类,而是需要一个对象,比如小明吃饭,小明说话,而且你创建的对象小明一定会满足人类的属性,即小明一定是哺乳动物,是灵长类动物。
(特别提示:其中用类创建对象的过程被称为实例化instantiation,在之前的输入中曾经使用过)
类与对象的创建
class类一般通过如下方式定义
class 类名称{
成员属性;
成员方法;
}
让我们看看我们最早写的第一个程序Hello World
public class HelloWorld {
public static void main(String[] args){
System.out.println("Hello World!");
}
}
其中不难发现我们定义了一个类,类的名字叫做HelloWorld,且类的前面有一个public来修饰(目前阶段仅了解,你只需要知道,一个java文件中可以存在若干个类,但只能存在一个public类,且public类的类名需要跟java文件名一样),类中没有成员属性,存在一个成员方法,方法名为main(为程序的入口方法)。
现在让我们目光锁定到方法上,类中的方法通过如下形式定义
返回值类型 方法名称(形式参数列表){
方法体;
return 返回值;
}
其中,返回值类型为需要返回的值的类型,方法名称就是调用该方法需要使用的名称,形式参数列表就是该方法需要传入的参数的列表。
让我们用一个简单的类来举例:
class Preson{// 直接放在public类下面
String name;
int age;
int height;// 这里是成员属性
// 以下是三种方法
String getName(){
return name;// 直接返回成员属性name
}
int getBirthYear(int currentYear){
return currentYear - age;// 传入的参数可以直接使用
}
void say(){// void的意思即为不返回任何参数
System.out.println("我叫" + "name");
}// 但是在void中可以使用return;意为将此方法结束
}
(特别提示:当然你也可以直接在public类中定义方法,这些方法不需要实例化即可直接使用)
注意,想要使用这个类,还需要创建这个类的对象,即实例化。让我们回到public类当中,实现代码如下
public class ObjectOriented {
public static void main(String[] args) {
Person person = new Person();// 实例化该对象,命名为person
// 之后只要与Person类相关,都需要使用person.xxx
// 可以用此方法创建person1,person2....personN,他们互不干扰
// 通过Person person2 = person;可以
person.name = "小明";
person.age = 8;
person.height = 158;// 定义成员变量
String name = person.getName();// 调用方法
System.out.println(name);
System.out.println(person.getBirthYear(2020));// 传入参数2020
person.say();
}
}
运行结果:
小明
2012
我叫小明
Process finished with exit code 0
类与对象的存储
在Java中,所有数据的存储都是自动的,即你不能手动更改各种数据被存储的位置。
Java中存储数据的位置被分为三块,栈内存,堆内存,和方法区。
其中方法区就是用来存储所有类的信息,以及其中的成员方法,静态的变量(static)和常量(final)也会被储存其中。
栈存储特点是先进后出,内存的区域一般比较小,但是栈的优点是存取速度很快,所以一般基本数据类型的数据和引用数据类型的引用会被存在栈中。
堆存储则是先进先出,内存的区域比较大,一般用于存放类的对象。
(这一部分内容虽然很难,看起来对编程似乎也没有帮助,但是理解之后能够对学习之后的内容很有帮助)
类与对象的更多操作
类的构造方法
构造方法,通常用于对象初始化,在对象被创建时,自动调用,所有类需要包含一个构造方法,构造方法的定义如下:
(特别提示:如果没有自行编写一个构造方法,编译器会自动生成一个空的构造方法)
class Person{
Person(){}
}
构造方法需要和类同名,可以传入参数,也可以在其中加入任何代码(就像一个自动执行的方法)
// 如果你这样定义构造方法
Person(String name){
System.out.println(name);
}
// 则你在调用时需要
Person person = new Person("小明");
方法的重载
一个类中定义的方法是允许重载的,重载即不同的方法使用相同的方法名称。
但是重载也必须满足以下条件:
- 方法名称相同
- 参数列表的长度 或 参数列表类型 或 参数顺序不同
(特别提示:与返回值,和方法里面的语句无关)
// 在同一类Person中
String getName(){
return name;
}
String getName(String name){
return name;
}
String getName(String firstName, String lastName){
return firstName + lastName;
}
// 可以通过如下方法依次调用
Person person = new Person();
String name = person.getName();
String name = person.getName("小明");
String name = person.getName("小", "明");
在调用重载的方法时,会根据参数传入的情况自动选择使用的方法。
构造方法也能进行重载
匿名的对象
所谓匿名的对象,即为创建对象时不命名该对象,让我们通过代码来说明
// 使用之前的getName方法
String getName(){
return name;
}
// 可以通过如下方式匿名调用getName方法
new Person();// 匿名对象只会自动调用构造方法
System.out.println(new Person().getName());// 直接输出
String name = new Person.getName();// 新建一个变量用于存储
匿名调用的优点是节约内存空间,代码看起来也更整洁,缺点就是构建的对象只能使用一次,无法再进行调用。
练习使用
此部分内容比较多,对于初学者来说并不简单,通过小练习能帮助理解这一部分内容
请定义并测试一个代表员工的Employee类。它的属性包括“员工姓名”、 “员工号码”、“员工基本薪水”、“员工薪水增长率”;他的方法包括“构 造方法”、“获取员工姓名”、“获取员工号码”、“获取员工基本薪水”、 “计算薪水增长额”及“计算增长后的工资总额”。
答案代码如下(查看前至少先进行思考):
public class OOPBasic {
public static void main(String[] args) {
Employee employee = new Employee();
employee.name = "大明";
employee.idNumber = 6666;
employee.salary = 10000;
employee.salaryIncreaseRate = 0.01f;
System.out.println(employee.getName());
System.out.println(employee.getIdNumber());
System.out.println(employee.getSalary());
System.out.println(employee.calculateIncreaseAmount());
System.out.println(employee.calculateSalary());
}
}
class Employee{
String name;
int idNumber;
float salary;
float salaryIncreaseRate;
Employee(){}
String getName(){
return name;
}
int getIdNumber(){
return idNumber;
}
float getSalary(){
return salary;
}
float calculateIncreaseAmount(){
return salary * salaryIncreaseRate;
}
float calculateSalary(){
return salary * (1 + salaryIncreaseRate);
}
}
运行结果:
大明
6666
10000.0
100.0
10100.0
Process finished with exit code 0
关于类的使用的常见问题
在进行完练习之后,你可能会对类产生一些疑问,让我们在这里集中解答一下:
Q1:为什么我要使用类?看起来明显使整个程序变长了,变复杂了
在使用了类之后,整段代码的体量确实肉眼可见的变长了近一倍,但是注意我之前举的例子,小明在只开一家店的时候确实使用面向过程的思维更方便,但是当小明开连锁店的时候,事情就不一样了。
在这里也是一样,题中让你创建了一个员工大明,看起来使用类弊大于利,但是如果是一个数千人的企业,设想一下每个员工的name,id,salary都需要单独的声明,使用类则只需要创建对象,每个对象都能用.name,.id,.salary,能够使代码缩短很多。
Q2:类起到了什么作用?我输出的数据不都是我输入的吗?
通过上面的例子,你可能发现所有的数据输出都需要通过你自己的输入来产生。其实道理和Q1一样,在小体量代码时,用类不能帮助你降低代码量,但是当体量变大时,你计算员工薪资的涨幅都要用重复的代码堆砌,但是运用类,你只需要使用相同的方法。
注意一点,在真正的项目中,class中的内容会很多很复杂(数万行起),但是class的内容并不需要你自己编写,大部分时候你只需要调用别人class里的方法就好,本题中class的方法比较简单,只是想起到练习帮助理解的作用,但是本题中你也可以看出,你所编写的内容中不含任何的逻辑和运算,只是简单的创建对象-输入数据-输出数据(如果Person中的内容算作别人编写的)。
Q3:既然类是别人编写的,我为什么要学会定义类和类的结构?
学习类是为了更好的理解类,如果你完全不理解类的结构,就不能更好的使用类。
编写类就是面向对象语言的一种进阶,换言之,如果你能编写一个表意清晰,功能强大,能供多人使用的类,也说明了你的能力十分强大。
Q4:既然类在大体量时才作用明显,我能不能不学习类?
目前世界上绝大多数的大型项目都是由多人组成的团队编写的,如果你没有进一步学习的打算,不学习类也可以完成绝大多数简单的编程(包括对编程思维的培养),但是如果你想要真正深入的学习Java或者任何一门面向对象的语言,都需要透彻理解和接受类的存在。