一、java是面向对象的编程语言
首先一般的编程语言有两种,一种是面向对象,一种是面向过程。前者更加关注代码中对象与对象之间关系与协作,而后者更加注重代码的执行过程。
举个例子
传统的方式:注重的是洗衣服的过程,少了一个环节可能都不行。而且不同衣服洗的方式,时间长度,拧干方式都不同,处理起来就比较麻烦。如果将来要洗鞋子,那就是另一种放方式。
按照该种方式来写代码,将来扩展或者维护起来会比较麻烦。
而现代洗衣服大家都用洗衣机,我们将洗衣服的步骤交给了洗衣机,我们更关心的是我们与洗衣机之间的交互的过程。
以面向对象方式来进行处理,就不关注洗衣服的过程,具体洗衣机是怎么来洗衣服,如何来甩干的,用户不用去关心,只需要将衣服放进洗衣机,倒入洗衣粉,启动开关即可,通过对象之间的交互来完成的。
面相对象程序设计关注的是对象,而对象是现实生活中的实体,比如:洗衣机。但是洗衣机计算机并不认识,需要开发人员告诉给计算机什么是洗衣机。
比如
上图就是对洗衣机简单的描述,该过程称为对洗衣机对象(实体)进行抽象(对一个复杂事物的重新认知),但是这些简化的抽象结果计算机也不能识别,开发人员可以采用某种面相对象的编程语言来进行描述,比如:Java语言。
二、类的定义
上述对于洗衣机对象的一些属性的描述就是在定义一个类
类是用来对一个实体(对象)来进行描述的,主要描述该实体(对象)具有哪些属性(外观尺寸等),哪些功能(用来干啥),描述完成后计算机就可以识别了。
java中定义类时需要用到class关键字,具体语法如下
//创建类
classClassName{ field; //字段(属性)或者成员变量 method; //行为或者成员方法
}
类中包含的内容称为类的成员。属性主要是用来描述类的,称之为类的成员属性或者类成员变量。方法主要说明类具有哪些功能,称为类的成员方法
比如说定义人这样一个类(在Person.java文件里)
public class person{public String name;//定义一个名字属性public int age;//定义一个年龄属性public void eat(){System.out.println("干饭!");}public void sleep(){//定义了一个睡觉行为(方法)System.out.println("睡觉");}
}
采用Java语言将洗衣机类在计算机中定义完成,经过javac编译之后形成.class文件,在JVM的基础上计算机就可以识别了。
注意:
一般来说一个java文件只写一个类(只能有一个public类)。
public修饰的类必须要和文件名相同。比如创建时的文件名是helloworld.java文件,那么这个文件的类在创建的时候就是public class helloworld{}
类的命名采用大驼峰定义的风格
成员前写法统一为public
不要轻易去修改public修饰的类的名称,如果要修改文件名,通过开发工具修改
2.1类的实例化
定义了一个类,就相当于在计算机中定义了一种新的类型,与int,double类似,只不过int和double是java语言自带的内置类型,而类是用户自定义了一个新的类型,比如上述的:Person类。有了这些自定义的类型之后,就可以使用这些类来定义实例(或者称为对象)。
类的实例化需要通关键字new,并且类实例化化后就是我们需要的对象了。
比如在helloworld.java 文件中
public class helloworld {public static void main(String[] args) {Person person1 = new Person();//通过new来实例化类,创建了对象person1person1.name = "白线";//对类的属性进行访问赋值person1.age = 15;Person person2 = new Person();//通过new来实例化类,创建了对象person2person2.name = "黑线";//对类的属性进行访问赋值person2.age = 16;}
}
注意
1.new 关键字用于创建一个对象的实例.
2.使用 . 来访问对象中的属性和方法.
3.同一个类可以创建多个个实例.
2.2类的存储
再来说明一下类创建后分别是存在哪一片内存区域的
首先类在创建后只是一些信息,存在JDK内存的方法区,然后在实例化时,执行Person person1 = new Person();
,会在栈区创建一个局部变量person1(又叫对象名),这个变量的类型是Person类型(其实就是结构体类型)在堆区开辟一片存储空间,这片存储空间的大小就是这个类定义的时候的大小。然后person1里面存的就是堆中那片区域的地址。在java 中这叫做该引用指向这个对象。person1.name = "白线"; person1.age = 15;
就是在堆区里面对name属性(成员)赋值白线,对age属性(成员)赋值15。后面person2也是一样。
实际是我们在创建一个字符串并给字符串赋值的时候,其实底层也是类的实例化
public class helloworld {public static void main(String[] args) {String str1 = "abcde";String str2 = new String("abcde");//这两个是一样的}
}
此外,还有一点要说明,在定义类的时候,成员变量是并没有赋值的,但是java并么有报错,这是因为编译器给了他们默认的值,如果是引用类型默认为null,如果是int float long等默认是0,boolean的默认值是false,char 的默认值是’\u0000’.
2.3this引用
SE.09.00.28.55
我们先来 看一个代码
public class Day_Date {public int year;public int month;public int day;//定义三个成员变量public void set_Date(int y,int m,int d){year = y;month = m;day = d;}public void print(){System.out.println(year+"年"+month+"月"+day+"日");}public static void main(String[] args) {Day_Date date1 = new Day_Date();date1.set_Date(2022,4,22);date1.print();Day_Date date2 = new Day_Date();date2.set_Date(2022,4,22);date2.print();Day_Date date3 = new Day_Date();date3.set_Date(2022,4,22);date3.print();}
}
结果是
但是如果我现在将set_date中的形参改为year month day 这时会出现 什么情况呢?
public class Day_Date {public int year;public int month;public int day;//定义三个成员变量public void set_Date(int year,int month,int day){//形参变量名变成year,month,dayyear = year;month = month;day = day;}public void print(){System.out.println(year+"年"+month+"月"+day+"日");}public static void main(String[] args) {Day_Date date1 = new Day_Date();date1.set_Date(2022,4,22);date1.print();Day_Date date2 = new Day_Date();date2.set_Date(2022,4,22);date2.print();Day_Date date3 = new Day_Date();date3.set_Date(2022,4,22);date3.print();}
}
此时会发现,打印出来全是0,这是因为
public void set_Date(int year,int month,int day){//形参变量名变成year,month,dayyear = year;month = month;day = day;}
编译器没法分清year,month ,day到底是什么了,是形参还是成员变量。
同时我们要知道这个代码的执行顺序是现在方法区里创建好Day_Date类,然后在栈区创建第一个对象(引用)date1并指向堆区里面的存储空间,按里面有初始化过的成员变量。但是我们在调用成员方法set_Date的时候,我们没有传任何有关于第一个对象的引用或者是地址,编译器怎么知道第一个set_Date是里面传输的实参在第一个对象的堆区空间里。后面的对象调用成员方法也是这样。
这是因为编译器省略了一个东西就是this引用
this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
所以我们写代码时,遇到成员方法需要用到调用成员变量时,最好在成员变量前加上this.这个更好理解
public class Day_Date {public int year;public int month;public int day;//定义三个成员变量public void set_Date(int year,int month,int day){//形参变量名变成year,month,daythis.year = year;this.month = month;this.day = day;}public void print(){System.out.println(this.year+"年"+this.month+"月"+this.day+"日");}public static void main(String[] args) {Day_Date date1 = new Day_Date();date1.set_Date(2022,4,22);date1.print();Day_Date date2 = new Day_Date();date2.set_Date(2022,4,22);date2.print();Day_Date date3 = new Day_Date();date3.set_Date(2022,4,22);date3.print();}
}
至此我们在梳理一下这个内存的过程,创建类的过程就不说了,在执行Day_Date date1 = new Day_Date();
时,系统会在栈区创建一个局部变量(或者叫对象)命名为data1,在堆区开辟一块存储空间用于存放输入data1对象所有变量,data1是引用类型,他存的是一个地址,指向的就是这块堆区。然后开始执行 date1.set_Date(2022,4,22);
调用方法区的方法,然后将2022,4,22以及data1所在的堆区地址(也就是this这个引用类型)作为实参传入set_Date方法中,执行ste_Date方法。进入这个方法后,编译器区分俩个year的方法是编译器知道等号左边的year的是被this这个引用所指向的,也就是data1这个成员的year,而等号右边的year是临时开辟的形参,用于接收2022的year。
下面这个图就很好的说明了这一点
publicstaticvoidmain(String[]args){Date d=newDate();d.setDay(2020,9,15);d.printDate();
}
注意:
- this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类
- this只能在"成员方法"中使用,同时不能用于静态的成员方法(这个后面会说)
- 在"成员方法"中,this只能引用当前对象,不能再引用其他对象
- this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法类也是可以通过编译的
2.4构造方法
构造方法是java中一类比较特殊的方法。
我们在上面的代码中初始化用的是一个set_Date的成员方法。但是要知道我要是每次生成一个对象都要调用一个成员方法,这多麻烦啊!所以有没有简单一点的的方法呢!答案是有的,就是构造方法
构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次。
具体来说
在执行Day_Date date1 = new Day_Date();
时,也就是实例化对象时,就会调用data1对象的构造方法。
构造方法非常特殊,没有返回值,(不是返回值为void,单纯就是 没有返回值,定义的时候也不写)方法名也必须和类名一致,参数列表可有可没有。
public class Day_Date {public int year;public int month;public int day;//定义三个成员变量public void set_Date(int year,int month,int day){//形参变量名变成year,month,daythis.year = year;this.month = month;this.day = day;}public void print(){System.out.println(this.year+"年"+this.month+"月"+this.day+"日");}public Day_Date(){//定义一个构造函数,这个函数不用在主函数里面写明调用,java也会调用,// 如果你不写构造函数,java也会直接提供一个不带任何参数和函数体的构造方法System.out.println("这是构造函数!");}public static void main(String[] args) {Day_Date date1 = new Day_Date();date1.set_Date(2022,4,22);date1.print();}
}
构造方法的特性
- 名字必须与类名相同
- 没有返回值类型,设置为void也不行
- 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)
- 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)
所以我们初始化对象,完全可以从构造方法的角度,直接传参就像下面这个例子一样
public class Day_Date {public int year;public int month;public int day;//定义三个成员变量// public void set_Date(int year,int month,int day){//形参变量名变成year,month,day
// this.year = year;
// this.month = month;
// this.day = day;
// }public void print(){System.out.println(this.year+"年"+this.month+"月"+this.day+"日");}public Day_Date(int year,int month,int day){//定义一个构造函数,这个函数不用在主函数里面写明调用,java也会调用,// 如果你不写构造函数,java也会直接提供一个不带任何参数和函数体的构造方法this.year = year;this.month = month;this.day = day;System.out.println("这是构造函数!");}public static void main(String[] args) {Day_Date date1 = new Day_Date(2022,4,22);//构造方法定义了形参和函数体后,直接赋值初始化就可以。date1.print();}
}
一定要注意,只要我们自己写了构造方法,那么java就会执行我们写的构造方法,只有我们没有写任何的构造方法时,java才会执行一个没有任何参数、返回值、函数体的构造方法。
这里有个问题需要解释一下
我们在调用构造函数是使用里this这个引用类型,但是this这个引用类型指向的是这个对象,但是我调用构造函数正是为了生成这个对象啊!这好像变成了我为了生成这个对象而引用了这个对象?
实际上,构造方法的作用就是对对象中的成员进行初始化,并不负责给对象开辟空间。也就是说对象的生成分为两步,第一步是开辟内存空间,这个是new来完成的,内存空间一旦开辟了就可以引用了,而第二步才是调用构造方法进行初始化,实际就是对内存空间内的成员变量进行赋值。
构造方法中,可以通过this调用其他构造方法来简化代码
public class Date {public int year;public int month;public int day;
// 无参构造方法--内部给各个成员赋值初始值,该部分功能与三个参数的构造方法重复
// 此处可以在无参构造方法中通过this调用带有三个参数的构造方法
// 但是this(1900,1,1);必须是构造方法中第一条语句public Date(){
//System.out.println(year); 注释取消掉,编译会失败this(1900, 1, 1);
//this.year = 1900;
//this.month = 1;
//this.day = 1;}
// 带有三个参数的构造方法public Date(int year, int month, int day) {this.year = year;this.month = month;this.day = day;}
}
这里背后的逻辑在主函数初始化对象先调用了无参的函数构造方法,然后执行this(1900, 1, 1);
此时系统会自动调用另外一个重载函数:带有三个参数的构造方法
从而完成初始化。
但是需要注意的是
1.必须放到this(1900, 1, 1);
第一行
2.只能在构造方法内部用
3.不能形成环
public Date(){
this(1900,1,1);
}
public Date(int year, int month, int day) {
this();
}
/*
无参构造器调用三个参数的构造器,而三个参数构造器有调用无参的构造器,形成构造器的递归调用
编译报错:Error:(19, 12) java: 递归构造器调用
*/
2.5 默认初始化
public class Date {
public int year;
public int month;
public int day;
public Date(int year, int month, int day) {
// 成员变量在定义时,并没有给初始值, 为什么就可以使用呢?
System.out.println(this.year);
System.out.println(this.month);
System.out.println(this.day);
}
public static void main(String[] args) {
// 此处a没有初始化,编译时报错:
// Error:(24, 28) java: 可能尚未初始化变量a
// int a;
// System.out.println(a);
Date d = new Date(2021,6,9);
}
}
在程序层面只是简单的一条语句,在JVM层面需要做好多事情,下面简单介绍下:
-
检测对象对应的类是否加载了,如果没有加载则加载
-
为对象分配内存空间
-
处理并发安全问题
比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突 -
初始化所分配的空间
即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值,比如:
-
设置对象头信息(关于对象内存模型后面会介绍)
-
调用构造方法,给对象中各个成员赋值
在说完构造方法后,我们来说一说面向对象程序的三大特性