# Java面向对象
[TOC]
## 导学
通过前面的学习,我们对Java程序的运行流程有了一定的认识,掌握了分支结构,循环结构等常用逻辑,了解了Java的基本数据类型和引用数据类型,还认识了Java的方法构建。
我们也能够通过这些知识来解决一些简单的问题,但是当遇到一些复杂问题的时候,这些技能是远远不够的。这就像盖房子,想盖一间小房子,会砌砖抹泥就已经足够了。但是想要盖一幢摩天大楼,就一定要懂得建筑工程方面的知识了。
在Java开发中,面向对象程序思想就是这样一种技能。相较于早年面向过程的程序开发,面向对象开发在程序的稳定性,可扩展性和可重用性方面都有着无可比拟的优势。
在本单元的学习中,我们将要学习面向对象编程的三大特征,封装,继承和多态,以及编写具有面向对象思想的Java程序。
## 初识面向对象
### 类和对象
* 什么是对象
对象不仅在编程领域,而且在现实生活中都是一个非常重要的概念。我们需要理解一个概念,“万物皆对象”。显示存在的客观事物都是对象。比如长城,电脑,一件衣服,一只狗,一只猫都是对象。只要是现实生活中存在的都是对象。
* 什么是面向对象
从字面上理解,面向对象就是与对象面对面,关注对象。从计算机的角度,就是关注现实存在的事物的各方面信息,从对象的角度出发根据事物的特征进行程序的设计。
虽然这些概念比较玄幻,但是只要根据现实生活的中事物的分析方式,就可以轻松的搞定对象了。
* 什么是类
这里我们使用一个例子来描述:
比我去逛宠物商店,想要买一只猫。我会告诉店员我想要一只短毛的,小一点的可爱的猫。于是店员给我推荐了两只猫
![](https://img.kancloud.cn/b1/88/b1885e55de567f123565985f978a91e0_564x316.png)
![](https://img.kancloud.cn/46/b2/46b2c72ad02dbf11c18110b31b506e22_972x379.png)
所以这样的一个虚拟的描述,就是我们的类,而花花与凡凡就是根据类的特征而选出来的具体的实物(对象)。
所以,针对于类,我们可以给出这样的描述:
>[info]类就是模子,用于确定对象将会拥有的特征(属性)和行为(方法)
**对象是类的实例化表现。**
由于在计算机世界中所有的信息都是数据,所以我们可以认为:
1. 类是对象的类型
2. 对象是特定类型的数据
~~~
//类作为数据类型 对象因类成就数据
Cat huahua
~~~
* 类和对象的关系
所以,针对于类与对象的关系,我们结合上述课程可以总结出如下观点:
* 对象是类的实例化表现
* 类是对象的类型
* 对象是特定类型的数据
之前我们也提到对象拥有属性和方法两个特征,那么什么是属性和方法呢。属性代表着对象具有的各种静态特征,即对象有什么;方法代表着对象具有的各种动态行为,即对象能做什么。
在上节内容中,我们举了两只猫的例子,我们可以发现这两只猫都有着名字,性别,年龄和毛色等这些特征。只不过每只猫的具体指示是不同的,那么这些名字,性别,年龄和毛色等共有特征,就是猫的属性,而类似猫的跑,跳,睡,吃等动作,就是猫能干什么,意味着猫这个对象的方法。
最后总结一下:
![](https://img.kancloud.cn/99/bd/99bd0dd7b94a8d9bdb438074c06c4cc8_936x440.png)
### 创建类
本节内容,我们试着通过Java程序来描述一下,我们举过的两只猫的例子。
#### 包的管理
首先,针对于本节课的开始,我们需要理解一下包的概念。比如我们在操作系统中会通过文件夹来实现文件的管理,在Java中,我们也可以通过包实现类的管理。
为了便于类文件的管理,Java 中引入了包的概念 package,类的唯一性是要带包名的。
包名也存在其对应的命名规范
1. 英文字母小写
2. 域名的倒序
比如:
~~~
第一层是企业的域名的反写
例如:com.dodoke
第二层是企业项目的名称
例如:com.dodoke.j96、com.dodoke.crm
第三层是企业项目模块的名称
例如:com.dodoke.j96.oop、com.dodoke.crm.base
~~~
我们也可以在类中自主的定义包
~~~
// 在类文件的第一行
package com.duduke.j96.oop;
~~~
>[danger]一个Java源文件中,只能有一个package语句,而且必须放置在Java源文件的第一行。
>[info]在不同的包中,是可以定义相同类名的类的。因为一个类的唯一性是包含包名的,比如`com.ntduduke.j96.oop.Demo1`叫做类的全名。
在实际的企业开发中,是不允许出现没有包的类。
#### 包的使用
使用 import 关键字将本类要使用的其他包中的类进行引入。
>[info] 但是,import 不是必须的,我们可以使用类全名的方式进行类的使用。一般不建议,太麻烦。
使用`ALT+/`可以单个引入,也可以使用`CTRL+SHIFT+O`全部引入。
如果要引入某个包下面的所有类,可以使用通配符`*`,例如,引入`com.ntduduke.j96.oop.*`,但是要注意通配符只能出现在最后。
对于 java.lang 包中的类,系统会自动的默认引入,不需要使用 import。
#### 创建对象的类型
~~~
public class Cat{
//成员属性(属性指有什么)、昵称、年龄、体重;
String name;//昵称
int month;//年龄
//方法(方法指能做什么):跑步、吃东西
//成员方法
//跑步的方法
public void run(){
System.out.println("我会跑步");
}
//吃东西的方法
public void eat(){
System.out.println("我会吃鱼");
}
}
~~~
### 实例化对象
对象是由类创建的,我们通过一堆对象总结出了类,那么类就有了对象的共同属性与方法。由此,我们同样可以得到,由类这个模板来创建对象,这种方式我们称之为实例化。
~~~
public class CatTest{
public static void main(String[] args) {
Cat one= new Cat();
one.eat();
one.run();
System.out.println(one.month);//有默认值
}
}
~~~
我们发现对于对象的`month`属性,它是存在默认值的。
那么再来对比一下,我们之前在方法中给大家介绍的局部变量。
局部变量通常是指我们在方法,流程控制结构等内容中定义的变量。
>[warning] 变量的生命周期在 {} 定义的范围内
~~~
public void demo(int a) {
int b = 5;
if(a > 5) {
int c = a - b;//可以调用a和b
System.out.println(c);
} else {
//System.out.println(c);不能调用,c的生命周期只在上一个大括号内
int n;
//System.out.println(n);显示错误,未初始化
}
}
~~~
而针对于类中的成员属性,如果是引用数据类型的,没有定义默认值,那么成员变量的值为`null`,如果是基本数据类型,没有定义默认值,那么成员变量的值是有意义的,比如说int类型的值就是0,boolean类型的值就是false。
最后,针对于成员变量,我们也可以**通过赋值等于号,在程序中为属性赋值**。
### 单一职责原则
在上述的程序中,我们创建了两个类,在`CatTest`中利用`main`方法进行程序的测试。这和我们之前学习过的内容不同,之前都是在一个类中进行代码的构建。而这个时候,我们使用的是两个类进行程序的构建。
本章节,我们就来简单的介绍一下单一职责原则(单一功能原则)。
该原则是面向对象程序设计中的一项重要原则,该原则要求我们一个类有且只有一个引起功能变化的原因。简单的说就是一个类最好只有一个功能,只干一件事。如果在一个类中承载的功能越多,那么它的**交融和耦合性**就越高,从而**被复用**的可能性就越低。
同时,因为一个类中存在多个职责,当其中一个职责发生改变就有可能会引起同类中其他职责的变化,进而影响整个程序的运行。在程序设计中,尽量把不同的职责,放在不同的类中(也就是说把不同的可能引发变化的原因封装到不同的类里面,这样当一方发生变化时,对其他参与者的影响会少很多,并提升复用性)
### new关键字
在之前的学习中,我们通过new关键字完成了对象的实例化过程,实际上也就是对象的创建过程。
~~~
对象实例化语法:
类名 对象名 = new 类名();
~~~
实例化对象的过程可以分为两部分:
- 声明对象 Cat one
- 实例化对象 new Cat();
**声明对象**:是在内存的"栈"空间里开辟了一块空间,取名叫one,此时里面为空(null),并且对它的属性和方法的调用是不允许的。所以因为这样我们并不能像真正的对象那样使用它
**实例化对象**:是在内存的堆空间里开辟了一块空间,完成了对象相关信息的初始化操作
声明对象和实例化对象是在内存的两个不同的空间去完成的,接着通过赋值符号"="把两个空间关联起来关联,将堆空间中的内存地址存放到了栈空间中,在栈当中存储的实际上是堆当中地址的引用。
![](https://img.kancloud.cn/4c/48/4c48b85f65ce453cf51cd0b63e416724_463x288.png)
`new`关键字的作用,实际上就是去开辟新的内存空间。
~~~
Cat one= new Cat();
Cat two= new Cat();
~~~
我们针对于`two`对象,采取同样的赋值。如果修改`two`对象的信息则不会对`one`对象造成任何的影响。
![](https://img.kancloud.cn/7c/df/7cdff9ccfa800e545b20e05d40d6520f_392x273.png)
另一种实例化方式:
~~~
Cat one= new Cat();
Cat two= one;//将one所代表的内存地址复制给了two
~~~
此时,如果对one`对象`进行修改,则会影响到`two`对象
![](https://img.kancloud.cn/fc/2c/fc2c65cad32c06a68b687c9e7f72ea3a_417x300.png)
## 构造方法介绍
构造方法也称之为构造函数,构造器,是面向对象编程中的一个重要概念。
我们经常会使用构造方法来完成对象初始化的相关设置。构造方法在调用的时候必须配合new关键字,是不能被单独调用的。
构造方法语法:
![](https://img.kancloud.cn/bf/73/bf736df496ab1a540a4e7a2dfd5fb961_690x280.png)
注意:**构造方法与类同名且没有返回值。构造方法只能在对象实例化的时候被调用**
构造器本身是一个比较特殊的方法,方法名就是类名,没有返回值(和void是有区别的),构造器是类创建对象的唯一途径。
>[danger]构造器的最大用处就是创建对象
### 无参构造方法
之前我们也学习过方法,方法必须先定义好才能使用。但是,在我们的类中并没有创建构造方法,但是我们依然可以使用构造方法去创建对象。
这是因为,**当没有指定构造方法时,系统会自动添加无参构造方法**。**也就是说在一个类中至少会存在一个构造方法**。便于我们的程序能够正常的执行,对象能够正常的进行实例化操作。
~~~
public Cat() {
System.out.println("我是无参构造方法");
}
Cat one = new Cat();//其实也就是调用了无参构造方法
~~~
**一个类中可以有多个构造方法,当有指定构造方法、无论是有参、无参的构造方法,都不会自动添加无参的构造方法。**
### 有参构造方法
通常我们会通过构造方法来完成对象的实例化操作。**通过构造器为成员变量定义初始化值,这也是构造器的最最最重要的用途之一**
~~~
public Cat(String name, int month, double weight, String species) {
name = name;
month = month;
weight = weight;
species = species;
}
~~~
实际上,上述构造方法中的四条语句不会起作用。其实此处的代码逻辑发生了错误,遵循了一种**就近原则** ——赋值过程中先优先的去找同一个作用范围内的成员进行赋值操作。只有找不到的情况下才会扩大作用范围,去类里面找。
针对于这样的问题,我们可以有两种解决方案。
1. 修改参数名称
2. 使用this.属性名=参数名
### 构造器小结
结合之前学习的内容,我们再来分析一下一个对象的创建过程:
1. 在栈内存中,会存储对象名, 在没有执行构造器创建对象并赋值是,此时对象名对应的值应为null
2. 通过new关键字调用类的构造器在堆内存中分配了一块对象区域;
3. 通过赋值运算符= ,将堆内存中的对象地址赋给栈内存中的变量名;
4. 例如再次给对象的属性赋值: 通过栈内存中的地址定位到对象在堆内存中的地址,找到相应的成员变量,进行一个赋值操作
>[info]引用: 引用还可以称之为【地址】,【指针】 。特指的是引用数据类型。因为只用类类型才会在堆内存中分配对象空间,并将地址(指针)在栈内存中用于对象变量名的引用。
### this关键字
~~~
public Cat(String name, int month, double weight, String species) {
this.name = name;
this.month = month;
this.weight = weight;
this.species = species;
}
~~~
Java中使用this关键字,指向调用该方法的对象。根据this所在的位置,大致分为两种情况
* 出现在构造器中:引用该构造器正在初始化的对象
* 出现在普通方法中: 正在调用该方法的对象
this用在类定义中,获取当前对象的属性,或者调用当前对象的方法
>[warning] 在类定义中,可以省略this关键字去调用属性或者方法,但是在类被编译的时候,编译器还是会加上this关键字。所以我们强烈建议在类定义的时候如果要调用该类中的普通成员变量或者方法,还是要把this给加上去
>[danger] 用static修饰的方法是不能使用this关键字的
在之前的方法学习中,我们也给大家提到了在普通方法中调用另一个方法是不需要通过对象的,但是我们也建议调用的使用加上this关键字。
### 构造方法调用
首先,如果我们创建一个与构造器同名的方法
~~~
public Cat(String name, int month, double weight, String species) {
this.name = name;
this.month = month;
this.weight = weight;
this.species = species;
}
public Cat() {
System.out.println("我是无参构造器");
}
public void Cat() {
System.out.println("我不是构造方法,只是一个与构造方法同名的普通方法");
}
~~~
这样的方法不会出现语法上的错误,但是强烈不推荐大家这样使用
第二点,通过this()调用重载的构造器:
构造方法在类外进行调用时,只能配合new关键字,不能直接通过对象名.来调用。
类中的普通方法之间可以互相调用。如
~~~
public void run(){
Cat(); //出错
eat();
System.out.println("aaa");
}
~~~
构造方法在类内,普通方法不能调用构造方法。
构造方法的调用,只能在构造方法之间来完成。如
~~~
public Cat(String name ,...){
this();
}
~~~
构造方法内可以使用this()来调用构造方法(有参、无参均可,只要是申明过的),且必须放在方法体内第一行。**而且不允许出现两条this()语句。**
## 课程总结
![](https://img.kancloud.cn/9d/8a/9d8a836eab87db675719c30ae01185f4_665x366.png)
现阶段,我们对于类的创建有着如上的了解。同时,需要补充的是成员属性的默认值问题
![](https://img.kancloud.cn/d5/38/d53803df623a2420c13c71b8abe4ecfa_594x536.png)
## 练习
一、单选
1. 有关Java中的类和对象,以下说法错误的是
~~~
A. 同一个类的所有对象都拥有相同的特征和行为
B. 类和对象一样,只是说法不同
C. 对象是具有属性和行为的实体
D. 类规定了对象拥有的特征和行为
~~~
2. 在java中,以下程序的运行结果是
![](https://img.kancloud.cn/76/08/7608320e072888528a6eb7f7b19ac324_450x266.png)
~~~
A. 输出:null
B. 正常运行,但不会输出任何内容
C. 编译出错,不能运行
D. 能运行,但运行时会出现异常
~~~
3. 下面代码运行的正确结果是
![](https://img.kancloud.cn/ce/03/ce03d40bd8b8da39015338555f554bab_448x293.png)
~~~
A. 编译错误,无法正常运行
B. 编译正确,但运行时产生错误
C. hello
D. world
~~~
4. 哪个空间用于存储使用new关键字所创建的对象
~~~
A. 堆
B. 栈
C. 方法区
D. 实例区
~~~
5. 分析下面的Java代码,编译运行结果是
![](https://img.kancloud.cn/66/40/6640921e15bf20ccf3f3e5c9c7c2c887_449x252.png)
~~~
A. 运行结果为:学号:1 姓名:张三
B. 运行结果为:学号:null 姓名:张三
C. 程序出现编译错误
D. 程序出现运行时异常
~~~
6. 下面的哪几项是合法的构造方法重载(多选)
![](https://img.kancloud.cn/a4/af/a4af8b40ecee897e5d4e96377c1b14c0_403x112.png)
~~~
A. public Display(String s){}
B. public int display(int n1,int n2){}
C. Display (int …a){}
D. public display(Strnig s,int a){}
~~~
7. 运行结果为()
![](https://img.kancloud.cn/14/55/1455547db693f969940fa0b79d755e59_431x177.png)
~~~
A. mainboard:华硕,cpu:Intel
B. mainboard:s1,cpu:s2
C. mainboard:Intel,cpu:华硕
D. 华硕,Intel
~~~
8. 在Java中,以下程序编译运行后的输出结果为( )
![](https://img.kancloud.cn/0d/20/0d2011ab1e053f1354d289a835b0147b_445x328.png)
~~~
A. 6
B. 3 4
C. 8
D. 7
~~~
9. 在Java中,下列关于this的说法错误的选项是(多选)
~~~
A. 在构造方法中如果使用this调用其他构造方法,只能是第一条语句
B. 不能在构造方法中调用同一个类的其他构造方法
C. 在构造方法在中如果使用this调用其他构造方法,语句可以放在任意位置
D. 可以使用“this.方法名()”或“this.属性名”来引用当前对象的成员
~~~
二、编程
1. 创建Person类和测试类
属性:名字(name),年龄(age),年级( grade)
方法:
* 无参无返回值的student方法,描述为:我是一名学生!
* 带参数(性别sex)的方法,描述为:我是一个\*\*孩!(其中,\*\*为传入参数)
* 无参无返回值的mySelf方法,介绍自己的姓名、年龄、年级(参数参考效果图)
~~~
public class Test {
public static void main(String[] args) {
//使用new关键字实例化对象
//传入name、age、grade的参数值
//分别调用student、sex、mySelf方法
}
}
~~~
2. 编写自定义猴子类
![](https://img.kancloud.cn/bc/3f/bc3ffcc3b4e85013eaeae1e093e47a75_324x284.png)
效果图:
![](https://img.kancloud.cn/dc/de/dcdeaa13b0e31a58e2ecc1cb249deb8b_319x138.png)
~~~
public class Monkey {
//属性:姓名(name)、特征(feature)
//无参的构造方法(默认初始化name和feature的属性值,属性值参考效果图)
//带参的构造方法(接收外部传入的参数,分别向 name 和 feature 赋值)
}
~~~