写在前面:做为一门面向对象的语言,我们需要明白什么是类,何为对象这些基本概念。
摘要:
- 面向对象与面向过程
- 类的定义形式与对象实例化
- this引用
- 再谈对象的初始化过程
1.面向对象与面向过程
1.1何为面向对象
java与c++做为两种较为广泛使用的编程语言,都体现了面向对象这一重要的编程思想。而Java则是一门纯面相对象的语言(Object Oriented Program,简称OOP),可能我们第一个编写的helloworld便是在一个class 类中来实现的。在面向对象的世界里,一切皆为对象。面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。用面向对象的思想来涉及程序,更符合人们对事物的认知模式。对于大型程序的设计,扩展以及维护都非常友好。
1.2面向对象与面向过程区别
在此处以生活中的一个例子来进行说明会更加真实便于理解。比如我们洗衣服,传统的方式便是面向过程的一种思想:接水–放衣服–放洗衣粉–手搓–换水–手搓–拧干–晾晒。这个方式强调的是整体走下来的流程,不能缺少任何一个环节。每个环节可以看为是一个函数。但是以此思想编写代码的缺点显而易见:
- 不同衣服洗的方式,放的洗衣粉量,拧干方式都不同
- 如果换为洗床单,洗鞋则需要大面积修改方法
按照面向对象的思想来写代码,后期的扩展与维护会比较麻烦。
但是现代洗衣服的方式则可以视为面向对象的思维方式。人将衣服放进洗衣机,再放入洗衣粉,启动机器即可。我们不会去关注衣服是如何具体被清洗的过程。整个过程我们大致可分为两步。第一步我们需要从此行为中抽离出所有的对象。例如洗衣服可以抽象出四个对象:人,洗衣机,衣服和洗衣服。第二步依靠对象之间的交互我们完成了洗衣服。
面向对象就好比盖浇饭,而面向过程则更像蛋炒饭
p.s.:面向对象与面向过程并不是一门语言,而是不同的思维与解决问题的方法,没有优略之分,应用于不同的问题与需求。
2.类的定义形式与对象实例化
2.1初识类
面向对象的语言核心便是对象。生活中的万物(对象)当我们想使用时,计算机并不会识别生活中的各种对象(物体)。因此我们需要使用编程语言将对象抽象出来描述给计算机。因此需要类。**类是对一个实体(对象)进行抽象描述的。**包括其属性和功能。
2.2类的定义
java中定义类的格式如下:修饰符 class classname(类名)语法如下:
public class Student{
public String name;//属性:用来描述类的,也成为类的成员变量
public String gender;
public short age;
public void goClass(){
System.out.println(name+"按时上课");//行为:用来说明类具有哪些功能,也成为类的成员方法
}
public void doHomeworrk(){
System.out.println(name+"完成作业");
}
}
注意:
- 一般一个文件中只定义一个类,类名采用大驼峰命名。
- main方法所在的类中一般要使用public修饰
- public修饰的类必须和文件名相同,不要轻易去修改名称。
2.3类的实例化
定义了类,便是定义了一种新的数据类型,就像基本类型int,double一样。比如上面的student类便是一种数据类型。可以用它来定义变量,用类来定义变量(创建对象)的过程称为类的实例化。在java中用new关键字来实例化对象。此处我们用上文的student类做为实例来实例化对象。代码如下:
public class test{
public static void main(String[] args){
Student a=new Student();
a.name="jack";
a.age=18;
a.goClass();
a.doHomework();
Student b=new Student();
b.name="alice";
b.age=19;
b.goClass();
b.doHomework();
}
}
//输出结果
jack按时上课
jack完成作业
alice按时上课
alice完成作业
- 使用**.**来访问对象的属性和方法
- 同一个类可以创建多个实例对象
类和对象的说明
- 类只是一个类似模型的东西是对一个实体的抽象描述,限定类的属性与功能。
- 类是一种自定义的数据类型,用类自定义的变量称为对象。对象是一个个客观存在的实体。
- 一个类可以创建多个对象,每个对象都需要占用一定的物理空间来存储自身的成员变量,但是类不会占用物理空间去存储。
- 类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。
3.this引用
3.1为何有this引用?
如下有一个例子:
public class Date {
public int year;
public int month;
public int day;
public void setDay(int year, int month, int day){
year = year;
month = month;
day = day;
}
public void printDate(){
System.out.println(year + "/" + month + "/" + day);
}
public static void main(String[] args){
Date a=new Date();//定义两个日期类的对象
Date b=new Date();
//设置对象的成员变量值
a.setDay(2021,9,15);
b.setDay(2021,9,16);
//打印对象中的日期内容
a.printDate();
b.printDate();
}
}
//输出结果
0/0/0
0/0/0
在上述代码中我们可以发现两个问题:
- 形参名与成员变量值相同。在日期类中的setday成员方法中,形参为三个整形变量year,month,day与成员变量重名,那么赋值时形参与成员变量该如何赋值?
- **成员方法是如何知道哪个对象在调用它?**在上例中我们定义了两个对象a和b,分别调用了打印日期方法,并且得出的打印结果是错误的,那么printdate方法是如何识别哪个对象在调用它并分配相应对象的数据内容呢?
要回答这两个问题需要this引用出场了。
3.2什么是this引用
java编译器给每个“成员方法“增加了一个隐藏的引用类型参数,该引用参数指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
我们依然可以采用上面的日期类进行举例:
public class Date {
public int year;
public int month;
public int day;
public void setDay(this int year, int month, int day){
this.year = year;//此处需手动添加this引用
this.month = month;
this.day = day;
}
public void printDate(){
System.out.println(year + "/" + month + "/" + day);
}
this引用是编译器自动添加的,用户在实现代码时一般不需要显式给出。this引用的是调用该成员方法的对象。
可见this引用的值即为该对象a中的地址值。通过this即可访问到该对象。因此上文中的第一个问题在形参与成员变量重名时,只需在成员变量前加上this.即可访问到该对象的属性,日期结果也可以成功打印。
3.3this引用的特性
- 关于this的类型:对应于所属类的类型引用,哪个对象调用即为那个对象所在类的引用类型。如a为Date类,则this为Date类型引用
- this只能在非静态成员方法中使用。
- 在非静态成员方法中,this只能引用调用该方法的对象,不能引用其他对象,具有final属性,如果引用其他对象编译会报错。
- this是成员方法参数中第一个隐藏的参数,编译器会自动传递,在方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责接收,我们在写代码时可以不用显式写出this(上述代码已证明),但是加上也是可以的。如下例代码:
public class Date {
public int year;
public int month;
public int day;
public void setDay(Date this,int year, int month, int day){
this.year = year;//此处需手动添加this引用
this.month = month;
this.day = day;
}
public void printDate(Date this){
System.out.println(this.year + "/" + this.month + "/" + this.day);
}
}
接下来我们从字节码层面进行简单证明:
此外,this引用不能为空,不能指向空引用(编译可以通过,无语法问题,但是运行会出现异常)。
4再谈对象的初始化过程
4.1如何初始化对象
我们知道在java内部定义一个局部变量时必须要初始化,否则会编译失败。那么如果是对象的话我们可以采用上述例子来初始化:
public static void main(String[] args){
Date a=new Date();//定义两个日期类的对象
Date b=new Date();
//设置对象的成员变量值
a.setDay(2021,9,15);
}
通过调用成员方法来给对象的成员变量赋值。但是这样显然十分麻烦,并且我们在之前的代码中会发现成员变量没有赋初值时依然可以被使用。
4.2构造方法
4.2.1概念
构造方法(构造器)是一个特殊的成员方法,格式也是固定的,名字必须与类名相同,不能写返回值类型,编译器自动调用,并且在整个对象生命周期中只调用一次。构造方法作用是对对象的成员变量初始化,不负责给对象开辟空间。参考如下代码:
public class Date{
public int year;
public int day;
public Date(int y,int d){
year=y;
day=d;
System.out.println("调用成功");
}
public static void main(String[] args){
Date a=new Date();
}
}
//输出结果
调用成功
构造方法的返回值类型不能书写,也不能为void。
4.2.2构造方法特性
构造方法可以重载,用户可根据需求不同来提供不同参数的构造方法。代码如下:
public class Date{
public int year;
public int month;
public int day;
//无参构造方法
public Date(){
System.out.println("test1");
}
//两个参数的构造方法
public Date(int a,int b){
System.out.println("test2");
}
//三个参数的构造方法
public Date(int a,int b,int c){
System.out.println("test3");
}
}
上述三个构造方法即构成了方法重载,用户在使用时可根据不同的参数需求进行调用。如果用户没有显式定义,编译器会自动生成一个默认的构造方法,且为无参类型,我们可以从字节码层面,利用jclasslip插件进行证明:
我们可以看到在方法中多出了一个init,我们并未定义它,并且在此方法的局部变量表中只有一个隐藏的参数this。因此我们知道了编译器会将构造方法统一命名为init,并且该构造方法是无参的,得以证明无构造方法时编译器会自动生成。 然而一旦用户自己定义了构造方法,编译器则不再生成。
在构造方法中可以通过this来调用其他的构造方法来简化代码:
public class Date{
public int year;
public int month;
public int day;
public Date(){
this(2001,1,1);
}
public Date(int year,int month,int day){
this.year=year;
this.month=month;
this.day=day;
}
}
需要注意两点:
- this()必须是构造方法中的第一条语句。
- this()不能成环,比如两个构造方法不能互相使用this。
4.3默认初始化
上文中提到为什么成员变量在使用前可以不用初始化,我们需要明白new这个关键字背后的一些含义:
Date a=new Date();
虽然这只是一条简单的语句,但是在JVM层面却需要做很多事情,以下做一个简单的介绍:
- 检测对象对应的类是否加载了,如果没有加载则加载。
- 为对象分配内存空间。
- 处理并发安全问题(如多个线程同时申请对象,要保证空间不冲突)。
- 初始化所分配的空间,即对象空间被申请好后,对象中包含的成员变量已经设置好初值。如下表
数据类型 | 默认值 |
---|---|
byte | 0 |
char | ‘\u0000’ |
short | 0 |
int | 0 |
long | 0L |
boolean | false |
float | 0.0f |
double | 0.0 |
reference | null |
- 设置对象头信息。
- 调用构造方法。
4.4就地初始化
除此之外,我们还可以在声明成员变量时就给出其初始值:
public class Date {
public int year=1999;
public int month=1;
public int day=1;
public Date(){
}
public Date(int year,int month){
}
public void printDate(Date this){
System.out.println(this.year + "/" + this.month + "/" + this.day);
}
public static void main(String[] args){
Date a=new Date();
a.printDate();
}
}
//输出结果
1999/1/1
我们可以从字节码层面来看这一过程:
代码编译完成后,所有初始化成员变量的语句会添加到每个构造方法的第一句处。
当然java类与对象的内容还有很多,此处只是第一部分粗略的概括。后续我也会继续总结并与大家分享。谢谢大家的阅读与点赞。
作者:端履门没有门