7.1 类与对象
7.1.1 看一个养猫猫问题
张老太养了两只猫猫:一只名字叫小白,今年 3 岁,白色。还有一只叫小花,今年 100 岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示 张老太没有这只猫猫。
7.1.2 使用现有技术解决
【常规操作】
-
- 单独的定义变量解决 => 不利于数据的管理
-
- 使用数组解决
- (1)数据类型体现不出来
- (2) 只能通过[下标]获取信息,造成变量名字和内容的对应关系不明确
- (3) 不能体现猫的行为(猫的方法)
public class Object01{
public static void main(String[] args){
//需求:
//张老太养了两只猫猫:一只名字叫小白,今年 3 岁,白色。
//还有一只叫小花,今年 100 岁,花色。
//请编写一个程序,当用户 输入小猫的名字时,就显示该猫的名字,年龄,颜色。
//如果用户输入的小猫名错误,则显示 张老太没有这只猫猫。
//思路分析:
//单独变量来解决 => 不利于数据的管理(你把一只猫的信息拆解)
//
//第一只猫的信息
String cat1Name = "小白";
int cat1Age = 3;
String cat1Color = "白色";
//第二只猫的信息
String cat2Name = "小花";
int cat2Age = 100;
String cat2Color = "花色";
//数组 ===>(1) 数据类型体现不出来
// (2) 只能通过[下标]获取信息,造成变量名字和内容的对应关系不明确
// (3) 不能体现猫的行为
//第1只猫信息
String[] cat1 = {
"小白", "3", "白色"};
//第二只猫的信息
String[] cat2 = {
"小花", "100", "花色"};
}
}
7.1.3 现有技术解决的缺点分析
不利于数据的管理,以往的技术使用效率低 ===》 引出我们的新知识点 类与对象;
Java 设计者 引入 类与对象(OOP) ,根本原因就是现有的技术,不能完美的解决新的需求。
public class Object01 {
//编写一个main方法
public static void main(String[] args) {
/*
张老太养了两只猫猫:一只名字叫小白,今年3岁,白色。
还有一只叫小花,今年100岁,花色。请编写一个程序,当用户输入小猫的名字时,
就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,
则显示 张老太没有这只猫猫。
*/
//使用OOP面向对象解决
//实例化一只猫[创建一只猫对象]
//爱摸鱼的TT~解读
//1. new Cat() 创建一只猫(猫对象【真实】)
//2. Cat cat1 = new Cat(); 把创建的猫赋给 cat1【别称/名】
//3. cat1 就是一个对象(对象的引用)
Cat cat1 = new Cat();
cat1.name = "小白";
cat1.age = 3;
cat1.color = "白色";
cat1.weight = 10;
//创建了第二只猫,并赋给 cat2
//cat2 也是一个对象(猫对象)
Cat cat2 = new Cat();
cat2.name = "小花";
cat2.age = 100;
cat2.color = "花色";
cat2.weight = 20;
//怎么访问对象的属性呢
System.out.println("第1只猫信息" + cat1.name
+ " " + cat1.age + " " + cat1.color + " " + cat1.weight);
System.out.println("第2只猫信息" + cat2.name
+ " " + cat2.age + " " + cat2.color + " " + cat2.weight);
}
}
//使用面向对象的方式来解决养猫问题
//
//定义一个猫类 Cat -> 自定义的数据类型
class Cat {
//属性/成员变量/filed字段
String name; //名字
int age; //年龄
String color; //颜色
double weight; //体重
//行为
}
7.1.4 类与对象的关系示意图
7.1.5 类和对象的区别和联系
通过上面的案例和讲解,我们可以看出:
-
- 类是抽象的,概念的,代表一类事物,比如人类,猫类…,即它是数据类型;
-
- 对象是具体的,实际的,代表一个具体事物, 即 是实例;
-
- 类是对象的模板,对象是类的一个个体,对应一个实例。
- 类是对象的模板,对象是类的一个个体,对应一个实例。
7.1.6 对象在内存中存在形式(重要的)
对象在内存中存在堆内存里
7.1.7 属性/成员变量/field字段
基本介绍
-
- 从概念或叫法上看: 成员变量 = 属性 = field(字段) (即 成员变量是用来表示属性的,在之后统一叫 属性) 案例演示:Car(name,price,color) Object02.java
public class Object02 {
//编写一个main方法
public static void main(String[] args) {
}
}
class Car {
String name;//属性, 成员变量, 字段 field
double price;
String color;
String[] master;//属性可以是基本数据类型,也可以是引用类型(对象,数组)
}
-
- 属性是类的一个组成部分,一般是基本数据类型,也可是引用类型(对象,数组)。比如我们前面定义猫类 的 int age 就是属性。
- 属性是类的一个组成部分,一般是基本数据类型,也可是引用类型(对象,数组)。比如我们前面定义猫类 的 int age 就是属性。
注意事项和细节说明
PropertiesDetail.java
-
- 属性的定义语法同变量一致,示例:访问修饰符 属性类型 属性名;
这里简单的介绍访问修饰符: 控制属性的访问范围
有四种访问修饰符 public, proctected, 默认, private ,后面我会详细介绍
-
- 属性的定义类型可以为任意类型,包含基本类型或引用类型
-
- 属性如果不赋值,有默认值,规则和数组一致。
默认值: int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000, boolean false,String null
public class PropertiesDetail {
//编写一个main方法
public static void main(String[] args) {
//创建Person对象
//p1 是对象名(对象引用)
//new Person() 创建的对象空间(数据) 才是真正的对象
Person p1 = new Person();
//对象的属性默认值,遵守数组规则:
//int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000,boolean false,String null
System.out.println("\n当前这个人的信息");
System.out.println("age=" + p1.age + " name="
+ p1.name + " sal=" + p1.sal + " isPass=" + p1.isPass) ;
}
}
class Person {
//四个属性
int age;
String name;
double sal;
boolean isPass;
}
7.1.8 如何创建对象
-
- 先声明再创建
Cat cat ; //声明对象 cat是对象名(对象引用),在栈空间,指向为null【因为没有真正的对象空间(没分配空间)】
cat = new Cat(); //创建 new Cat() 创建的对象空间(数据),在堆内存 才是真正的对象
-
- 直接创建
Cat cat = new Cat();// 先在堆内存分配好空间,然后赋值给栈内存,栈内存就指向该地址
7.1.9 如何访问属性
基本语法
对象名(对象引用).属性名;
案例演示赋值和输出
cat.name ;
cat.age;
cat.color;
7.1.10 类和对象的内存分配机制(重要)
我们先思考一道题:
采用内存分析方法来解决:
Java 内存的结构分析
-
- 栈: 一般存放基本数据类型(局部变量)
-
- 堆: 存放对象(Cat cat , 数组等)
-
- 方法区:常量池(常量,字符串…), 类加载信息
- 方法区:常量池(常量,字符串…), 类加载信息
Java 创建对象的流程简单分析
Person p = new Person();
p.name = “jack”;
p.age = 10;
- 先加载 Person 类信息(属性和方法信息到方法区, 只会加载一次)
2) 在堆中分配空间, 进行默认初始化(看和数组规则一样) 【有数据就有地址】
3) 把地址赋给 p , p 就指向该对象
4) 进行指定初始化, 比如 p.name =”jack” p.age = 10
例如:看一个练习题,并分析画出内存布局图,进行分析
分析(一步到位):你们在分析过程可以一步一步执行,就能明白了。
7.2 成员方法
7.2.1 基本介绍
在某些情况下,我们要需要定义成员方法(简称方法)。比如人类:除了有一些属性外( 年龄,姓名…),我们人类还有一些行为比如:可以说话、跑步… 通过学习,还可以做算术题。这时就要用成员方法才能完成。现在要求对 Person 类进行完善。
7.2.2 成员方法快速入门
下面有4个方法实例进行实现:
1) 添加 speak 成员方法,输出 “我是一个好人”
2) 添加 cal01 成员方法,可以计算从 1+…+1000 的结果
3) 添加 cal02 成员方法,该方法可以接收一个数 n,计算从 1+…+n 的结果
4) 添加 getSum 成员方法,可以计算两个数的和
public class Method01 {
//编写一个main方法
public static void main(String[] args) {
//方法使用
//1. 方法写好后,如果不去调用(使用),不会输出
//2. 先创建对象 ,然后调用方法即可
Person p1 = new Person();
p1.speak(); //调用方法
p1.cal01(); //调用cal01方法
p1.cal02(5); //调用cal02方法,同时给n = 5
p1.cal02(10); //调用cal02方法,同时给n = 10
//调用getSum方法,同时num1=10, num2=20
//把 方法 getSum 返回的值,赋给 变量 returnRes
int returnRes = p1.getSum(10, 20);
System.out.println("getSum方法返回的值=" + returnRes);
}
}
class Person {
String name;
int age;
//方法(成员方法)
//添加speak 成员方法,输出 “我是一个好人”
//爱摸鱼的TT~解读
//1. public 表示方法是公开
//2. void : 表示方法没有返回值
//3. speak() : speak是方法名, () 形参列表
//4. {} 方法体,可以写我们要执行的代码
//5. System.out.println("我是一个好人"); 表示我们的方法就是输出一句话
public void speak() {
System.out.println("我是一个好人");
}
//添加cal01 成员方法,可以计算从 1+..+1000的结果
public void cal01() {
//循环完成
int res = 0;
for(int i = 1; i <= 1000; i++) {
res += i;
}
System.out.println("cal01方法 计算结果=" + res);
}
//添加cal02 成员方法,该方法可以接收一个数n,计算从 1+..+n 的结果
//爱摸鱼的TT~解读
//1. (int n) 形参列表, 表示当前有一个形参 n, 可以接收用户输入
public void cal02(int n) {
//循环完成
int res = 0;
for(int i = 1; i <= n; i++) {
res += i;
}
System.out.println("cal02方法 计算结果=" + res);
}
//添加getSum成员方法,可以计算两个数的和
//爱摸鱼的TT~解读
//1. public 表示方法是公开的
//2. int :表示方法执行后,返回一个 int 值
//3. getSum 方法名
//4. (int num1, int num2) 形参列表,2个形参,可以接收用户传入的两个数
//5. return res; 表示把 res 的值, 返回
public int getSum(int num1, int num2) {
int res = num1 + num2;
return res;
}
}
那底层是如何呢?
7.2.3 方法的调用机制原理(重要!-示意图!!!)
提示:画出程序执行过程[就针对getSum()方法分析]+说明
7.2.4 为什么需要成员方法
我们先看一个需求:请遍历一个数组 , 输出数组的各个元素值。
- 解决思路 1,传统的方法,就是使用单个 for 循环,将数组输出,大家看看问题是什么?
- 假如我们需要输出很多数组值,那采用该思路就只能不断重复写for循环这几行代码
- 要修改for循环代码,那也只能每个代码都需修改
public class Method02 {
//编写一个main方法
public static void main(String[] args) {
//请遍历一个数组 , 输出数组的各个元素值
int [][] map = {
{
0,0,1},{
1,1,1},{
1,1,3}};
//遍历map数组
//传统的解决方式就是直接遍历
for(int i = 0; i < map.length; i++) {
for(int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + "\t");
}
System.out.println();
}
//....
//
//要求再次遍历map数组
for(int i = 0; i < map.length; i++) {
for(int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + "\t");
}
System.out.println();
}
//...再次遍历
//
for(int i = 0; i < map.length; i++) {
for(int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + "\t");
}
System.out.println();
}
}
}
- 解决思路 2: 定义一个类 MyTools ,然后写一个成员方法,调用方法实现,看看效果又如何。
public class Method02 {
//编写一个main方法
public static void main(String[] args) {
//请遍历一个数组 , 输出数组的各个元素值
int [][] map = {
{
0,0,1},{
1,1,1},{
1,1,3}};
//使用方法完成输出, 创建MyTools对象
MyTools tool = new MyTools();
//遍历map数组
//传统的解决方式就是直接遍历
// for(int i = 0; i < map.length; i++) {
// for(int j = 0; j < map[i].length; j++) {
// System.out.print(map[i][j] + "\t");
// }
// System.out.println();
// }
//使用方法
tool.printArr(map);
//....
//
//要求再次遍历map数组
// for(int i = 0; i < map.length; i++) {
// for(int j = 0; j < map[i].length; j++) {
// System.out.print(map[i][j] + "\t");
// }
// System.out.println();
// }
tool.printArr(map);
//...再次遍历
//
// for(int i = 0; i < map.length; i++) {
// for(int j = 0; j < map[i].length; j++) {
// System.out.print(map[i][j] + "\t");
// }
// System.out.println();
// }
tool.printArr(map);
}
}
//把输出的功能,写到一个类的方法中,然后调用该方法即可
class MyTools {
//方法,接收一个二维数组
public void printArr(int[][] map) {
System.out.println("=======");
//对传入的map数组进行遍历输出
for(int i = 0; i < map.length; i++) {
for(int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + "\t");
}
System.out.println();
}
}
}
7.2.5 成员方法的好处
- 提高代码的复用性 ;
2) 可以将实现的细节封装起来,然后供其他用户来调用即可。
7.2.6 成员方法的定义
访问修饰符 返回数据类型 方法名(形参列表..) {
//方法体
语句;
return 返回值;
}
- 形参列表:表示成员方法输入 cal(int n) , getSum(int num1, int num2)
2) 返回数据类型:表示成员方法输出, void 表示没有返回值
3) 方法主体:表示为了实现某一功能代码块
4) return 语句不是必须的。 【如果有返回值那就需要return,反之可以不写】
5) 爱摸鱼的TT~提示:结合前面的题示意图, 来理解
7.2.7 注意事项和使用细节
1. 访问修饰符 (作用:控制方法使用的范围)
- 如果不写,就为默认访问,[有四种: public, protected, 默认, private], 具体在后面说。
2. 返回数据类型
- 一个方法最多有一个返回值 [思考,如何返回多个结果?—>采用 返回“数组” ]
//1. 一个方法最多有一个返回值 [思考,如何返回多个结果?]
public int[] getSumAndSub(int n1, int n2) {
int[] resArr = new int[2];
resArr[0] = n1 + n2;
resArr[1] = n1 - n2;
return resArr;
}
- 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
3) 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值; 而且要求返回值类型必须和 return 的值类型一致或兼容
//3. 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值;
// 而且要求返回值类型必须和return的值类型一致或兼容
public double f1() {
double d1 = 1.1 * 3;
int n = 100;
return n; // int ->double
//return d1; //ok? double -> int (x)
}
- 如果方法是 void,则方法体中可以没有 return 语句,或者 只写 return ;
//如果方法是void,则方法体中可以没有return语句,或者 只写 return ;
//爱摸鱼的TT~提示:在实际工作中,我们的方法都是为了完成某个功能,所以方法名要有一定含义最好是见名知意
public void f2() {
System.out.println("hello1");
System.out.println("hello1");
System.out.println("hello1");
int n = 10;
//return n; // 错误
return ;
}
3. 方法名
- 遵循驼峰命名法,最好见名知义,表达出该功能的意思即可, 比如 得到两个数的和 getSum, 开发中按照规范。
4. 形参列表
- 一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开,比如:getSum(int n1, int n2)
- 参数类型可以为任意类型,包含基本类型或引用类型,比如:printArr(int[][] map)
- 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数!
- 方法定义时的参数称为形式参数,简称形参;方法调用时的传入参数称为实际参数,简称实参,实参和形参的类型要一致或兼容、个数、顺序必须一致。
5. 方法体
里面写完成功能的具体的语句,可以为输入、输出、变量、运算、分支、循环、方法调用、但里面不能再定义方法!即:方法不能嵌套定义。
6. 方法调用细节说明(!!!)
- 同一个类中的方法调用:直接调用即可。比如 print(参数);
案例演示:A类 sayOk 调用 print()
- 跨类中的方法A类调用B类的方法:需要通过对象名调用。比如 对象名.方法名(参数);
案例演示:B类 sayHello 调用 print()
- 特别说明一下:跨类的方法调用和方法的访问修饰符相关,先暂时这么提一下,后面我们讲到访问修饰符时,还要再细说。
7.2.8 巩固习题
- 编写类 AA ,有一个方法:判断一个数是奇数 odd 还是偶数, 返回 boolean
public class MethodExercise01{
public static void main(String[] args){
//需求:
//编写类 AA ,有一个方法:判断一个数是奇数 odd 还是偶数, 返回 boolean
AA a = new AA();
if(a.isOdd(12)){