java学习笔记(七) - 面向对象基础

一、类与对象

  • 类是抽象的,概念的,代表一类事物,比如人类,猫类.…,即它是数据类型
  • 对象是具体的,实际的,代表一个具体事物,即是实例
  • 类是对象的模板,对象是类的一个个体,对应一个实例

1. 创建对象

  • 先声明再创建

    1
    2
    Cat cat;//声明对象
    cat = new Cat();//创建
  • 直接创建

    1
    Cat cat = new Cat();

2. 类和对象的内存分配机制

2.1 java内存的结构分析

  • 栈:一般存放基本数据类型(局部变量)
  • 堆:存放对象(Cat cat,数组等)
  • 方法区:常量池(常量,比如字符串),类加载信息

2.2 java创建对象的流程

  • 先加载Person类信息(属性和方法信息,只会加载一次)

  • 在堆中分配空间,进行默认初始化(看规则)

  • 把地址赋给p,p就指向对象

  • 进行指定初始化

    1
    2
    p.name =" jack";
    p.age = 10;

3. 属性/成员变量

3.1 介绍

  • 成员变量也可以称为属性或者字段(filed)

  • 属性是类的一个组成部分,一般是基本数据类型,也可能为引用类型(对象,数组)

3.2 注意事项

  • 属性的定义:

    1
    访问修饰符 属性类型 属性名;
  • 属性如果不赋值,有默认值

4. 成员方法

4.1. 定义

1
2
3
4
5
public 返回数据类型 方法名(参数列表){
//方法体
//逻辑代码
return 返回值;
}
1
2
3
4
5
6
7
8
Class Person {
String name;
int age;
//成员方法
public void say(){
System.out.print("hello...")
}
}
  • 参数列表:表示成员方法输入
  • 数据类型(返回类型):表示成员方法输出,void表示没有返回值
  • 方法主体:表示为了实现某一功能代码
  • return不是必须的

4.2 注意事项

4.2.1 访问修饰符
  • 不写为默认,共四种public 、protected、默认、private
4.2.2 返回数据类型
  • 一个方法最多有一个返回值,返回多个结果采用数组
  • 返回类型可以为任意类型,包含基本类型或引用数据类型(数组、对象)
  • 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为return值;而且要求返回值类型必须和return的值类型一致或兼容
  • 如果方法是void,则方法体中可以,没有return语句,或者只写return
4.2.3 方法名
  • 遵循驼峰命名法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class AA {
//1,一个方法最多有一个返回值,如何返回多个结果:返回数组
//2.返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
public int[] getSumAndSub(int n1, int n2) {
int[] resArr = new int[2];//创建一个数组
resArr[O] = n1 + n2;
resArr[1] = n1 - n2;
return resArr;
}
//3.如果方法要求有返回数据类型,则方法体中最后的执行语句必须为return值;
//而且要求返回值类型必须和return的值类型一致或兼容
public double f1(){
double d1 = 1.1 *3;
return 1.1;
}
}
4.2.4 形参列表
  • 一个方法可以有0个参数,也可以有多个参数
1
2
public void hi(){}
public void say(String name, int age){}
  • 参数类型可以为任意类型,包含基本类型或引用类型
  • 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数
  • 方法定义时的参数称为形式参数,简称形参;方法调用时的参数称为实际参数简称实参,实参和形参的类型要一致或兼容、个数、顺序必须一致
4.2.5 方法体
  • 里面写完成功能的具体的语句,可以为输入、输出、变量、运算、分支、循环方法调用
  • 方法不能嵌套定义

5. 方法调用注意事项

  • 同一个类中的方法调用:直接调用即可
1
2
3
4
5
6
Class Test {
public void hi(){}
public void say(){
hi();
}
}
  • 跨类中的方法A类调用B类方法:需要通过对象名调用,且跨类的方法调用和方法的访问修饰符相关
1
2
3
4
5
6
7
8
9
Class Test {
public void hi(){}
}
Class Test1 {
public void say(){
Test test = new Test();
test.hi();
}
}

6. 方法的传参机制

  • 引用类型传递的是地址,可以通过形参影响实参

二、递归

  • 方法自己调用自己,每次调用传入不同的变量

1. 使用规则

  • 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
  • 方法的局部变量是独立的,不会相互影响
  • 如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据
  • 递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError
  • 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕

2. 案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class MiGong {
//编写一个main方法
public static void main(String[] args) {
//迷宫地图
//新建一个二维数组 行8 列7
int[][] map = new int[8][7];

//第一行最后一行为1 表示障碍物
for(int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}

//第一列和最后一列为1
for(int i = 0; i < 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
//障碍物坐标
map[3][1] = 1;
map[3][2] = 1;

//创建一个游戏对象
Game game = new Game();
game.findWay(map, 1, 1);//调用方法

//打印地图
System.out.println("======迷宫地图======");
for(int i = 0; i < 8; i++) {
for(int j = 0; j < 7; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
}

class Game {
//1.返回值类型为boolean类型
//2.如果找到就返回true 没有就返回false
//3.map就是二维数组,即表示迷宫地图
//4.i j 为老鼠的位置 初始化位置(1,1)
//5.因为我们是递归的找路,定义map数组的各个值得含义
//0 表示可以走 1 表示障碍物 2 表示可以走 3 表示走过 但走不通是死路
//6.当map[6][5] = 2时 就说明找到通路,就可以结束,否则就继续找
//7.确定老鼠找路策略 右--> 下--> 左--> 上

public boolean findWay(int[][] map, int i, int j) {

if(map[6][5] == 2) {//说明找到
return true;

} else {
if(map[i][j] == 0) {//当前位置0,表示可以走通

//假定可以走通
map[i][j] = 2;

//使用策略判断是否可以走通
//右--> 下--> 左--> 上
if(findWay(map, i, j + 1)) {//往右走
return true;
} else if(findWay(map, i + 1, j)) {//往下走
return true;
} else if(findWay(map, i, j - 1)) {//往左走
return true;
} else if(findWay(map, i - 1, j)) {//往上走
return true;
} else {
map[i][j] = 3;
return false;
}

} else {//map[i][j] = 1,2,3
return false;
}
}
}
}

三、方法重载(OverLoad)

  • 同一个类中方法名相同但参数列表不同(参数类型或者个数或顺序之一不同),对于参数名,返回类型无要求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
public class OverLoadExercise01 {

//编写一个main方法
public static void main(String[] args) {

/*

编写程序,类Methods中定义三个重载方法并调用。方法名为m。
三个方法分别接收一个int参数、两个int参数、一个字符串参数。分别执行平方运算并输出结果,
相乘并输出结果,输出字符串信息。在主类的main ()方法中分别用参数区别调用三个方法。OverLoadExercise.java

*/
Methods methods = new Methods();//创建一个对象
methods.m(8);
methods.m(8, 4);
methods.m("haha");

System.out.println(methods.max(4, 7));
System.out.println(methods.max(4.0, 7.7));
System.out.println(methods.max(4.0, 7.7, 5.7));
}
}

class Methods {

//方法的返回值类型 void
//方法的名字 m
//方法的形参(int n)
//方法体 执行平方运算

public void m(int n) {
System.out.println(n + "的平方=" + n * n);

}

//方法的返回值类型 void
//方法的名字 m
//方法的形参(int n1, int n2)
//方法体 进行进行相乘 并输出
public void m(int n1, int n2) {
System.out.println("相乘=" + n1 * n2);

}
//方法的返回值类型 void
//方法的名字 m
//方法的形参(int n1, int n2)
//方法体 进行进行相乘 并输出

public void m(String n) {
System.out.println("字符串信息=" + n);
}

//方法的返回值类型 int
//方法的名字 max
//方法的形参(int n1, int n2)
//方法体 返回两个int中的最大值

public int max(int n1, int n2) {
//return n1 > n2 ? n1 : n2;
if(n1 > n2) {
return n1;
} else {
return n2;
}
}

//方法的返回值类型 double
//方法的名字 max
//方法的形参(double n1, double n2)
//方法体 返回两个double中的最大值

public double max(double n1, double n2) {

//return n1 > n2 ? n1 : n2;

if(n1 > n2) {
return n1;
} else {
return n2;
}

}



//方法的返回值类型 double
//方法的名字 max
//方法的形参(double n1, double n2, double n3)
//方法体 返回两3个double中的最大值

public double max(double n1, double n2, double n3) {

//double max = n1 > n2 ? n1 : n2;
//return max > n3 ? max : n3;
if(n1 > n2) {
if(n1 > n3) {
return n1;
} else {
return n3;
}

} else {
if(n2 > n3) {
return n2;
} else {
return n3;
}
}
}
}

1. 可变参数

  • 一个类中多个同名同功能但参数个数不同的方法,封装成一个方法

1.1 语法

1
2
3
4
5
6
7
8
9
10
11
12
13
访问修饰符 返回类型 方法名(数据类型... 形参名){}

//案例
//使用可变参数,可以当做数组来使用,即nums可以当做数组
//
public int sum(int... nums) {
//System.out.println("接收的参数个数=” + nums.length);
int res = 0;
//遍历nums求和
for(int i = 0; i < nums.length; i++){
res += nums[i];
}return res;
}

1.2 注意事项

  • 可变参数的实参可以为0个或任意多个

  • 可变参数的实参可以为数组

  • 可变参数的本质就是数组

  • 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后

    1
    public void test(String s, int... nums){}
  • 一个形参列表中只能出现一个可变参数

1.3 案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class VarParameterExercise {
//编写一个main方法
public static void main(String[] args) {

//有三个方法,分别实现返回姓名和两门课成绩 (总分),返回姓名和三门课程成绩(总分),
//返回姓名和五门成绩(总分)。封装成一个可变参数的方法

HspMethod student = new HspMethod();
//int score = {67,78,90};
System.out.println(student.showScore("小明", 67,78,90));
}
}

class HspMethod {
//返回值类型 void
//方法名 showScore
//方法的形参(String , double... score)
//方法体 返回成绩总分 和姓名

public String showScore(String name, double... scores) {
int totalScores = 0;
for(int i = 0; i < scores.length; i++) {
totalScores+= scores[i];
}
return name + scores.length + "门课的成绩总分=" + totalScores;
}
}

四、作用域

1 全局变量

  • 即属性,作用域为整个类体
  • 可以不赋值直接使用,存在默认值

2 局部变量

  • 除属性外的其他变量,作用域为定义它的代码块中

  • 局部变量必须赋值,没有默认值

1
2
3
4
5
6
7
8
9
10
11
public void cry() {
//1. 局部变量一般是指在成员方法中定义的变量
//2. n和name就是局部变量
//3. n和name的作用域在cry方法中
int n = 10;
string name = "jack";
System.out.println("在cry中使用属性age=" + age);
}
public void eat() {
System.out.println("在eat中使用属性age="+ age);
}

3. 注意事项

  • 属性和局部变量可以重名,访问时遵循就近原则
  • 在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名
  • 属性生命周期较长,伴随着对象的创建而创建,伴随着对象的死亡而死亡
  • 局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而死亡,即在一次方法调用过程中
  • 作用域范围不同:
    • 全局变量/属性:可以被本类便用,或其他类使用(通过对象调用)
    • 局部变量:只能在本类中对应的方法中使用
  • 修饰符不同:
    • 全局变量/属性可以加修饰符
    • 局部变量不可以加修饰符

五、构造器(constructor)

  • 构造方法又叫构造器,是类的一种特殊的方法,它的主要作用是完成对新对象的初始化

1. 语法

1
2
3
4
5
6
7
8
9
10
11
12
[修饰符] 方法名(形参列表){}

//案例
Class Test{
String name;
//无参构造器
public Test(){}
//有参构造器
publci Test(String name){
this.name = name;
}
}
  • 构造器的修饰符可以默认
  • 构造器没有返回值
  • 方法名和类名字必须一样
  • 参数列表和成员方法一样的规则
  • 构造器的调用系统完成

2. 注意事项

  • 一个类可以定义多个不同的构造器,即构造器重载
  • 构造器名和类名要相同
  • 构造器没有返回值
  • 构造器是完成对象的初始化,并不是创建对象
  • 在创建对象时,系统自动的调用该类的构造方法
  • 如果没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器)
  • 一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,除非显式的定义一下

3. Javap反编译

  • 对class文件提供的字节代码进行反编译
1
2
javap <options> <classes>
javap -c -v 类名

六、对象创建流程

  • 加载Person类信息(Person.class),只会加载一次
  • 在堆中分配空间(地址)
  • 完成对象初始化
    • 默认初始化age=0 name=null
    • 显式初始化age=90 name=null
    • 构造器的初始化 age =20 name=小倩
  • 在对象在堆中的地址,返回给p(p是对象名,是对象的引用)

七、this关键字

  • 表示当前对象

1. 注意事项

  • this关键字可以用来访问本类的属性、方法、构造器
  • this用于区分当前类的属性和局部变量
  • 访问成员方法的语法:this.方法名(参数列表)
  • 访问构造器语法:this(参数列表),注意只能在构造器中使用(即只能在构造器中访问另外一个构造器,必须放在第一条语句)
  1. this不能在类定义的外部使用,只能在类定义的方法中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class T {
//细节:访问成员方法的语法:this.方法名(参数列表);
public void f1(){
System.out.println( "f1()方法..");
}
public void f2(){
System.out.print1n( "f2()方法..");
//调用本类的 f1
//第一种方式
f1();
//第二种方式
this.f1();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class T {
/*
细节:访问构造器语法:this(参数列表);
注意只能在构造器中使用(即只能在构造器中访问另外一个构造器)
注意:访问构造器语法:this(参数列表);必须放置第一条语句
*/
public T(){
//这里去访问T(String name,int age)构造器this( "jack", 100);
System.out.print1n("T()构造器");
}
public T(String name, int age) {
System.out.println("T(String name,int age)构造器");
}
}
1
2
3
4
5
6
7
//this关键字可以用来访问本类的属性
public void f3(){
//传统方式
System.out.println( "name=" + name + " num=" + num);
//也可以使用this访问属性
System.out.println( "name=" + this.name + " num=" + this.num);
}