导学
通过前面的学习,我们对Java程序的运行流程有了一定的认识,掌握了分支结构,循环结构等常用逻辑,了解了Java的基本数据类型和引用数据类型,还认识了Java的方法构建。
我们也能够通过这些知识来解决一些简单的问题,但是当遇到一些复杂问题的时候,这些技能是远远不够的。这就像盖房子,想盖一间小房子,会砌砖抹泥就已经足够了。但是想要盖一幢摩天大楼,就一定要懂得建筑工程方面的知识了。
在Java开发中,面向对象程序思想就是这样一种技能。相较于早年面向过程的程序开发,面向对象开发在程序的稳定性,可扩展性和可重用性方面都有着无可比拟的优势。
在本单元的学习中,我们将要学习面向对象编程的三大特征,封装,继承和多态,以及编写具有面向对象思想的Java程序。
初识面向对象
类和对象
- 什么是对象
对象不仅在编程领域,而且在现实生活中都是一个非常重要的概念。我们需要理解一个概念,“万物皆对象”。显示存在的客观事物都是对象。比如长城,电脑,一件衣服,一只狗,一只猫都是对象。只要是现实生活中存在的都是对象。 - 什么是面向对象 ( 因观测而产生 ➡ 对象 ➡ 根据对象的特征 ➡ 描述对象 )
从字面上理解,面向对象就是与对象面对面,关注对象。从计算机的角度,就是关注现实存在的事物的各方面信息,从对象的角度出发根据事物的特征进行程序的设计。
虽然这些概念比较玄幻,但是只要根据现实生活的中事物的分析方式,就可以轻松的搞定对象了。 - 什么是类 ( 对象的特征 ➡ 总结成类 )
- 这里我们使用一个例子来描述:比我去逛宠物商店,想要买一只猫。我会告诉店员我想要一只短毛的,小一点的可爱的猫。于是店员给我推荐了两只猫所以这样的一个虚拟的描述,就是我们的类,而花花与凡凡就是根据类的特征而选出来的具体的实物(对象)。所以,针对于类,我们可以给出这样的描述:
类就是模子,用于确定对象将会拥有的特征(属性)和行为(方法)
对象是类的实例化表现。由于在计算机世界中所有的信息都是数据,所以我们可以认为:
- 类是对象的类型
- 对象是特定类型的数据
//类作为数据类型 对象因类成就数据
Cat huahua
类和对象的关系 。 所以,针对于类与对象的关系,我们结合上述课程可以总结出如下观点:
- 对象是类的实例化表现
- 类是对象的类型
- 对象是特定类型的数据
- 之前我们也提到对象拥有属性和方法两个特征,那么什么是属性和方法呢。属性代表着对象具有的各种静态特征,即对象有什么;方法代表着对象具有的各种动态行为,即对象能做什么。
在上节内容中,我们举了两只猫的例子,我们可以发现这两只猫都有着名字,性别,年龄和毛色等这些特征。只不过每只猫的具体指示是不同的,那么这些名字,性别,年龄和毛色等共有特征,就是猫的属性,而类似猫的跑,跳,睡,吃等动作,就是猫能干什么,意味着猫这个对象的方法。
最后总结一下:
创建类
本节内容,我们试着通过Java程序来描述一下,我们举过的两只猫的例子。
包的管理
首先,针对于本节课的开始,我们需要理解一下包的概念。比如我们在操作系统中会通过文件夹来实现文件的管理,在Java中,我们也可以通过包实现类的管理。
为了便于类文件的管理,Java 中引入了包的概念 package,类的唯一性是要带包名的。
包名也存在其对应的命名规范
- 英文字母小写
- 域名的倒序
比如: ``` 第一层是企业的域名的反写
例如:com.dodoke
第二层是企业项目的名称
例如:com.dodoke.j96、com.dodoke.crm
第三层是企业项目模块的名称
例如:com.dodoke.j96.oop、com.dodoke.crm.base
我们也可以在类中自主的定义包
// 在类文件的第一行 package com.duduke.j96.oop;
> 一个Java源文件中,只能有一个package语句(包的声明语句),而且必须放置在Java源文件的第一行。
> 在不同的包中,是可以定义相同类名的类的。因为一个类的唯一性是包含包名的,比如`com.ntduduke.j96.oop.Demo1`叫做类的全名。
> 在实际的企业开发中,是不允许出现没有包的类。
> **当几个类同属于一个包下,相互之间可以直接使用**
<a name="ZlQtt"></a>
#### 包的使用
在同一个包中,优先使用同一个包中的类。<br />在不同的包中,无法得知具体使用哪个类,所以此时需要告诉jvm,在当前类中,具体使用的是哪个类
1. 使用类全名 - Java依靠类全名区分类,所以可以写全类的名字进行区分,一般不建议
1. 引入包中的类 - 如果某个包中的类在本类中大量的使用,可以使用import进行引入类全名
1. 引入包中的所有类 - 如果一个包中绝大多数的类都会在本类中被使用到,那么可以将类全部引入,一般不建议
使用 import (**在类名和包中间填写引入语句**)关键字将本类要使用的其他包中的类进行引入。
> 但是,import 不是必须的,我们可以使用类全名的方式进行类的使用。一般不建议,太麻烦。
使用`ALT+/`可以单个引入,也可以使用`CTRL+SHIFT+O`全部引入。<br />如果要引入某个包下面的所有类,可以使用通配符`*`,例如,引入`com.ntduduke.j96.oop.*`,但是要注意通配符只能出现在最后。<br />**java.lang 包中的类**不需要使用 import引入,jvm会自动在加载每个类时就会引入Java.lang包中的所有类。不需要使用 import。
<a name="PVgPA"></a>
#### 创建对象的类型
类存在动态的行为和静态的特征<br />用代码描述: 类的成员是对象<br />动态的行为 - 叙述具体动作的方法(成员方法 - 类的成员所拥有的方法)<br />静态的特征 - 描述具体的属性(成员属性 - 类的成员所拥有的属性,可以利用访问修饰符public进行修饰)<br />虽然这个属性和方法写在类中,但只有对象才能够使用
通过代码描述了猫这个物种
```java
public class Cat{
//成员属性(属性指有什么)、昵称、年龄、体重;
public String name;//昵称
public int month;//月份
public double weight;//体重
public int maxAge = 20;//猫的平均最大年龄为20年(给属性设置了默认值,即每只猫最大年龄都是20年,但是可以修改)
//成员方法(方法指能做什么):跑步、吃东西
//跑步的方法
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();
//在Cat类中存在四个成员属性,当构建Cat类的one对象时,one就拥有了这四个属性
//Cat.eat();类无法调用成员方法和成员属性
System.out.println(one.maxAge);//有默认值 20
one.maxAge = 25;//对属性重新赋值
System.out.println("我的最大年龄" + one.maxAge);//25
//同样可以设置one的属性
one.name = "花花";
one.month = 300;
one.weight = 1.8;//kg
Cat two = new Cat();
System.out.println(two.maxAge);//20
}
}
我们发现对于对象的month
属性,它是存在默认值的。
那么再来对比一下,我们之前在方法中给大家介绍的局部变量。
局部变量通常是指我们在方法,流程控制结构等内容中定义的变量。
局部变量不存在默认值,未初始化调用会报错
变量的生命周期在 { } 定义的范围内
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 类名();
实例化对象的过程可以分为两部分:
- 声明对象 - 告诉JVM该对象存在 Cat one;
实例化对象 - 即真正创建对象 new Cat(); 匿名对象,匿名对象只能使用一次
⬇
为了多次使用实例化对象,需要将匿名对象地址保存起来 : one = new Cat();
声明对象:是在内存的”栈”空间里开辟了一块空间,取名叫one,此时里面为空(null),并且对它的属性和方法的调用是不允许的。所以因为这样我们并不能像真正的对象那样使用它
实例化对象:是在内存的堆空间里开辟了一块空间,完成了对象相关信息的初始化操作
声明对象和实例化对象是在内存的两个不同的空间去完成的,接着通过赋值符号”=”把两个空间关联起来关联,将堆空间中的内存地址存放到了栈空间中,在栈当中存储的实际上是堆当中地址的引用。
new
关键字的作用,实际上就是去开辟新的内存空间。
System.out.print(one);System.out.print(two);
引用数据类型,打印类全名+@+内存地址的哈希值,如果两个对象是同一个对象,即内存地址相同,则哈希值相同
System.out.print(one == two); //false
== 引用数据类型,判断内存地址是否相同
one 与 two 不是同一个对象,指向不同的内存地址
Cat one= new Cat();
Cat two= new Cat();
我们针对于two
对象,采取同样的赋值。如果修改two
对象的信息则不会对one
对象造成任何的影响。
另一种实例化方式:
Cat one= new Cat();
Cat two= one;//将one所代表的内存地址复制给了two
构造方法介绍
构造方法也称之为构造函数,构造器,是面向对象编程中的一个重要概念。
我们经常会使用构造方法来完成对象初始化的相关设置。构造方法在调用的时候必须配合new关键字,是不能被单独调用的。
构造方法语法:
注意:构造方法与类同名且没有返回值。构造方法只能在对象实例化的时候被调用
构造器本身是一个比较特殊的方法,方法名就是类名,没有返回值(和void是有区别的),构造器是类创建对象的唯一途径。
构造器的最大用处就是创建对象
无参构造方法
之前我们也学习过方法,方法必须先定义好才能使用。但是,在我们的类中并没有创建构造方法,但是我们依然可以使用构造方法去创建对象。
当没有去显示的写无参构造器时,jvm会自动帮我们创建无参构造器
这是因为,当没有指定构造方法时,系统会自动添加无参构造方法。也就是说**在一个类中至少会存在一个构造方法(自动或手动创造)**。便于我们的程序能够正常的执行,对象能够正常的进行实例化操作。
在一个类中,如果手动创建有参构造器,一定要再手动创建一个无参构造器
当显示的写了无参构造器时,JVM不会帮我们创建构造器,只会调用在类中的显式构造器
public Cat() {
System.out.println("我是无参构造方法");
}
Cat one = new Cat();//其实也就是调用了无参构造方法
一个类中可以有多个构造方法,当有指定构造方法、无论是有参、无参的构造方法,都不会自动添加无参的构造方法。
有参构造方法
无参构造器 与 有参构造器的区别:
无参构造器不会改变对象的属性值
有参构造器可以在创建对象的同时,为对象的属性赋值
通常我们会通过构造方法来完成对象的实例化操作。通过构造器为成员变量定义初始化值,这也是构造器的最最最重要的用途之一
在构造器和无static修饰的方法中,可以直接调用当前类的属性
public Cat(String name, int month, double weight, String species) {
name = name;
month = month;
weight = weight;
species = species;
}
//修改参数名称
//使用this.属性名=参数名
实际上,上述构造方法中的四条语句不会起作用。其实此处的代码逻辑发生了错误,遵循了一种就近原则 ——赋值过程中先优先的去找同一个作用范围内的成员进行赋值操作。只有找不到的情况下才会扩大作用范围,去类里面找。
针对于这样的问题,我们可以有两种解决方案。
- 修改参数名称
-
构造器小结
结合之前学习的内容,我们再来分析一下一个对象的创建过程:
在栈内存中,会存储对象名, 在没有执行构造器创建对象并赋值是,此时对象名对应的值应为null
- 通过new关键字调用类的构造器在堆内存中分配了一块对象区域;
- 通过赋值运算符= ,将堆内存中的对象地址赋给栈内存中的变量名;
- 例如再次给对象的属性赋值: 通过栈内存中的地址定位到对象在堆内存中的地址,找到相应的成员变量,进行一个赋值操作
引用: 引用还可以称之为【地址】,【指针】 。特指的是引用数据类型。因为只用 类类型(引用数据类型)才会在堆内存中分配对象空间,并将地址(指针)在栈内存中用于对象变量名的引用。
this关键字
通过this调用属性,并为属性赋值
this可以调用属性和方法
只有对象才能调用成员属性和成员方法,所以this代表一个对象,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指向该对象。根据this所在的位置,大致分为两种情况
- 出现在构造器中:引用该构造器正在初始化的对象
- 出现在普通方法中: 正在调用该方法的对象
this用在类定义中,获取当前对象的属性,或者调用当前对象的方法在类定义中,可以省略this关键字去调用属性或者方法,但是在类被编译的时候,编译器还是会加上this关键字。所以我们强烈建议在类定义的时候如果要调用该类中的普通成员变量或者方法,还是要把this给加上去 用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()调用重载的构造器:
构造器不能直接出现在其他构造器中,必须借由this()来代表一个构造器,去调用其他构造器
构造方法在类外进行调用时,只能配合new关键字,不能直接通过对象名.来调用。
类中的普通方法之间可以互相调用。如
public void run(){
Cat(); //出错
eat();
System.out.println("aaa");
}
构造方法在类内,普通方法不能调用构造方法。
构造方法的调用,只能在构造方法之间来完成。如
public Cat(String name ,...){
this();
}
构造方法内可以使用this()来调用构造方法(有参、无参均可,只要是申明过的),且必须放在方法体内第一行。而**且不允许出现两条this()语句**。
通过参数个数和参数类型来决定具体调用哪个构造器
课程总结
现阶段,我们对于类的创建有着如上的了解。同时,需要补充的是成员属性的默认值问题
public class Person {
//成员属性(属性指有什么)、昵称、年龄、体重;
//定义属性:[访问修饰符] 数据类型 属性名
public String yourName;//名字
public int month;//月份
public double weight;//体重
//成员方法(方法指能做什么):跑步、吃东西
//定义方法:访问修饰符 返回类型 方法名(参数){ 方法体 }
public void run(){
System.out.println("我会跑步");
}
//吃东西的方法
public void eat(){
System.out.println("我会吃东西");
}
//构造器
//构造方法:访问修饰符 没有返回值类型 与类名相同的构造方法名 (可以指定参数) { //初始化代码 }
//无参 构造器
public Person() {
System.out.println("我是无参构造方法");
}
//有参 构造器
public Person(String yourName, int month, double weight) {
this.name = yourName;
this.month = month;
this.weight = weight;
}
//主方法
public static void main(String[] args) {
System.out.println("通过主方法来执行程序,是程序执行的入口");
}
}