1. 类与对象是什么
1.1 类是什么
可以把类想象成一个百宝袋,里面装着一个事物的属性以及行为,比如一只可爱的猫猫,这只猫猫的属性——种类,也可以理解为一个事物的特征,这只猫猫的特征——颜色。那么于此同时,在这个百宝袋里面还存储着猫猫的行为,比如喵叫,猫步,吃饭等。把这些行为以及属性打包放在一起就称为类。
1.2 对象是什么
对象可以理解为世间存在的一切事物存在,譬如上面可爱的猫猫,或者猫抓老鼠中老鼠,也可以是猫粮,不管是天上飞的还是海里游的,可以是各种各样的对象。
1.3 面向对象
这里就要进入Java中的核心概念面与对象,Java是一种面向对象的语言。面向对象是一种解决问题的思想,主要依靠对象之间的交互来完成一件事情。比如说猫抓老鼠这件事情,完成这件事情,需要进行以下几步:
1.把贪睡的猫猫叫醒,告诉它捉老鼠
2.老鼠看见猫来了赶紧跑
3.猫猫展开追逐
4.成功抓获
不知道各位的猫猫有没有这么厉害呢。
这期间的对象就是猫猫,老鼠。而行为则是老鼠的逃跑,猫猫的追逐。这个过程就是通过猫猫和老鼠两个对象通过逃跑和追逐的行为进行交互来完成这个事件。我们不需要关心花了多长时间,期间发生了什么事情,只需最开始让猫猫捉老鼠,即可获得结果。
1.4 面向过程
可以与上面的面向对象做一个对比,来加深印象。在编程语言中还有一种解决问题的方法,就是面向过程,如果说面向对象是关注对象之间的交互,那面向过程则是把猫抓老鼠的过程拆分为每一步。
1.猫猫发现老鼠
2.猫猫向老鼠追去
3.老鼠赶紧逃跑
4.老鼠被抓了
在这里,注重的是猫抓老鼠的过程,少一个环节都可能不行。按照该种方式来写代码,将来扩展或者维护起来会比较麻烦。
注意:面向过程和面相对象并不是一门语言,而是解决问题的方法,没有那个好坏之分,都有其专门的应用场景。
2. 类的定义
2.1 类的定义
// 创建类 class ClassName{ field; // 字段(属性) 或者 成员变量 method; // 行为 或者 成员方法 }public class Cat { public String name; //名字 public String kind; //种类 public String color; //颜色 public void run(){ System.out.println("追逐"); } public void eat(){ System.out.println("吃猫粮"); } public void miaow(){ System.out.println("喵喵"); } }类成员变量——其中我们创建了猫这么一个类,创建了诸如名字,种类,颜色等猫猫的特征,当然这里还没赋值。我们也可以将这些特征换成专业一点的词,比如属性,以及上面提到的新概念——类的成员属性或者类的成员变量,意思是一样的,不过是换了说法
类的成员方法——随后我们在下面创建了关于猫猫的一些行为,比如追逐,吃猫粮,喵喵叫等,在这里将具体的行为代码替换成打印对应的行为,以便可读性。我们将猫猫的这些行为称之为类的成员方法
注意事项:
- 类名注意采用大驼峰命名方式
- 其中成员前的public,以及创建的成员方法为什么少了static将在后续慢慢解答
- 当定义了public的类之后,当前的文件名,必须和public的类名是一致的
- 定义类的时候,一般是一个文件中只定义一个类
- 一个文件也可以定义多个类,但是要求多个类中只能有一个设定为public
接下来演示一下注意事项下面两条的示例,便于理解
可以看到当一个文件里面出现了两个public,那么IDEA就会提示报错信息,也就是第二幅图中的红线,想消除的话,要么去掉Dog前面的public,或者把Cat前面的public移动到Dog前面。
但是如果是将Cat前面的public移动到Dog前面,需要将文件名改成Dog,正对应了注意事项的第三条,文件名需要和public的类名是一致的。
随后将文件名和public还原成Cat,进行下一小节。
3. 类的实例化
3.1 什么是类的实例化
public static void main(String[] args) { Cat cat1 = new Cat(); }需要注意的是第一个和最后一个Cat,是类名,而cat1则是这个变量名。
3.2 使用对象的成员
使用对象的成员也就是属性和方法,需要用到. 成员访问符。
public static void main(String[] args) { Cat cat1 = new Cat(); cat1.name = "小白"; System.out.println(cat1.name); cat1.eat(); }注意事项:
new关键字用于创建一个对象的实例 同一个类可以创建多个实例 使用.来访问对象中的属性和方法
4. this引用
4.1 为什么要用this引用
接下来我们通过下面的代码来认识一下,this的作用
public class Date { public int year; public int month; public int day; public void setDate(int y,int m,int d){ year = y; month = m; day = d; } public void printDate(){ System.out.println(year+"/"+month+"/"+day); } public static void main(String[] args) { Date date = new Date(); date.setDate(2024,8,10); date.printDate(); } }可以看见这个代码是简单利用了 方法把值赋给对象,然后通过方法来打印出来。
但是有一个点需要注意,这里的setDate方法,里面的成员变量与方法里的形参是不一样,刻意区分了,假如说成员变量year和形参y一样,那么这个代码还能不能运行呢?
4.2 this引用的作用
public void setDate(int year,int month,int day){ year = year; month = month; day = day; }可以看到将两者修改成一致后,我们的setDate方法并没有成功赋值,导致输出结果全是0。
因为编译器识别到year的时候,会优先把year理解为形参,而不是成员变量。所以这个代码的意思是形参赋值给形参,而不是赋值给成员变量,所以结果是0。
所以就要用到this这样的关键字,来区分形参和成员变量,用this来标明哪个是成员变量。
public void setDate(int year,int month,int day){ this.year = year; this.month = month; this.day = day; }这是一个特殊的引用,表示当前的实例的,什么是实例呢?回忆一下前面的内容,创建对象的过程叫做实例化。那当前的实例表示的是谁的呢?
public static void main(String[] args) { Date date = new Date(); date.setDate(2024,8,10); date.printDate(); }答案很简单,谁用.这个成员访问符调用的,那当前的实例就是谁的。这里date调用setDate方法,因此this表示的当前实例就是date的。
再举一个例子加深印象。
public void setDate(int year,int month,int day){ this.year = year; this.month = month; this.day = day; }public static void main(String[] args) { Date date = new Date(); date.setDate(2024,8,10); date.printDate(); Date date2 = new Date(); date2.setDate(2025,9,11); date2.printDate(); }下面是date2调用的setDate方法,因此this就是指向date2。谁调用,那么this指向的就是谁。
此外,如果方法中局部变量没有和成员变量冲突,那么这个this写不写都可以。
public void printDate(){ System.out.println(year+"/"+month+"/"+day); }4.3 this引用的特性
- this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
- this只能在"成员方法"中使用
在"成员方法"中,this只能引用当前对象,不能再引用其他对象 this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责来接收
重点解释一下第四点,如下图:
简单来说就是编译器内部把一些参数隐藏了。
5. 对象的构造及初始化
5.1 对象的初始化
我们首先了解一下什么是对象的初始化,在Java方法内部定义一个局部变量时,必须要初始化,否则会编译失败。如下图:
int num = 10; System.out.println(num);如果这时候不给这个变量赋值,且打印出来,系统就会报错。
而对象其实也是变量,Date date = new Date()这个形式里面,此处只是给date这个引用初始化了,实际上等于号后面的 new Datw()里面还包含着三个成员变量,如下方代码:
public class Date { public int year; public int month; public int day; }这三个就是创建Date这个类所创建的成员变量,可以看到并没有初始化。没有报错的原因就是后面写了一个方法给这些变量赋值。如果把赋值的方法注释掉的话,那么运行结果就是全0;
public static void main(String[] args) { Date date = new Date(); // date.setDate(2024,8,10); date.printDate(); Date date2 = new Date(); // date2.setDate(2025,9,11); date2.printDate(); } }因此更希望在对象创建的时候初始化,也就引出了构造方法。
5.2 构造方法
接下来跟随我来认识一下构造方法:
public Date(){ }形如这样的形式便是构造方法,有几个点需要注意:
- 构造方法的方法名必须跟类名一致,并且不能有返回值
- 构造方法一般不需要自己去调用,new对象的时候,由编译器自动调用的
然后我们在构造方法里面初始化一下,来看看效果
public Date(){ year = 2024; month = 8; day = 15; }这是没有参数的构造方法,也可以用有参数的构造方法:
public Date(int year,int month,int day){ this.year = year; this.month = month; this.day = day; }把参数传进去然后打印:
Date date = new Date(2025,8,16); date.printDate();根据需要写出不同的构造方法,满足不同的初始化方式。
5.2.1 构造方法的特性
名字必须与类名相同 没有返回值类型,设置为void也不行 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法) 如果用户没有显式定义,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的 一旦用户定义了其他的构造方法,编译器则不再生成 构造方法中,可以通过this调用其他构造方法来简化代码 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)
5.2.2 示例
我来示例一下第4,5个特性:
public Date(int year,int month,int day){ this.year = year; this.month = month; this.day = day; }如果在代码中只定义了有参数的构造方法,就不会自动生成无参数的构造方法,例如下面代码:
public Date(){ }编译器自动生成的构造方法一定是无参的。
// public Date(){ // year = 2024; // month = 8; // day = 15; // } public Date(int year,int month,int day){ this.year = year; this.month = month; this.day = day; }如果只保留了有参数的构造方法,而把无参数的构造方法给注释掉,那么就只支持下面的方式
如果采用无参数的构造方法,如下图:
将会报错。
因此一旦定义了其他的构造方法,编译器便不再自动生成。
接下来是第6个特性:
public Date(){ // year = 2024; // month = 8; // day = 15; this(2024,8,15); } public Date(int year,int month,int day){ this.year = year; this.month = month; this.day = day; }允许构造方法通过this调用其他的构造方法,实现代码复用的效果,如果在有参数的构造方法修改,那么无参数的构造方法不用做修改,因为是一个调用的关系。
不过有一点需要注意,上面的代码中,因为把前面的赋值给注释掉了,因此this是在第一行的,如果不是请看下图:
那么下面的this将会报错,如果在this之前还放了别的代码是不可以的!
另外调用是不能形成循环的,比如A调用B,B是不能再调取A的!
5.3 初始化的其他方法
构造方法虽然好用,但是随着语法的发展变化,还演化出了一些其他的初始化方式
5.3.1 就地初始化
就地初始化就是在成员变量创建的时候就赋值,简单直接。
public class Date { public int year = 2024; public int month = 8; public int day = 17;无参数的构造方法可以用这种方法来代替
5.3.2 代码块初始化
代码块初始化简单来说就是用两个大括号,然后里面给成员变量赋值,也很简单。
//代码块 { year = 2024; month = 8; day = 15; System.out.println("代码块初始化"); }可以根据自己的兴趣或者需要来选择不同的初始化方法。
5.3.3 初始化的执行顺序
在代码执行过程中,需要注意三种初始化的执行顺序
- 就地初始化
- 代码块初始化
- 构造方法
按照上面序号的顺序来执行,后面的执行的逻辑会覆盖前面的。
我们可以做一个小实验来验证一下。
将构造方法的年份设置为2025,就地初始化的年份设为2024,代码块的年份初始化设置为2023,然后打印出来看执行结果的年份是什么,就知道谁是最后的赢家。
//就地初始化 public class Date { public int year = 2024; public int month = 8; public int day = 15; }//构造方法 public Date(){ year = 2025; month = 8; day = 15; } //代码块 { year = 2023; month = 8; day = 15; System.out.println("代码块初始化"); }根据执行顺序来说,大家心里的答案是什么呢,来运行看结果。
可以看到最后执行的是构造方法。
而如果我们把构造方法给注释掉了,那么最后执行的便是代码块初始化。
// public Date(){ // year = 2025; // month = 8; // day = 15; // } //代码块 { year = 2023; month = 8; day = 15; System.out.println("代码块初始化"); }6. 对象的打印
正常来说,想打印什么直接用下面的语句打印即可
System.out.println("代码块初始化");那如果我们直接打印对象呢?来试试。
System.out.println(date);打印出来以后发现结果并没有我们想象的那样,打印出年月日,而是有点类似乱码的样子。
其实直接打印对象,打印出来的是hashCode,至于这个是什么,大家可以期待后续数据结构篇的时候,为大家进行讲解。
既然没有打印出我们想要的结果,那么我们就要来解决一下问题。
我们可以转变一下想法,不可以直接打印的话,如果我们将它转换成String类型的呢?
public String toString() { return year + "-" + month + "-" + day ; }在这里我创建了一个toString的方法,年月日中间隔开的符号可以根据喜好来调
就这么一看,似乎没看出来有什么联系,我们运行看一下结果。
诶?为什么只是添加一个toString方法就能打印出来,甚至还没调用,这可能是大多数同学的疑问。我也不卖关子了,来为大家解释一下:
toString是一个特殊的方法,当打印对象时会自动调用toString方法,把对象转为String。
因此我们在写toString方法的时候,需要注意此处的写法:
方法名字固定为toString(如果是其他名字,将不会自动调用),返回值类型为String,无参数。
System.out.println(date.toString());当然各位同学如果想,也可以手动调用,不影响。
7.结语
以上就是关于类的定义,对象的构造方法等内容,我们下期再见。
ps:有不理解或者不懂的可以在评论区提出,如有看到会解答。