Java基础|1-02-面向对象和封装 @面向对象篇
写在前面:
此文是笔者在学习Java系列课程的过程中,参考相关课件、视频讲解、课程代码,并结合一些文档、思维导图及个人理解,对所学内容做的阶段性梳理与总结。
- 写于:2021年1月25日
- 内容:Java后端系列笔记002(Java基础-面向对象和封装)
- 全文:7316字
一、面向对象思想
1. 1 面向对象思想概述
概述
- Java语言是一种
面向对象
的程序设计语言,而面向对象思想是一种程序设计思想,我们在面向对象思想的指引下,使用Java语言去设计、开发计算机程序。 这里的对象泛指现实中一切事物,每种事物都具备自己的属性
和行为
。 - 面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,描述成计算机事件的设计思想。
- 它区别于面向过程思想,强调的是通过
调用对象的行为
来实现功能,而不是自己一步一步的去操作实现。
理解
- 乔布斯是这样解释面向对象编程的:
Jeff Goodell:请你用尽量简练的语言解释一下,究竟 什么是面向对象的软件 ?
乔布斯:对象就像人一样,也是活生生的生命。他们有知识,知道怎么完成任务;他们有记忆,可以把发生的事情记下来。而你和他们的互动并不是低层次的,你是与他们在一个 高度抽象 的层面上互动,就像我们现在的对话一样。
我举个例子来说明。如果我是一个 “洗衣”对象 ,你可以把脏衣服给我,然后告诉我说:“请帮我把这些衣服洗了吧!” 而我恰好知道旧金山最好的洗衣房在哪,并且我会说英语,兜里也有美元。于是我出门打了一辆出租车,告诉司机带我去位于旧金山的洗衣房。我到了那里洗好衣服之后,又坐车回到这里。我把洗好的衣服交还给你,说:“你的衣服已经洗好了。”
你并不知道我是怎么做到的。你不知道哪里有洗衣店,也可能只会说法语,或者是兜里没钱,连车都打不了。但是我知道怎么完成这项任务,而你 不需要知道任何细节 。所有的这些复杂流程都隐藏在我的内部,而我们之间可以 高度抽象地互动 ,这就是对象。他们把复杂过程封装在内部,而对外呈现的接口是高层次的,抽象的。
- 例子分析:
总的来说,就是:“你”只需要把事情交代给“我”,“我”来实现该需求,等事情完成,“你”看到的就是一个完成后的结果。至少细节,“你”不关心,也无需知道,原本复杂的事情,对“你”来说,变得简单了不少。
区别
-
面向过程(强调步骤)
- 当需要实现一个功能的时候,每一个具体的步骤都要亲力亲为(过程),详细处理每一个细节
-
面向对象(强调对象)
- 当需要实现一个功能的时候,不关心具体的步骤,而是找一个已经具有该功能的人(对象),来帮我做事
程序中的面向对象
public static void main(String[] args) {
int[] array = {
10, 20, 30, 40, 50, 60 };
// 要求打印格式为:[10, 20, 30, 40, 50,60]
// 使用【面向过程】,每一个步骤细节都要亲力亲为。
System.out.print("[");
for (int i = 0; i < array.length; i++) {
if (i == array.length - 1) {
// 如果是最后一个元素
System.out.println(array[i] + "]");
} else {
// 如果不是最后一个元素
System.out.print(array[i] + ", ");
}
}
}
public static void main(String[] args) {
int[] array = {
10, 20, 30, 40, 50, 60 };
// 使用【面向对象】
// 找一个JDK给我们提供好的Arrays类,(import java.util.Arrays;)
// 其中有一个toString方法,直接就能把数组变成想要的格式的字符串
System.out.println(Arrays.toString(array));
}
小贴士:面向对象的语言中,包含了三大基本特征,即封装、继承和多态
1. 2 类和对象 [ 导图 ]
环顾周围,你会发现很多对象,比如桌子,椅子,同学,老师等。桌椅属于办公用品,师生都是人类。那么什么是类呢?什么是对象呢?
实例:我们根据抽象的设计图,设计出具体的各种车,这个过程就是——实例化具体的对象
1. 3 类的定义
事物与类的对比
- 现实世界的一类事物:
- 属性:事物的状态信息
- 行为:事物能够做什么
- Java中用class描述事物也是如此:
- 成员变量:对应事物的属性
- 成员方法:对应事物的行为
类的定义格式
public class ClassName {
//成员变量
//成员方法
}
- 定义类:就是定义类的成员,包括成员变量和成员方法
- 成员变量:和以前定义变量几乎是一样的,只不过位置发生了改变(在类中,方法外)
- 成员方法:和以前定义方法几乎是一样的,只不过把static去掉
举例:
- 定义一个类,用来模拟“学生”事物。其中就有两个组成部分:
- 属性(是什么):姓名、年龄
- 行为(能做什么):吃饭、睡觉、学习
- 对应到Java的类当中:
- 成员变量(属性)
- 成员方法(行为)
/* 注意事项: 1. 成员变量是直接定义在类当中的,在方法外边。 2. 成员方法不要写static关键字。 */
public class Student {
// 成员变量
String name; // 姓名
int age; // 年龄
// 成员方法
public void eat() {
System.out.println("吃饭!"); }
public void sleep() {
System.out.println("睡觉!");}
public void study() {
System.out.println("学习!");}
}
1. 4 对象的使用
通常情况下,一个类并不能直接使用,需要根据类创建一个对象,才能使用。
- 导包:指出需要使用的类在什么位置
import 包名称.类名称;
import cn.itcast.day06.demo01.Student;
//对于和当前类属于同一个包的情况,可以省略导包语句不写
- 创建,格式:
类名称 对象名 = new 类名称();
Student stu = new Student();
- 使用,分为两种情况:
使用成员变量:对象名.成员变量名
使用成员方法:对象名.成员方法名(参数)
//也就是,想用谁,就用对象名点谁
举例:
public class Demo02Student {
public static void main(String[] args) {
// 1. 导包
// 我需要使用的Student类,和我自己Demo02Student位于同一个包下,所以省略导包语句不写
// 2. 创建:类名称 对象名 = new 类名称();
Student stu = new Student(); // 根据Student类,创建了一个名为stu的对象
// 3. 使用其中的成员变量:对象名.成员变量名
System.out.println(stu.name); // null
System.out.println(stu.age); // 0
System.out.println("=============");
// 改变对象当中的成员变量数值内容
// 将右侧的字符串,赋值交给stu对象当中的name成员变量
stu.name = "赵丽颖";
stu.age = 18;
System.out.println(stu.name); // 赵丽颖
System.out.println(stu.age); // 18
System.out.println("=============");
// 4. 使用对象的成员方法:对象名.成员方法名()
stu.eat();
stu.sleep();
stu.study();
}
}
小贴士: 如果成员变量没有进行赋值,那么将会有一个默认值,规则和数组一样
成员变量的默认值
数据类型 | 默认值 | |
---|---|---|
基本类型 | 整数(byte、short、int、long) | 0 |
浮点数(float、double) | 0.0 | |
字符(char) | ‘\u0000’ | |
布尔(boolean) | false | |
引用类型 | 数组、类、接口 | null |
1. 5 对象内存图
1-① 一个对象,调用一个方法内存图
- 通过上图,我们可以理解,在栈内存中运行的方法,遵循 “先进后出,后进先出” 的原则。变量one指向堆内存中的空间,寻找方法信息,去执行该方法。
- 但是,这里依然有问题存在。创建多个对象时,如果每个对象内部都保存一份方法信息,这就非常浪费内存了,因为所有对象的方法信息都是一样的。那么如何解决这个问题呢?请看如下图解。
1-② 两个对象,调用同一方法内存图
- 对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。这样哪怕是多个对象,方法信息只保存一份,节约内存空间。
1-③ 两个引用,指向同一个对象内存图
2-① 使用对象类型,作为方法的参数
- 使用对象类型作为方法的参数,传递的是对象的地址值
2-② 使用对象类型,作为方法的返回值
- 使用对象类型作为方法的返回值,返回的是对象的地址值
1. 6 成员变量和局部变量区别 [ 导图 ]
二、封装
2. 1 封装概述
面向对象三大特征:封装、继承、多态
概述
- 面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是
隐藏在对象内部
的,外界无法直接操作和修改 - 封装可以被认为是一个
保护屏障
,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的方式 - 适当的封装可以让代码更容易理解与维护,也加强了代码的安全性
原则
- 将属性隐藏起来,若需要访问某个属性,提供公共方法对其访问
小贴士:封装就是将一些细节信息隐藏起来,对于外界不可见。
优点
- 良好的封装能够减少耦合
- 类内部的结构可以自由修改
- 可以对成员变量进行更精确的控制
- 隐藏信息,实现细节
封装性在Java当中的体现
- 方法就是一种封装
- 关键字private也是一种封装(私有)
封装的步骤
- 使用 private 关键字来修饰成员变量
- 对需要访问的成员变量,提供对应的一对 getXxx方法 、setXxx 方法(Getter/Setter方法)
小贴士:对于Getter来说,不能有参数,返回值类型和成员变量对应; 对于Setter来说,不能有返回值,参数类型和成员变量对应。
2. 2 封装的操作 - private关键字
private的使用
- 问题描述:定义Person的年龄时,无法阻止不合理的数值被设置进来
- 解决方案:用private关键字将需要保护的成员变量进行修饰
- 格式:
private 数据类型 变量名 ;
- 格式:
- 说明:一旦使用了
private
进行修饰,那么本类当中仍可随意访问。但是!超出了本类范围之外就不能再直接访问了 - 如何访问:可
间接
访问private成员变量 —— 定义一对Getter/Setter方法
(以setXxx或getXxx命名)
public class Person {
String name; // 姓名
private int age; // 年龄(将age属性设置为私有的)
public void show() {
// 本类中可随意访问被 private 修饰的成员变量age
System.out.println("我叫:" + name + ",年龄:" + age);
}
// 这个setXxx成员方法,专门用于向age设置数据
public void setAge(int num) {
if (num < 100 && num >= 9) {
// 如果是合理情况
age = num;
} else {
System.out.println("数据不合理!");
}
}
// 这个getXxx成员方法,专门用于获取age的数据
public int getAge() {
return age;
}
}
public class Demo03Person {
public static void main(String[] args) {
Person person = new Person();
person.show();// 我叫:null,年龄:0
person.name = "赵丽颖";
// person.age = -20; // 直接访问private内容,错误写法!
person.setAge(20);// 使用setAge方法设置年龄
person.show();// 我叫:赵丽颖,年龄:20
}
}
private的含义
- private是一个权限修饰符,代表最小权限
- 可以修饰成员变量和成员方法
- 被private修饰后的成员变量和成员方法,只在本类中才能访问
注意事项
- 对于基本类型当中的boolean值,Getter方法一定要写成isXxx的形式,而setXxx规则不变
private boolean male; // 是不是爷们儿
public void setMale(boolean b) {
male = b;
}
public boolean isMale() {
// isXxx的形式
return male;
}
2. 3 封装优化 1 - this关键字
this的使用
- 当方法的局部变量和类的成员变量重名的时候,根据“就近原则”,优先使用局部变量
- 如果需要访问本类当中的成员变量,需要使用格式:
this.成员变量名
- 含义:this代表所在类的当前对象的引用(地址值),即对象自己的引用
- 提示:“通过谁调用的方法,谁就是this。”
图例-理解this的含义:
封装优化 - this
- 使用 this 修饰方法中的变量,解决成员变量被隐藏的问题
public class Person{
private String name;
private int age;
public void setName(String name){
// 赋值失败!由于形参变量名与成员变量名重名,导致成员变量名被隐藏
// name = name;
//使用this修饰方法中的变量,解决成员变量被隐藏的问题
this.name = name;
}
public String getName(){
return name;
// 也可写成return this.name (this可省略)
// 理由:方法中只有一个变量名时,默认也是使用 this 修饰
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
小贴士:采用 this 关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突
2. 4 封装优化 2 - 构造方法
概念
- 构造方法是专门用来创建对象的方法
- 当我们通过关键字new来创建对象时,其实就是在调用构造方法
格式
public 类名称(参数类型 参数名称) {
方法体
}
示例:
public class Student {
// 成员变量
private String name;
private int age;
// 无参数的构造方法
public Student() {
System.out.println("无参构造方法执行啦!");
}
// 全参数的构造方法
public Student(String name, int age) {
System.out.println("全参构造方法执行啦!");
this.name = name;
this.age = age;
}
}
注意事项
-
构造方法的名称必须和所在的类名称完全一样,就连大小写也要一样
-
构造方法不要写返回值类型,连void都不写
-
构造方法不能return一个具体的返回值
-
如果没有编写任何构造方法,那么编译器将会默认赠送一个构造方法,没有参数、方法体什么事情都不做
public Student() {}
-
一旦编写了至少一个构造方法,那么编译器将不再赠送
-
构造方法也是可以进行重载的(重载:方法名称相同,参数列表不同)
2. 5 标准代码 - JavaBean
一个标准的类(也叫JavaBean)通常要拥有下面四个组成部分:
- 所有的成员变量都要使用 private 关键字修饰
- 为每一个成员变量编写一对 Getter/Setter 方法
- 编写一个无参数的构造方法
- 编写一个全参数的构造方法
下面来编写一个完整的程序:
- 快速编写JavaBean,步骤:
- 写好成员变量后,使用快捷键
Alt + Insert
: - 使用
Alt + Insert
→ 选择Constructor
→ 点击Select None
→ 自动生成无参构造方法 - 使用
Alt + Insert
→ 选择Constructor
→ 选择成员变量→OK
→ 自动生产有参构造方法 - 使用
Alt + Insert
→ 选择Getter and Setter
→ 选择所有成员变量 →OK
→ 自动生成Getter/Setter 方法
- 写好成员变量后,使用快捷键
public class Student {
// 成员变量
private String name; // 姓名
private int age; // 年龄
//无参构造方法【必须】
public Student() {
}
//有参构造方法【建议】
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//成员方法:getXxx()、setXxx()
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
- 测试类,代码:
public class Demo04Student {
public static void main(String[] args) {
Student stu1 = new Student();
stu1.setName("小黑");
stu1.setAge(20);
System.out.println("姓名:" + stu1.getName() + ",年龄:" + stu1.getAge());
System.out.println("=================");
Student stu2 = new Student("小白", 21);
System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
stu2.setAge(22);
System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
}
}
- 执行结果:
姓名:小黑,年龄:20
=================
姓名:小白,年龄:21
姓名:小白,年龄:22