②对象和类
文章目录
1. 引例
- Java对象的模板——类也是这么描述对象的
- 属性(外貌特征)
- 行为(能力特征,能做什么)
- 得出类的定义
- 类就是对象的模板
- 类抽取了同种类型的所有对象属性和行为上的共性
- 一个类可以描述千千万万个对象
我们简单总结一下,面向对象就是在模仿我们的现实世界
这部分随便看看吧,我觉得很啰嗦
- 类比客观世界的组成:对象是运行中的Java面向对象程序的基本组成单位,类比客观世界中的个体
- 类比客观世界个体的描述:对象的属性和行为
- 类比客观世界的个体的全体描述(模板):抽取对象的共性特征形成类
- 类抽取出了同种类型的千千万万个对象的属性和行为上的共性(共同特征) ,类就是对象的模板
- 类和对象的关系
- 类描述了,同种类型的对象,在属性和行为上的共同特征
- 但是,类只规定了,有什么样的属性,有什么样的行为,但是,对象属性的取值,是由对象自己决定。一个类可以有千千万万不同的对象
- 对象与对象的关系
- 不同类生成的对象,属性和行为往往都具有差异,不是同种对象
- 相同类生成的对象,属性和行为具有相似性,但是也完全可能不同
2. 类的定义
定义类包括定义类结构本身,和定义类中属性和行为
2.1 定义一个类
类抽取出对象的共性的行为和共有的属性
- 类的定义我们并不陌生
- 语法
[各种修饰符] class 类名{
}
- 一个Java文件中只能定义一个public修饰的类,但是可以定义多个非public类
2.2 定义类的成员
类中用成员变量来描述对象共有的属性
类中用成员方法来描述对象共性的行为
-
成员变量和成员方法合起来统称为(类的)成员
-
局部位置:方法或者一些代码块结构当中属于局部位置
-
成员位置:类体种方法等局部位置外的其他位置
练习
定义学生类
分析学生类中的属性和行为,根据学生类的属性和行为来定义学生类。
创建学生类对象,给属性赋值,并调用成员方法
-
成员变量来表示属性
- 成员变量:定义在成员位置的变量,而不是方法或局部位置的局部变量
- 语法
[访问权限修饰符] 数据类型 成员变量名;
-
成员方法来表示行为
- 成员方法:方法都定义在成员位置,表示对象行为的成员方法不能加static
- 语法
[访问权限修饰符] 返回值类型 方法名(){ }
-
创建类的对象
- 在方法和能够写语句的地方创建对象然后使用该对象,可以认为是初始化一个引用数据类型的过程
- 语法
类名 对象名 = new 类名();
- 对象名作为一个标识符字符串,遵循变量名的命名规范,小驼峰式的书写方式
-
使用对象获取对象的属性和行为
- 访问属性
对象名.成员变量;
- 调用行为(方法)
对象名.成员方法;
-
直接输出对象名
- 直接打印对象名得到的是类的全限定类名 + 十六进制的地址值
打印对象名
//com.cskaoyan.javase._0introduction.Student@1540e19d
//com.cskaoyan.javase._0introduction.Student可以唯一的确定一个类,称之为类的全限定类名
//@后面跟的是一个十六进制的数,它仍然可以看成是一个地址值
//这个地方打印对象名实质上是调用了一个方法toString()
System.out.println(s);
注意事项
- 类可以嵌套定义叫做内部类,但是现在不要这么做,一个Java文件中定义多个class应该并列而不是包含
- 一个Java文件中的多个class是同包(文件夹)关系
- 一个类当中,应该开门见山的定义成员变量,而后再写成员方法
- 类中没有的属性和行为,对象是不可能有的,类是模板,模板中有才能创建出来
- 使用new关键字就会创建新的对象,两条new语句创建的对象是完全独立的
3. 引用数据类型的全新理解
我们通过数组内存图引入了引用数据的概念
现在我们已经知道了,引用数据类类型是由栈上的引用和堆上的对象两部分组成
- 现在请回忆一下,基础语法部分数据类型的概念
- 思考:
- 能否用数据类型的概念来统一基本数据类型和引用数据类型呢?
3.1 数据类型的概念
-
数据类型的概念
- 一个数据集合和基于这个数据集合的一组操作,比如int,有固定的取值范围,有固定的操作
-
类的定义中,类包括:
- 成员变量和成员方法
- 做一下类比,得到:
- 数据集合: 类中成员变量的集合
- 操作集合: 类中成员方法的集合
-
总结
- 一个类的定义,实际上就是定义了一种全新的数据类型,是一种自定义的数据类型
- 这种数据类型,和基本数据类型不同,它是我们自己自定义的数据类型(引用数据类型)
3.2 引用数据类型的不同点
类实际上一种我们自定义的数据类型,里面有什么数据,能做什么操作都是我们自定义的,这种自定义的数据类型就是引用数据类型, 类实际上就是一个引用数据类型
*
引用数据类型和基本数据类型的区别:
我们在使用一个基本数据类型时,由于基本数据类型是Java当中已经定义好的
所以JVM能够正常帮助我们开辟空间,正常给变量赋值
但是引用数据类型(类)是我们自定义的数据类型,jvm是不可能预先知道这个类型的信息的
所以想要它帮助我们创建还需要一个让jvm了解该类信息的过程
这个过程在Java称之为类加载
显然
-
类加载要在创建对象之前,也就是说创建一个类的对象触发***该类的类加载***
-
类加载需要把一个类的class(字节码)文件读取到方法区内存中,然后经过一系列操作完成类加载
- 类加载完毕后,JVM已经获取了该类的类型信息(有什么成员变量,方法等),能够完成创建对象
-
类和对象的关系,就可以类比于基本数据类型和基本数据类型变量之间的关系
int a = 1; //类比 Student s = new Student(); //
-
在Student s = new Student()中
- s是对象名或者称之为引用,我们通过引用访问这个s对象
- new Student()是指示JVM帮助我们创建引用类型Student的对象
3.3 对象内存图练习
练习:
1个对象的内存图,一个对象的基本初始化过程
3个对象的内存图,其中有两个引用指向同一个对象
类加载过程只会执行一次,后面再有对象创建时,不会进行类加载
③局部变量和成员变量
1. 比较的方式
局部变量和成员变量的比较,我们从以下五个方面去比较:
- 在类中定义的位置不同
- 在内存中的位置不同
- 生命周期不同
- 初始化值不同
- 作用范围
2. 结论
- 在类中定义的位置不同
- 局部变量
- 方法、方法的形参或者代码块结构等位置
- 成员变量
- 类体中的非局部位置
- 局部变量
- 在内存中的位置不同
- 局部变量
- 栈上
- 成员变量
- 堆中的对象中
- 局部变量
- 生命周期不同
- 局部变量
- 随着方法的进出栈而存在而销毁
- 成员变量
- 对象创建以后就存在
- 对象销毁就没了
- 和对象同生共死,但实际上只要该对象栈上的引用被销毁,对象中的成员变量也就无效了
- 局部变量
- 初始化值不同
- 局部变量
- 没有默认的初始化值,需要手动赋值和初始化
- 成员变量
- 有默认的初始值
- 局部变量
- 作用范围
-
局部变量
- 只在方法范围内生效
-
成员变量
- 对象当中,整个类的成员方法都可以使用
- 在加static的非普通成员方法中,不能使用
-
④this关键字
1. 引例
class Car {
//成员变量
String color;
double speed;
//成员方法
public void run(int speed) {
System.out.println(color + "的小汽车以" + this.speed + "速度嘟嘟嘟的跑!");
//this.test("");
}
// 这个成员方法打印的速度是对象调用run方法时传参过来的int 类型的speed,而非它自身的成员变量speed,如果仍想访问成员变量speed,那必须使用到this
}
2. 就近原则
就近原则最经典的体现,就是在方法中如果存在同名的局部变量和成员变量
那么访问该同名变量,得到的结果
- 必然是距离访问语句更近的局部变量,而不是同名成员变量
- 就近原则在Java中非常常见,是Java语言设计者遵守的一个常规协定
3. this关键字
在就近原则的前提下,普通手段是无法访问同名成员变量的,所以引用this关键字实现访问
3.1 引入
- this关键字(重点)
- Java当中每个类的成员方法的形参列表中都隐含了一个传参
- 该传参用this引用接收,this引用指向当前对象
- 所以this关键字表示当前对象
- 什么叫当前对象?
- 创建对象,然后用对象名点调用成员方法
- 对象名(引用)指向的对象,就是当前对象,就是成员方法中的隐含传参this指向的对象
- 正是因为成员方法中,有隐含的this传参指向了当前对象
- 所以可以直接在成员方法中,用this去访问成员变量和调用成员方法
- 因为是隐式传参,上述访问成员时,this可以省略
- 但是当成员方法中,有同名局部变量时,由于就近原则
- 这个时候,如果还想要访问成员变量,就不能省略this关键字
- 所以,可以用this访问表示成员变量,来和局部变量做区分
3.2 this关键字的作用
- 可以在成员方法中区分同名的成员变量和局部变量
- 可以在成员方法中去调用别的成员方法(实际上并没有必要写出这个this)
- static方法和static方法之间可以直接互相调用
- 非static成员方法之间也可以直接互相调用
- static方法中想要访问成员方法,必须先创建对象
- this很重要的用途是:
- 用来指示一个变量到底是成员变量还是局部变量
- 在代码比较复杂的情况下,可以显著增加代码可读性
- 未完待续…
4. 总结和注意事项
总结和注意事项
- this指向当前对象的隐式传参,必须是在普通成员方法中
- 加static的方法中,没有该this传参
- 可以在成员方法中打印该this关键字,可以发现和在main方法中打印对象名(引用)结果一致
- 既然this指向当前对象,那么不同的this指向的对象必然不同
⑤构造方法(constructor)
1. 引例
创建一个教师类,有课程和年龄两个属性,行为是可以上课
- 思考一下,我们给成员变量赋值的语句,是不是显得冗余?
- 怎么改进?
- 对象的属性,能不能"出厂"的时候就设定好呢?
2. 构造方法
构造方法:
在Java程序设计语言中,new创建对象,必须使用构造器
- 构造方法是一种特殊方法,用来创建初始化对象(实例)
- 作用是给对象的成员变量赋值
语法:
public 类名(参数){
//方法体
}
注意:
-
构造方法不是普通方法,它没有返回值,并且方法名是首字母大写的(因为类名是首字母大写)
-
new对象的时候,JVM自动去调用这个方法,构造方法无法通过普通方法的调用方式调用
- JVM会在创建对象的最后一步去调用构造方法,保证构造器能够给成员变量正确赋值
- “永远可以相信构造器”
-
Java默认提供无参构造方法,但是如果你提供了一个有参的构造方法,那么就没有默认的无参构造了
- 建议写代码时,顺手提供无参构造方法
- 有些框架可能依赖无参构造创建对象
- 继承中,子类对象初始化可能依赖父类无参构造
- 建议写代码时,顺手提供无参构造方法
-
构造方法可以重载
-
构造方法中可以使用this关键字,去访问对象的成员,也可以去调用已有的构造方法
public Teacher(String course, int age, int height) { this(course, age); this.height = height; }
- 如果使用this调用别的构造方法,那么这条语句必须在该构造方法的第一行
每new创建一个对象,都会调用一次构造方法,而类加载仅有一次
- 如果使用this调用别的构造方法,那么这条语句必须在该构造方法的第一行
三种给成员变量赋值方法的顺序
- 赋默认初值–>显式初值(类中自带的成员变量赋值)–>构造器赋值
赋值顺序的文字描述
1,第一步永远是先默认初始化,成员变量具有默认值
2,去找到new对象使用的构造方法
3,考虑该构造方法的第一行有没有this去调用别的构造方法
a,如果没有,也不会立刻执行构造方法,而是先去执行成员变量的显式赋值(如果有的话)
b,如果有this构造器,立刻跳到this构造器,然后也不会立刻执行this构造器, 而是先去执行成员变量的显式赋值(如果有的话)
成员变量的显式赋值执行结束后,再执行this构造方法
this构造方法执行结束后,不会再执行显式赋值,而是立刻回去执行new对象调用的那个构造方法
作业
已知有3个班级(一班,二班,三班)分别有3人,2人,5人
键盘录入每个班级的学生的成绩
请使用二维数组存储数据
并计算:
每个班级的平均成绩,每个班级中的最高成绩和最低成绩,并输出
这道题主要看二维数组的定义方式,如何实现每个二维数组的长度是不一样的
public class Work1 {
public static void main(String[] args) {
//格式二动态初始化二维数组
double[][] arr = new double[3][];
//分别初始化其中的一维数组
arr[0] = new double[3];
arr[1] = new double[2];
arr[2] = new double[5];
//键盘录入成绩
//遍历数组
Scanner sc = new Scanner(System.in);
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.println("请输入" + (i + 1) + "班第" + (j + 1) + "个同学的成绩:");
//录入成绩
arr[i][j] = sc.nextDouble();
}
}
//System.out.println(Arrays.deepToString(arr));
//double[][] arr2 = {{20, 30, 10}, {100, 90}, {10, 20, 30, 40, 50}};
getClassAverage(arr);
getClassMaxScore(arr);
getClassMinScore(arr);
}
//计算每个班级的平均分,实际是计算每个一维数组的平均值
public static void getClassAverage(double[][] arr) {
//遍历二维数组
for (int i = 0; i < arr.length; i++) {
double sum = 0;
for (int j = 0; j < arr[i].length; j++) {
sum += arr[i][j];
}
System.out.println((i + 1) + "班的平均分是:" + (sum / arr[i].length));
}
}
public static void getClassMaxScore(double[][] arr) {
//逐个遍历,然后求最大值
for (int i = 0; i < arr.length; i++) {
//设每个一维数组的第一个元素是最大值
double max = arr[i][0];
for (int j = 0; j < arr[i].length; j++) {
if (arr[i][j] > max) {
max = arr[i][j];
}
}
System.out.println((i + 1) + "班的最高分是:" + max);
}
}
public static void getClassMinScore(double[][] arr) {
//逐个遍历,然后求最小值
for (int i = 0; i < arr.length; i++) {
//设每个一维数组的第一个元素是最小值
double min = arr[i][0];
for (int j = 0; j < arr[i].length; j++) {
if (arr[i][j] < min) {
min = arr[i][j];
}
}
System.out.println((i + 1) + "班的最低分是:" + min);
}
}
}