一、面向对象引入
我们的客观世界是由两种东西所组成:生活在客观世界中的个体(客体,或物体)以及个体之间的联系。正是由于现实世界中的个体们“各司其职”, 直接或间接的“相互协作”,才保证了这个世界的正常有序地流转。
面向对象的程序世界和现实世界也极其相似:运行中的程序,由多个个体(也就是对象)组成;运行中的个体,需要相互协作,共同完成程序的功能。
二、面向对象基本概念
类比于现实世界中的个体(客体),运行中的面向对象程序也是由一个一个的对象(个体)组成。所以,要想写出面向对象的程序,必须首先学会构建面向对象程序的基本组成单位——对象。
1.如何描述一个对象呢?
对象有很多,我们不可能一一去描述,所以我们从一般个体中抽取出来共性,即对某种类型个体的一般性描述(属性和行为),其实就是类。
对象:是由属性和行为两部分数据组成。
类是用来(向jvm)描述对象的属性和行为。
类:同种物体(对象)在属性和行为上的集合与抽象。
类通过定义成员变量描述对象的属性,通过定义成员方法描述对象的行为。
2.类和对象,以及对象与对象之间的关系
类 VS 对象:
类:描述同种类型的对象共有的属性和行为,但是类描述的是对象有哪些属性,有哪些行为。
对象:对象属性的具体取值,类并没有规定,也就是说,对象属性的具体取值,是由每一个具体的对象自己来决定的。
对象 VS 对象:
a: 不同类型的对象,对象可能具有不同的属性和行为。
b:同种类型的对象,也有区别,区别就在同种类型的不同对象,它们的属性取值可以不同。
三、面向对象基本语法
1.Java语言中定义类,也是从这两个方面入手:
- 成员变量 就是事物的属性;
- 成员方法 就是事物的行为;
Java中定义类其实就是定义类的成员(成员变量和成员方法)。
2.成员变量 VS 局部变量:
在学习类(class)时引入了一种和之前不太一样的变量——成员变量。而之前我们在代码中所使用的所有的变量都是局部变量。
哪些变量是局部变量? 方法中定义的变量和方法的形式参数。
局部变量和成员变量的比较:
-
在类中定义的位置不同:
a.局部变量定义在方法体中
b.成员变量定义在类中方法体之外。 -
在内存中的位置不同
a. 局部变量,都是存储在方法对应的栈帧中
b. 成员变量,存储在堆上,在对象的内存中 -
初始化值不同
a. 局部变量,局部变量没有天生的初始值,必须在使用之前用代码初始化变量的值
b. 成员变量,成员变量存储在堆上,堆上的变量天然有默认初值 -
生命周期不同
a.因为局部变量是存储在栈帧中的,因此,随着栈帧的存在而存在,随着栈帧的销毁而销毁
b.因为成员变量存储在堆上对象的内存空间中,成员变量随着对象的存在而存在,随着对象的销毁而销毁(当对象无法被使用的时候,也就是说没有引用变量指向该对象,对象变成垃圾)
成员方法: 只要把我们之前所写的方法中的static修饰符去掉就是成员方法。
3.在main方法中创建对象及访问对象的成员变量和成员方法的格式:
类的定义:class 类名{}
创建对象:类名 对象名 = new 类名();
访问对象的成员变量:对象名.成员变量;
访问对象的成员方法:对象名.成员方法;
示例代码
public class Demo1 {
public static void main(String[] args) {
Student st1 = new Student();
st1.name = "wuliuqi";
st1.age = 20;
st1.isMale = true;
st1.sno = 1;
//访问对象的行为
st1.eat();
}
}
//定义学生类,描述所有学生对象
class Student {
//姓名属性
String name;
//年龄属性
int age;
//性别
boolean isMale;
//学号
int sno;
//吃饭行为
public void eat() {
System.out.println(name + " eating");
}
//睡觉行为
public void sleep() {
System.out.println("sleeping");
}
//学习行为
public void study() {
System.out.println("studying");
}
}
4.从语法层面再次认识类和对象:
- 数据类型:一个数据集合和基于这个数据集合的一组操作。byte short int char
- 类定义:类体中包括,成员变量和成员方法。
类定义中的数据集合: 成员变量的集合
类定义中的操作集合: 成员方法的集合
一个类定义,其实就是一种数据类型的定义,这种数据类型比之于byte,short,int,char不一样,类其实是coder自己定义的自定义数据类型,jvm天生不认识,所以需要我们通过定义类向jvm描述。那么,一旦我们将类看做是一种自定义数据类型,类和对象的关系,就可以类比于基本数据类型和基本数据类型变量之间的关系。
int a = 1;
Student st = new Student();
5.类和对象的内存映象
a.类信息存储在方法区中,并且包含了每个方法实现的字节码指令。
b.对象存储在堆上,对象中只存储了成员变量的值。
四、面向对象“特殊”语法
1.构造方法
我们之前是如何给对象的属性赋值的?
对象名.成员变量名 = 成员变量值
其实Java语言还提供了另外一种方式,帮助我们完成对成员变量(对象属性)的初始化——构造方法.
构造方法的作用:就是使得jvm在创建对象的时候,帮助我们进行成员变量的初始化,以方法参数的形式,通过接收方法参数来初始化对象的成员变量值。
与普通方法的不同之处及自身特点
- 构造方法的格式
a. 构造方法的方法名固定,和类名相同 (这一点是java语言的语法规范,所以构造方法命名确实违反了驼峰命名规则)。
b. 构造方法的方法声明中,没有返回值这一项。而当一个普通方法没有返回值则使用void返回值类型进行方法声明。 - 构造方法的注意事项
a. 一个类中可以定义多个重载的构造方法,重载条件和普通方法的重载条件相同,可以根据new 类名(实参列表)中的实参列表指明创建某对象时所使用的构造方法;
b. 当我们没有在类中定义任何一个构造方法的时候,jvm会自动添加一个默认的构造方法(无参构造方法, 实际上什么也不会做),但是,一旦我们自己在类定义中定义了哪怕只有一个构造方法,jvm就不会在自动帮我们添加那个无参构造方法了。
c. jvm创建对象的固定流程
在堆上申请对象的内存空间 -> 给对象的成员变量赋予默认初值 ->(最后一步)调用某一个构造方法初始化成员变量的值
d. 构造方法何时执行呢?
构造方法由jvm来真正调用执行,在创建对象的最后一步执行构造方法给对象的成员变量赋予Coder传递的初值。
2.this关键字
this关键字:代表对当前对象的引用
当前对象的含义:
- 在构造方法中的 this指代的当前对象,其实就是构造方法执行时jvm正在创建的那个对象。
- 在普通成员方法的方法体中,this指代的对象是谁?
对于普通成员方法而言,即对象名.方法(),在哪个对象上调用方法,方法中的this就指的是哪个对象。
this关键字的作用:
- 解决成员变量的隐藏的问题(构造方法的成员变量初始化);
- 访问对象的成员(访问当前对象的成员变量值,访问当前对象的成员方法);
- 访问对象的构造方法,即this(实参列表),在某个构造方法中调用其他构造方法;
a. this调用构造方法,这个代码只能某个构造方法的方法体中
b. this调用构造方法,必须处在构造方法的第一条语句的位置
比如,我们可以在无参构造方法中调用多参构造方法赋予我们业务逻辑的默认初值,而不是使用jvm赋值给堆上的默认初值。
3.static关键字
static关键字:可以修饰(普通)成员变量和 (普通)成员方法。
说明一下: 按照严格意义上的面向对象思想, 被static修饰的成员变量和成员方法都不能算做类中定义的成员。但我们还是习惯上称呼被static修饰的变量为静态成员变量,简称静态变量,方法为静态成员方法,也简称静态方法。
static关键字的特点:
- 被当前类的所有对象所共享,也是判定是否使用static修饰的关键。
a. 当static修饰了成员变量,该成员变量的值就不再存储于对象中了,而是单独存储了一份,被类的所有对象所共享。
b. 当static修饰成员方法的时候,该方法被当前类的所有对象共享。
当前类对象.方法(和普通成员方法从共享的角度,几乎没有太大区别)。 - 可以通过类名访问(对于静态成员,官方推荐:类名.要访问的静态成员)
a. 通过类名直接访问,static成员变量的值
b. 通过类名直接调用,static成员方法 - 随着类的加载而加载
a. static成员变量,随着类加载过程,其实就已经在方法区中分配了内存,并赋予默认初值
b. static成员方法, 一旦类加载完毕,我们就可以直接访问static方法(通过类名.),而不必等待创建该类对象 - 优先于对象而存在(不依赖于对象而存在)
a. 成员变量的角度,static修饰的成员变量,不再依赖于对象而存在,因为static修饰的成员变量的值,不再存储在该类的每个对象中。作为对比,没有被static修饰的成员变量都依赖于对象而存在,因为他们的值都存储在对象中。
b. 成员方法的角度,被static修饰的成员方法,在没有对象存在的情况下,也可以直接通过类名来调用方法。作为对比,没有被static修饰的普通成员方法,它依赖于对象而存在, 原因是普通成员方法中可以访问普通成员变量的值,而普通成员变量的值又是依赖于对象而存在的。
注意事项:
-
在静态上下文(静态方法??)无法访问当前对象的非静态的成员变量或非静态的成员方法。
为什么?
对于非静态成员变量,静态成员是不依赖于对象而存在的,非静态成员变量依赖于对象而存在。当我们在静态上下文要访问当前对象的非静态的成员变量时,此时,当前对象不一定存在,对象如果都不存在的话,存储在对象中的成员变量就更不会存在了。所以我们不能访问。
对于非静态成员方法,虽然非静态成员方法随着类加载会加载到方法区的内存中,看似和堆内存的对象没有关系,但是非静态成员方法有能力访问非静态成员变量,当我们在非静态成员方法中去访问非静态成员变量时,就会遇到同样的问题,即在静态上下文访问非静态成员变量。所以,在静态上下文中也无法访问非静态的成员方法。
但是,在静态方法中可以访问我们new出的对象的非静态的成员变量或非静态的成员方法。
在非静态成员方法中可以访问静态成员变量和静态成员方法。 -
静态方法中不能使用this。
为什么?理由同第一点。this指对当前对象的引用。 -
静态方法 or 非静态方法,方法体中都不能使用static关键字定义变量。
为什么?
我们知道被static修饰的变量是被当前类的所有对象所共享,存储于方法区中。而我们知道在方法体中定义的变量是局部变量,局部变量随着栈帧的存在而存在,随着栈帧的销毁而销毁。如果我们将方法体中定义的变量用static修饰,就与我们所学知识相矛盾,所以不能把在方法体中使用static关键字修饰变量。
静态方法的使用场景:
通常是作为工具方法来使用。之所以工具方法被定义成静态方法,是为了方便使用,可直接通过类名进行调用。工具方法(静态方法)绝大多数情况下访问的都是参数数据。
静态成员变量 VS 普通成员变量:
- 所属不同
静态变量属于类,所以也称为类变量
成员变量属于对象,所以也称为实例变量(对象变量) - 内存中的位置不同
静态变量存储于方法区(类的字节码文件中)
成员变量存储于堆内存 - 内存出现时间不同
静态变量随着类的加载而加载,随着类的消失而消失
成员变量随着对象的创建而存在,随着对象的消失而消失 - 调用不同
静态变量可以通过类名调用,也可以通过对象调用
成员变量只能通过对象名调用
4.代码块
在Java中,使用{}括起来的代码被称为代码块,根据其位置和声明的不同,可以分为局部代码块,构造代码块,静态代码块,同步代码块。
局部代码块(在实际开发中不会使用)
-
声明方式和出现的位置:声明方式{}, 出现在方法体中
-
执行的时机:随着方法的执行而执行
优点:限定变量生命周期,及早释放,提高内存利用率
这个优点理论上确实存在,但是这个优点,在现在jvm中其效果微乎其微,甚至可以忽略不计,我们在开发中,同时还要追求代码可维护性(包括代码的可读性),所以开发中基本不会使用局部代码块。在嵌套的作用域中(代码块),不能定义同名变量
构造代码块
- 声明方式和出现的位置:声明方式{}, 出现在类中方法体之外
- 执行的时机:每次在创建对象的时候执行
a. 构造代码块和构造方法都是在创建对象的过程中执行的,它们的先后顺序是?
构造代码块 先于 构造方法 执行
b. 成员变量的初始化语句和构造代码块以及构造方法执行顺序的先后关系:
成员变量的初始化语句 VS 构造方法: 构造方法后执行
成员变量的初始化语句 VS 构造代码块: 执行的先后顺序,依赖于代码书写的先后顺序 - 构造代码块的使用场景:
a. 可以把多个构造方法的方法体中相同的部分提取出来放到构造代码块中,每次调用构造都执行,并且在构造方法前执行
b. 我们也可以用构造代码块来初始化对象的成员变量的值
静态代码块
- 声明方式和出现的位置:声明方式 static {}, 出现在类中方法体之外
- 执行的时机:随着类加载而执行,因为同一个类在jvm中最多只会被加载一次,所以静态代码块最多也只会执行一次
- 使用场景: 最多只需要执行一次的代码
注意事项:
在学习了静态代码块之后,静态代码块也是静态上下文的一种,即不能访问非静态成员变量,但可以访问静态成员变量。
5.package(可以帮助解决命名冲突问题)
包:类似于操作系统中的文件目录(组织文件),包是用来组织类(不是源文件)
在Java源程序文件的第一行使用package声明可以使文件中定义的类成为指定包的成员。
package声明的语法如下:
package 包名;
包名通常由多个名字字符串构成,中间用句点“.”分隔,每个名字表示的包称为其前面的名字表示的包的子包。 通常以组织机构的域名反转形式作为其所有包的通用前缀,
比如:com.somecompany.apps
注意事项:并非每个java文件都必须有package关键字来指定该文件中的类所属的包。
即使,一个java文件中没有package关键字,该java文件中的类也属于一个包(默认包)
6.import关键字
预备知识:
在类名前面加上类所属的包名,中间用句点“.”分隔,称为类的完全限定名(Full Qualified Name),简称类的限定名。
类的全限定名: 包名.类名
在java语言中:通过类的全限定类名来唯一确定一个类
当在类体中使用了与当前类不同包的类名时,编译器编译时因为无法找到该类的定义而报错,有两个解决办法:
a. 使用不同包类的完全限定名
b. 使用import声明,为编译器提供该类的定义信息
默认情况下,编译器只会在当前的包中找我们用到的类。
import注意事项:
a. import声明一般紧跟在package声明之后,必须在类声明之前,其基本语法如下:
import 类的完全限定名;
b. Java语言核心包java.lang包中的类将被隐式导入,可以直接使用其中的类;
c. import声明提供了一种包的智能导入方式: import 包名.*;
包中的类将根据需要导入,避免使用多条import声明(注意:智能导包方式不能嵌套导包;按需导包,即当前包下没有这个类才会去智能导包)。
五、面向对象思想
1.概念
面向过程的思想:(动词的集合)
所谓面向过程的编程思想,简单理解,程序是“动词”的集合,即程序功能是由一系列有序的动作来完成。
面向对象的思想:(名词的集合)
所谓面向对象的编程思想,简单理解,程序是由一系列的对象(消息)+消息组成,即程序是由一系列的对象和对象间的消息组成。
2.同样的问题,用不同的思想写代码描述:把大象装进冰箱
以面向过程的思想,描述把大象装进冰箱
public void procedure() {
//1. 打开冰箱
//2. 把大象塞进冰箱
//3. 关上冰箱
}
以面向对象的思想,描述把大象装进冰箱
public void object() {
// 冰箱对象.in(大象对象)
}
3.面向对象和面向过程的比较:
- 结构化程序设计(面向过程程序设计):
对应的典型的计算机语言, 例如: C;
面向操作的 ;
函数(方法)是程序的基本单位; - 面向对象程序设计:
对应的典型的计算机语言, 例如: Java, C++, C#;
面向对象(object)的;
类(class)是程序的基本单位;
给对象发送消息: 在对象上调用一次方法,我们就说我们给该对象发送了一个消息,对象的方法调用就是给对象发送消息。对象收到这个消息后,就会执行相应的动作(执行方法),执行方法是对消息的一个响应。
#个人学习记录,如发现有错误之处,欢迎与我交流