Day07面向对象基础(封装)
学习目标
- 初步了解面向对象的思想
- 理解类与对象的概念和关系
- 能够掌握类的定义格式
- 能够掌握创建对象格式
- 理解包的作用
- 掌握包的声明和导入
- 掌握实例变量的声明和使用
- 掌握实例方法的声明和调用
- 理解实例变量与局部变量的区别
- 封装
- 构造器
1 面向对象编程思想概述
1.1 编程语言概述
Java是一种计算机程序设计语言。所有的计算机程序一直都是围绕着两件事在进行的,程序设计就是用某种语言编写代码来完成这两件事,所以程序设计语言又称为编程语言(编写程序的语言)。
- 如何表示和存储数据
- 基本数据类型的常量和变量:表示和存储一个个独立的数据
- 对象:表示和存储与某个具体事物相关的多个数据(例如:某个学生的姓名、年龄、联系方式等)
- 数据结构:表示和存储一组对象,数据结构有数组、链表、栈、队列、散列表、二叉树、堆…
- 基于这些数据都有什么操作行为,其实就是实现什么功能
- 数据的输入和输出
- 基于一个或两个数据的操作:赋值运算、算术运算、比较运算、逻辑运算等
- 基于一组数据的操作:统计分析、查找最大值、查找元素、排序、遍历等
1.2 程序设计方法
C语言是一种面向过程的程序设计语言,因为C语言是在面向过程思想的指引下去设计、开发计算机程序的。
Java语言是一种面向对象的程序设计语言,因为Java语言是在面向对象思想的指引下去设计、开发计算机程序的。
其中面向对象和面向过程都是一种编程思想,基于不同的思想会产生不同的程序设计方法。
-
面向过程的程序设计思想(Process-Oriented Programming),简称POP
-
关注的焦点是过程:过程就是操作数据的步骤,如果某个过程的实现代码在很多地方重复出现,那么就可以把这个过程抽象为一个函数,这样就可以大大简化冗余代码,也便于维护。
-
代码结构:以函数为组织单位。独立于函数之外的数据称为全局数据,在函数内部的称为局部数据。
-
-
面向对象的程序设计思想( Object Oriented Programming),简称OOP
-
关注的焦点是类:面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,用类来表示。某个事物的一个具体个体称为实例或对象。
-
代码结构:以类为组织单位。每种事物都具备自己的属性(即表示和存储数据,在类中用成员变量表示)和行为/功能(即操作数据,在类中用成员方法表示)。
-
2 类和对象
2.1 什么是类
类是一类具有相同特性的事物的抽象描述,是一组相关属性和行为的集合。
- 属性:就是该事物的状态信息。
- 行为:就是在你这个程序中,该状态信息要做什么操作,或者基于事物的状态能做什么。
2.2 什么是对象
对象是一类事物的一个具体个体(对象并不是找个女朋友)。即对象是类的一个实例,必然具备该类事物的属性和行为。
例如:做一个养宠物的小游戏
类:人、猫、狗等
类与对象的关系
- 类是对一类事物的描述,是抽象的。
- 对象是一类事物的实例,是具体的。
- 类是对象的模板,对象是类的实体。
2.4 定义类
关键字:class(小写)
[修饰符] class 类名{
}
类的定义格式举例:
public class Student{
}
2.5 创建对象
关键字:new
new 类名()//也称为匿名对象
//给创建的对象命名
//或者说,把创建的对象用一个引用数据类型的变量保存起来,这样就可以反复使用这个对象了
类名 对象名 = new 类名();
那么,对象名中存储的是什么呢?答:对象地址
public class TestStudent{
public static void main(String[] args){
System.out.println(new Student());//Student@7852e922
Student stu = new Student();
System.out.println(stu);//Student@4e25154f
int[] arr = new int[5];
System.out.println(arr);//[I@70dea4e
}
}
发现学生对象和数组对象类似,直接打印对象名和数组名都是显示“类型@对象的hashCode值",所以说类、数组都是引用数据类型,引用数据类型的变量中存储的是对象的地址,或者说指向堆中对象的首地址。
那么像“Student@4e25154f”是对象的地址吗?不是,因为Java是对程序员隐藏内存地址的,不暴露内存地址信息,所以打印对象时不直接显示内存地址,而是JVM帮你调用了对象的toString方法,将对象的基本信息转换为字符串并返回,默认toString方法返回的是“对象的运行时类型@对象的hashCode值的十六进制值”,程序员可以自己改写toString方法的代码(后面会讲如何改写)。
3 成员变量
3.1 如何声明成员变量
[修饰符] class 类名{
[修饰符] 数据类型 成员变量名;
}
示例:
public class Student{
String name;
char gender;
double score;
int age;
char sex;
}
位置要求:必须在类中,方法外
类型要求:可以是Java的任意类型,包括基本数据类型、引用数据类型(类、接口、数组等)
修饰符:成员变量的修饰符有很多,例如:public、protected、private、static、volatile、transient、final等,后面会一一学习。
其中static可以将成员变量分为两大类,静态变量和非静态变量。其中静态变量又称为类变量,非静态变量又称为实例变量或者属性。
3.2 对象的实例变量
1、实例变量的特点
(1)实例变量的值是属于某个对象的
- 必须通过对象才能访问实例变量
- 每个对象的实例变量的值是独立的
(2)实例变量有默认值
分类 | 数据类型 | 默认值 |
---|---|---|
基本类型 | 整数(byte,short,int,long) | 0 |
浮点数(float,double) | 0.0 | |
字符(char) | ‘\u0000’ | |
布尔(boolean) | false | |
数据类型 | 默认值 | |
引用类型 | 数组,类,接口 | null |
2、 实例变量和局部变量的区别:
1、声明位置和方式
(1)实例变量:在类中方法外
(2)局部变量:在方法体{}中或方法的形参列表、代码块中
2、在内存中存储的位置不同
(1)实例变量:堆
(2)局部变量:栈
3、生命周期
(1)实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消亡,
而且每一个对象的实例变量是独立的。
(2)局部变量:和方法调用的生命周期一样,每一次方法被调用而在存在,随着方法执行的结束而消亡,
而且每一次方法调用都是独立。
4、作用域
(1)实例变量:通过对象就可以使用,本类中“this.,没有歧义还可以省略this.”,其他类中“对象.”
(2)局部变量:出了作用域就不能使用
5、修饰符(后面来讲)
(1)实例变量:public,protected,private,final,volatile,transient等
(2)局部变量:final
6、默认值
(1)实例变量:有默认值
(2)局部变量:没有,必须手动初始化。其中的形参比较特殊,靠实参给它初始化。
3、实例变量的访问
对象.实例变量
例如:
package com.atguigu.test03.field;
public class TestPerson {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "张三";
p1.age = 23;
p1.gender = '男';
Person p2 = new Person();
/*
(1)实例变量的值是属于某个对象的
- 必须通过对象才能访问实例变量
- 每个对象的实例变量的值是独立的
(2)实例变量有默认值
*/
System.out.println("p1对象的实例变量:");
System.out.println("p1.name = " + p1.name);
System.out.println("p1.age = " + p1.age);
System.out.println("p1.gender = " + p1.gender);
System.out.println("p2对象的实例变量:");
System.out.println("p2.name = " + p2.name);
System.out.println("p2.age = " + p2.age);
System.out.println("p2.gender = " + p2.gender);
}
}
4、实例变量的内存分析
内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁。其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来。我们编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存。Java虚拟机要运行程序,必须要对内存进行空间的分配和管理,每一片区域都有特定的处理数据方式和内存管理方式。
JVM的运行时内存区域分为:方法区、堆、虚拟机栈、本地方法栈、程序计数器几大块。
区域名称 | 作用 |
---|---|
程序计数器 | 程序计数器是CPU中的寄存器,它包含每一个线程下一条要执行的指令的地址 |
本地方法栈 | 当程序中调用了native的本地方法时,本地方法执行期间的内存区域 |
方法区 | 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 |
堆内存 | 存储对象(包括数组对象),new来创建的,都存储在堆内存。 |
虚拟机栈 | 用于存储正在执行的每个Java方法的局部变量表等。局部变量表存放了编译期可知长度的各种基本数据类型、对象引用,方法执行完,自动释放。 |
Java对象保存在内存中时,由以下三部分组成:
-
对象头
- 指向类的指针:每一个对象需要记录它是由哪个类创建出来的,而Java对象的类数据保存在方法区,指向类的指针就是记录创建该对象的类数据在方法区的首地址。该指针在32位JVM中的长度是32bit,在64位JVM中长度是64bit。
- 数组长度(只有数组对象才有)
-
实例数据
- 即实例变量的值
4 static关键字
在类中声明的实例变量,其值是每一个对象独立的。但是有些成员变量的值不需要或不能每一个对象单独存储一份,即有些成员变量和当前类的对象无关。
在类中声明的实例方法,在类的外面必须要先创建对象,才能调用。但是有些方法的调用和当前类的对象无关,那么创建对象就有点麻烦了。
此时,就需要将和当前类的对象无关的成员变量、成员方法声明为静态的(static)。
4.1 静态变量
1、语法格式
有static修饰的成员变量就是静态变量。
[修饰符] class 类{
[其他修饰符] static 数据类型 静态变量名;
}
2、静态变量的特点
-
静态变量的默认值规则和实例变量一样。
-
静态变量值是所有对象共享。
-
静态变量的值存储在方法区的类上。
-
静态变量在本类任意位置可以直接使用。
-
如果权限修饰符允许,在其他类中可以通过“类名.静态变量”直接访问,也可以通过“对象.静态变量”的方式访问(但是更推荐使用类名.静态变量的方式)。
-
静态变量的get/set方法也静态的,当局部变量与静态变量重名时,使用“类名.静态变量”进行区分。
分类 | 数据类型 | 默认值 |
---|---|---|
基本类型 | 整数(byte,short,int,long) | 0 |
浮点数(float,double) | 0.0 | |
字符(char) | ‘\u0000’ | |
布尔(boolean) | false | |
数据类型 | 默认值 | |
引用类型 | 数组,类,接口 | null |
4.2 静态变量内存分析
4.3 静态和非静态成员变量比较
相同点:
- 代码位置相同: 类中方法外
- 都有默认值
不同点:
- 内存位置不同
- 静态成员变量:方法区
- 普通成员变量:堆中
- 内存中开辟空间的分数不同
- 静态成员变量: 开辟一份空间
- 普通成员变量: 每创建一次对象 开辟一份空间
- 调用方式不同:
- 静态成员变量: 类名.属性名
- 普通成员变量: 对象名.属性名
- 生命周期不同
- 静态成员变量,在类加载阶段就会开辟空间;普通成员变量, 创建对象后开辟空间
- 静态成员变量,在类卸载时销毁;普通成员变量,当没有对象指向堆中内存时 由GC回收。
- 修饰符不同
- 静态成员变量: 被static 修饰
- 普通成员变量: 没有static 修饰
4.4 静态方法
static还可以用来修饰方法,被static修饰的方法被称为静态方法。
[修饰符] class 类{
[其他修饰符] static 返回值类型 方法名(形参列表){
方法体
}
}
静态方法特点:
- 静态方法里只能访问静态属性,不能访问非静态属性!
- 可以不创建实例对象,直接使用
类名.静态方法名()
来调用
5 包(Package)
5.1 包的作用
(1)可以避免类重名:有了包之后,类的全名称就变为:包.类名
(2)可以控制某些类型或成员的可见范围
如果某个类型或者成员的权限修饰缺省的话,那么就仅限于本包使用。
(3)分类组织管理众多的类
例如:
- java.lang----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread等,提供常用功能
- java.net----包含执行与网络相关的操作的类和接口。
- java.io ----包含能提供多种输入/输出功能的类。
- java.util----包含一些实用工具类,如集合框架类、日期时间、数组工具类Arrays,文本扫描仪Scanner,随机值产生工具Random。
- java.text----包含了一些java格式化相关的类
- java.sql和javax.sql----包含了java进行JDBC数据库编程的相关类/接口
- java.awt和java.swing----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
5.2 如何声明包
关键字:package
package 包名;
注意:
(1)必须在源文件的代码首行
(2)一个源文件只能有一个声明包的package语句
包的命名规范和习惯:
(1)所有单词都小写,每一个单词之间使用.分割
(2)习惯用公司的域名倒置开头
例如:com.atguigu.xxx;
建议大家取包名时不要使用“java.xx"包
5.3 如何跨包使用类
==注意:==只有public的类才能被跨包使用
(1)使用类型的全名称
例如:java.util.Scanner input = new java.util.Scanner(System.in);
(2)使用import 语句之后,代码中使用简名称
import语句告诉编译器到哪里去寻找类。
import语句的语法格式:
import 包.类名;
import 包.*;
注意:
使用java.lang包下的类,不需要import语句,就直接可以使用简名称
import语句必须在package下面,class的上面
当使用两个不同包的同名类时,例如:java.util.Date和java.sql.Date。一个使用全名称,一个使用简名称
(3)静态导入:可以将一个类里的静态成员变量或者方法导入使用
import static java.util.Arrays.sort;
6 封装
6.1 为什么需要封装?
- 我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?
- 我们使用的电脑,内部有CPU、硬盘、键盘、鼠标等等,每一个部件通过某种连接方式一起工作,但是各个部件之间又是独立的
- 现实生活中,每一个个体与个体之间是有边界的,每一个团体与团体之间是有边界的,而同一个个体、团体内部的信息是互通的,只是对外有所隐瞒。
面向对象编程语言是对客观世界的模拟,客观世界里每一个事物的内部信息都是隐藏在对象内部的,外界无法直接操作和修改,只能通过指定的方式进行访问和修改。封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。
随着我们系统越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要遵循“高内聚、低耦合”,而“高内聚,低耦合”的体现之一:
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合:仅对外暴露少量的方法用于使用
隐藏对象内部的复杂性,只对外公开简单和可控的访问方式,从而提高系统的可扩展性、可维护性。通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
6.2 封装的一种实现形式private(私有化)
成员变量(field)私有化之后,提供标准的get/set方法,供外界访问
1、成员变量封装的目的
- 隐藏类的实现细节
- 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。
- 便于修改,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。
2、实现步骤
- 使用
private
修饰成员变量
private 数据类型 变量名 ;
代码如下:
public class Person {
private String name;
private int age;
private boolean marry;
}
- 提供
getXxx
方法 /setXxx
方法,可以访问成员变量,代码如下:
public class Person {
private String name;
private int age;
private boolean marry;
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
public void setAge(int a) {
age = a;
}
public int getAge() {
return age;
}
public void setMarry(boolean m){
marry = m;
}
public boolean isMarry(){
return marry;
}
}
3、测试
package com.atguigu.encapsulation;
public class TestPerson {
public static void main(String[] args) {
Person p = new Person();
//实例变量私有化,跨类是无法直接使用的
/* p.name = "张三";
p.age = 23;
p.marry = true;*/
p.setName("张三");
System.out.println("p.name = " + p.getName());
p.setAge(23);
System.out.println("p.age = " + p.getAge());
p.setMarry(true);
System.out.println("p.marry = " + p.isMarry());
}
}
3、如何解决局部变量与实例变量同名问题
当局部变量与实例变量(非静态成员变量)同名时,在实例变量必须前面加“this.”
package com.atguigu.encapsulation;
public class Employee {
private String name;
private int age;
private boolean marry;
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 boolean isMarry() {
return marry;
}
public void setMarry(boolean marry) {
this.marry = marry;
}
}
package com.atguigu.encapsulation;
public class TestEmployee {
public static void main(String[] args) {
Employee e = new Employee();
e.setName("张三");
System.out.println("e.name = " + e.getName());
e.setAge(23);
System.out.println("e.age = " + e.getAge());
e.setMarry(true);
System.out.println("e.marry = " + e.isMarry());
}
}
6.3 IDEA自动生成get/set方法模板
- 大部分键盘模式按Alt + Insert键。
- 部分键盘模式需要按Alt + Insert + Fn键。
- Mac电脑快捷键需要单独设置
7 构造器
我们发现我们new完对象时,所有成员变量都是默认值,如果我们需要赋别的值,需要挨个为它们再赋值,太麻烦了。我们能不能在new对象时,直接为当前对象的某个或所有成员变量直接赋值呢。
可以,Java给我们提供了构造器(Constructor)。
1、构造器的作用
new对象,并在new对象的时候为实例变量赋值。
2、构造器的语法格式
构造器又称为构造方法,那是因为它长的很像方法。但是和方法还是有所区别的。
[修饰符] class 类名{
[修饰符] 构造器名(){
// 实例初始化代码
}
[修饰符] 构造器名(参数列表){
// 实例初始化代码
}
}
代码如下:
package com.atguigu.constructor;
public class Student {
private String name;
private int age;
// 无参构造
public Student() {}
// 有参构造
public Student(String name,int age) {
this.name = name;
this.age = age;
}
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 String getInfo(){
return "姓名:" + name +",年龄:" + age;
}
}
注意事项:
- 构造器名必须与它所在的类名必须相同。
- 如果你不提供构造器,系统会给出无参数构造器,并且该构造器的修饰符默认与类的修饰符相同
- 如果你提供了构造器,系统将不再提供无参数构造器,除非你自己定义。
- 构造器是可以重载的,既可以定义参数,也可以不定义参数。
- 构造器的修饰符只能是权限修饰符,不能被其他任何修饰
package com.atguigu.constructor;
public class TestStudent {
public static void main(String[] args) {
//调用无参构造创建学生对象
Student s1 = new Student();
//调用有参构造创建学生对象
Student s2 = new Student("张三",23);
System.out.println(s1.getInfo());
System.out.println(s2.getInfo());
}
}
3、同一个类中的构造器互相调用
- this():调用本类的无参构造
- this(实参列表):调用本类的有参构造
- this()和this(实参列表)只能出现在构造器首行
- 不能出现递归调用
package com.atguigu.constructor;
public class Student {
private String name;
private int age;
// 无参构造
public Student() {
// this("",18);//调用本类有参构造
}
// 有参构造
public Student(String name,int age) {
this();//调用本类无参构造
this.name = name;
this.age = age;
}
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 String getInfo(){
return "姓名:" + name +",年龄:" + age;
}
}
4、IDEA生成构造器:Alt + Insert
5、IDEA查看构造器和方法形参列表快捷键:Ctrl + P
8 this关键字
实例方法里的this用来表示调用这个方法的对象。简单说,哪个对象调用的这个方法,这个方法里的this就表示哪个对象!
注意事项:
-
this:只能用于实例方法,不能用于静态方法。
-
如果方法内没有同名变量,完全可以省略this。
方法不调用不执行,调用一次执行一次,每次调用会在栈中有一个入栈动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值,当方法执行结束后,会释放该内存,称为出栈,如果方法有返回值,就会把结果返回调用处,如果没有返回值,就直接结束,回到调用处继续执行下一条指令。
栈结构:先进后出,后进先出。
package com.atguigu.test04.method;
public class MethodMemory {
public static void main(String[] args) {
Rectangle r1 = new Rectangle();
Rectangle r2 = new Rectangle();
r1.length = 10;
r1.width = 2;
r1.print('#');
System.out.println("r1对象:" + r1.getInfo());
System.out.println("r2对象:" + r2.getInfo());
}
}
##9 JavaBean
JavaBean
是 Java语言编写类的一种标准规范。符合JavaBean
的类,要求:
(1)类必须是具体的和公共的,
(2)并且具有无参数的构造方法,
(3)成员变量私有化,并提供用来操作成员变量的set
和get
方法。
public class ClassName{
//成员变量
//构造方法
//无参构造方法[必须]
//有参构造方法[建议]
//getXxx()
//setXxx()
//其他成员方法
}
编写符合JavaBean
规范的类,以学生类为例,标准代码如下:
public class Student {
// 成员变量
private String name;
private int age;
// 构造方法
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// get/set成员方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
``
[外链图片转存中…(img-S5uNY8aC-1708569200847)]
##9 JavaBean
JavaBean
是 Java语言编写类的一种标准规范。符合JavaBean
的类,要求:
(1)类必须是具体的和公共的,
(2)并且具有无参数的构造方法,
(3)成员变量私有化,并提供用来操作成员变量的set
和get
方法。
public class ClassName{
//成员变量
//构造方法
//无参构造方法[必须]
//有参构造方法[建议]
//getXxx()
//setXxx()
//其他成员方法
}
编写符合JavaBean
规范的类,以学生类为例,标准代码如下:
public class Student {
// 成员变量
private String name;
private int age;
// 构造方法
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// get/set成员方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}