前言
我们知道Java是一门面向对象的编程语言,封装、继承和多态是面向对象编程语言的三大特征。今天,我们在这里详解类和对象,对三大特征之一的封装进行简单的入门。希望能抛砖引玉,也欢迎各位大佬对文章错误的部分斧正。
什么是面向对象
我们在日常都说面向对象,面向过程。这部分作为文章的引子,也是为了文章知识点的完整性,我们在这里还是要简单的介绍一些两者,也简单的进行区分。下面我们都拿“洗衣服”举例
面向过程
面向过程的核心逻辑类似于“手洗”,洗衣服是一种洗法,洗鞋子又是另外一种洗法,对于不同的对象我们具有针对性的洗法,注重于过程,少任何一环都完成不了洗衣服这个功能。显然这种思想增加了维护的难度,想拓展功能也并不容易。C语言就是一种经典的面向过程的语言。
面向对象
面向对象的核心逻辑类似于“洗衣机”,我们并不关心衣服是怎么被洗干净的,只需要把衣服扔进去,衣服就会被洗干净,只需要能利用它提供的接口实现功能即可。在面向对象的世界里,一切皆对象,每个对象有自己的属性和功能,对象和对象的不断交互就能实现不同的功能。我们今天要介绍的Java就是一门经典的面向过程的语言。
注意,面向过程和面相对象并不是一门语言,而是解决问题的方法,没有那个好坏之分,都有其专门的应用场景。
类和对象的关系
计算机并不认识我们所说的对象是啥,我们就需要用基本数据类型和引用数据类型这些计算机能认识的东西向计算机描述对象的属性和功能(方法)。描述的过程就是我们所说的定义一个类,我们再可以通过类来实例化(new)一个对象,这样计算机就能认识这个对象,知道它有啥属性,有啥功能(方法),并且在计算机的世界里创造出这样一个对象,占据真实的物理存储空间。
类的定义格式
// 创建类
class ClassName{
field; // 字段(属性) 或者 成员变量
method; // 行为 或者 成员方法
}
class为定义类的关键字,ClassName为类的名字,{}中为类的主体。
类中包含的内容称为类的成员。属性主要是用来描述类的,称之为类的成员属性或者类成员变量。方法主要说明类具有哪些功能,称为类的成员方法。
举个例子——定义一个描述学生的类
class Student {
//成员变量/属性
public String name; //学生的姓名
public String id; //学生的学号
public int age; //学生的年龄
//成员方法
public void sleep() {
System.out.println(this.name + ":sleep");
}
public void work() {
System.out.println(this.name + ":work");
}
}
一些注意事项
类名注意采用大驼峰定义。后面会解释public(访问限制修饰符) 和 static(静态变量关键字),这里先不用关注。一般一个Java文件当中只定义一个类。main方法所在的类一般要使用public修饰。public修饰的类必须要和文件名相同。不要轻易去修改public修饰的类的名称,如果要修改,通过开发工具修改。
关于最后一点,通过开发工具修改public修饰的类名,我们在这里进行简单的介绍。
重命名前:
重命名:
重命名后 :
重命名后我们可以观察到以下变化
通过类来实例化对象
通过前面的描述,我们已经成功得到了一个类,但它依然只是一种自定义的类型,类似于int ,double等。下面我们将介绍如何通过类来实例化一个对象(new关键字)。
我们还是拿前面学生的例子来举例
class Student {
//成员变量/属性
public String name; //学生的姓名
public String id; //学生的学号
public int age; //学生的年龄
//成员方法
public void sleep() {
System.out.println(this.name + ":sleep");
}
public void work() {
System.out.println(this.name + ":work");
}
public static void main(String[] args) {
Student stu1 = new Student(); //这里实现实例化对象
}
}
下面,我们就可以给对象赋值,进行一些简单的交互
class Student {
//成员变量/属性
public String name; //学生的姓名
public String id; //学生的学号
public int age; //学生的年龄
//成员方法
public void sleep() {
System.out.println(this.name + ":sleep");
}
public void work() {
System.out.println(this.name + ":work");
}
//使用 . 来访问对象中的属性和方法
public static void main(String[] args) {
//stu1是对象变量名,我们可以通过一个类定义出多个对象
//new是实例化关键字
//Student是类名
Student stu1 = new Student();
//对stu1这个对象的属性赋值
stu1.name = "zheng";
stu1.age = 18;
stu1.id = "20050612";
//对stu1这个对象的成员方法的简单应用
stu1.sleep();
stu1.work();
}
}
再谈类和对象
- 类只是一个模型一样的东西,用来对一个实体进行描述,限定了类有哪些成员.
- 类是一种自定义的类型,可以用来定义变量.
- 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。
this引用
引入
我们通过以下的实例来引入我们要讲的this关键字
显然,这里没有被成功赋值的原因是变量优先原则,两个 name 计算机均解析成为形参的变量名,没有与对象的成员变量产生联系。而 this 就是产生这种关系的桥梁。
我们在看一种情况
this 的功能
this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。这里完全是由编译器自动完成,可以说this指向了调用成员方法的对象,并且作为第一个隐式参数传递给方法,方法由此可以准确的调用对象的属性值。
我们不难发现,this 和 调用方法的对象是同一指向,这也印证了我们前面的理论。
this引用的是调用成员方法的对象
一些注意事项
- this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
- this只能在"成员方法"中使用
- 在"成员方法"中,this只能引用当前对象,不能再引用其他对象
- this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责来接收
- 我们推荐在成员方法中,凡是要用到成员属性的地方,都加上this修饰,能加都要加,为了代码的准确性考虑。
访问限定修饰符
我们先填下前面埋下的坑,也解释一些为啥在main函数内通过对象回访成员属性并且赋值的初始化办法,暴露度太高了,不安全。
我们发现里面有些概念我们不了解——啥是包,子孙类有啥,构造函数又又又是啥?下面我们就简单的介绍一下包,构造函数下面会有一整块内容来介绍,protected主要是用在继承中,以后有机会会在继承的部分介绍。
啥是包?
包其实就是文件夹,为了便于我们管理文件,一般使用公司域名的倒叙。我们这里用“www.zheng.com”的域名举例
对应到文件资源管理器:
创建一个包:
包的一些设置,展开or平铺
导包
更多的时候,我们需要导入Java原生定义好的功能,就需要导入方法所在的包。这里我们以Scaner 举例
对象的构造及其初始化
前面我们介绍了一种在main函数内通过对象回访成员属性并且赋值的初始化办法,但这种办法代码的暴露度太高了,不安全。下面我们将再介绍几种构造的初始化的办法。
就地初始化
这种初始化的办法最为简单,只需要再定义成员属性的时候直接赋值,这种办法虽然损失灵活度,但在实际业务中可以作为默认值出现
class Student {
//成员变量/属性
public String name = "zheng"; //学生的姓名
public String id = "20050612"; //学生的学号
public int age = 18; //学生的年龄
//成员方法
public void sleep() {
System.out.println(this.name + ":sleep");
}
public void work() {
System.out.println(this.name + ":work");
}
}
构造方法
构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次。
class Student {
//成员变量/属性
public String name; //学生的姓名
public String id; //学生的学号
public int age; //学生的年龄
//成员方法
public void work() {
System.out.println(this.name + ":work");
}
//无参构造方法!!!!!!
public Student() {
}
public static void main(String[] args) {
}
}
构造方法的作用就是对对象中的成员进行初始化,并不负责给对象开辟空间。
名字必须与类名相同
没有返回值类型,设置为void也不行
创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次
构造方法可以重载(用户根据自己的需求提供不同参数的构造方法),还是以上面说的类举例
//无参构造方法
public Student() {
}
//全参构造方法
public Student(String name, String id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
如果用户没有写任何构造方法,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的。一旦定义了任何一个构造方法,编译器不在提供。可以了解为救急不救穷。这里就解释了为什么我们在前面没有写构造方法,编译器依然运行通过。
构造方法中,可以通过this调用其他构造方法来简化代码,语法形式如下。通过无参构造方法调用有参的构造方法。this(...)必须是构造方法中第一条语句,,不能形成环,也不能自己调用自己
//无参构造方法
public Student() {
this(name,id,age)//这里代表参数名,实际上应该传递具体的值,这里只做演示
}
//全参构造方法
public Student(String name, String id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
快速生成代码
IDEA为我们提供了快速生成代码的快捷键,我们也来了解一下。
这几种方法的使用方法相似,这里只介绍构造函数的生成,其他的生成方法相同
封装入门
面向对象程序三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性。何为封装呢?简单来说就是套壳屏蔽细节。
举个例子,我们的计算机是一个复杂对象,但它对外只展示开关机键,屏幕,键盘,以及各种接口实现各种功能,将功能实现的方法封装在内部。用户只需要准确的调用接口就可以,不需要知道内部是如何实现的,这就是一种封装。封装的出现也极大简化了代码调用的难度,是有利于编程的。
static修饰成员变量
在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对
象,是所有对象所共享的。
-
不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
- 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
- 类变量存储在方法区当中
- 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)
class Student {
//成员变量/属性
public String name; //学生的姓名
public String id; //学生的学号
public int age; //学生的年龄
//静态定义一个班级
public static String class1;
//注意 class是关键字,不能作为变量名
//成员方法
public void sleep() {
System.out.println(this.name + ":sleep");
}
public void work() {
System.out.println(this.name + ":work");
}
}
所有学生都是一个班的,可以定义一个静态变量,存放班级的属性,这个属性属于学生这个类,不属于任何一个对象。
public static void main(String[] args) {
Student.class1 = "class101";//赋值
System.out.println(Student.class1);//使用
}
这里是一种就地初始化static的办法,后面会在介绍一种静态代码块初始化的办法。
static修饰成员方法
这部分和修饰成员属性类似,需要注意以下几点
不属于某个具体的对象,是类方法
可以通过对象调用,也可以通过类名.静态方法名(...)方式调用,更推荐使用后者
不能在静态方法中访问任何非静态成员变量
静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用
代码块
定义在类中的代码块(不加修饰符)。也叫:实例代码块。构造代码块一般用于初始化实例成员变量。
语法结构如下
{
this.name = "zheng";
this.id = "20050612";
this.age = 18;
}
静态代码块
使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量。
语法结构如下
static {
class1 = "class103";
}
静态代码块不管生成多少个对象,其只会执行一次
静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)
实例代码块只有在创建对象时才会执行
创建对象的执行顺序
- 分配内存空间
- 调用静态代码块(永远只加载执行一次)
- 调用代码块(创建一个对象调用一次)
- 调用构造方法
- 创建完成
对象的打印
前面我们介绍了生成toString,可以实现传递对象名打印属性值(可自定义打印内容和格式)。否则打印对象名是对象的“地址”(Java没有地址的概念,只有引用,这里说地址是为了方便了解)。具体原理是因为打印对象名的时候是调用了默认的toString,我们自己实现一个即会调用我们写的toString,以实现快速打印的功能。
如果想要默认打印对象中的属性该如何处理呢?答案:重写toString方法即可。
重写前
public class Person {
String name;
String gender;
int age;
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public static void main(String[] args) {
Person person = new Person("Jim", "男", 18);
System.out.println(person);
}
} // 打印结果:day20210829.Person@1b6d3586
重写后
public class Person {
String name;
String gender;
int age;
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
@
Override
public String toString() {
return "[" + name + "," + gender + "," + age + "]";
}
public static void main(String[] args) {
Person person = new Person("Jim", "男", 18);
System.out.println(person);
}
} // 输出结果:[Jim,男,18]
结语
到了文章末尾一下子还不知道如何作结,作为笔者第十篇博客,也是笔者截至目前花了最长时间,最耐心的博客,我记得它配的上。写Java类和对象的文章对笔者也是挑战,作为初学者里面肯定讲了有很多不足或者有误的地方,但最好的输入就是输出,也欢迎大家交流指正。
笔者作为编程世界的小比特,本着计算机世界最最小的初心出发,从C语言到现在的Java,我觉得我还在坚持。
希望努力能被看见
好了
以上便是今天的全部内容。如果有帮助到你,请给我一个免费的赞。
因为这对我很重要。
编程世界的小比特,希望与大家一起无限进步。
感谢阅读!
恭敬在心,不在虚文