面向对象

你懂面向对象的奥义吗?你能接收面向对象的奥义吗?你的行为准则能按面向对象来吗?你真的可以接收自己面向对象的处事方式吗?

就是不必亲历亲为,做到开箱即用

类和对象

面向对象和面向过程编程思想

编程思想其实就是编程思路,我们开发中2种经典的编程思想就是面向过程编程思想和面向对象编程思想.

  • 面向过程编程思想强调的是过程,必须清楚每一个步骤,然后按照步骤一步一步去实现
  • 面向对象编程思想强调的是对象, 通过调用对象的行为来实现功能,而不是自己一步一步的去操作实现。

举例对比2种编程思想

  • 洗衣服:

    • 面向过程:把衣服脱下来–>找一个盆–>放点洗衣粉–>加点水–>浸泡10分钟–>揉一揉–>清洗衣服–>拧干–>晾起来
    • 面向对象: 把衣服脱下来–>给女朋友去洗
  • 吃饭

    • 面向过程: 买菜—>洗菜—>切菜—->炒菜—>吃
    • 面向对象: 找个饭店–>20块钱

java程序上的区别:

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
// 需求:打印数组中所有的元素,打印格式为: [元素1,元素2,元素3,元素,...,元素n]
public class Test {
public static void main(String[] args) {
/*
面向过程编程思想
- 强调的是过程,必须清楚每一个步骤,然后按照步骤一步一步去实现

面向对象编程思想
- 强调的是对象, 通过调用对象的行为来实现功能,而不是自己一步一步的去操作实现。
*/
// 需求:打印数组中所有的元素,打印格式为: [元素1,元素2,元素3,元素,...,元素n]
// 1 定义一个数组,并且初始化数组中的元素
int[] arr = {10, 20, 30, 40, 50};

// 面向过程:
// 2.循环遍历数组
for (int i = 0; i < arr.length; i++) {
// 3.在循环中,获取遍历出来的元素
int e = arr[i];
// 4.判断该元素:
if (i == 0) {
// 4.1 如果该元素是第一个元素,打印格式: [ + 元素 + 逗号空格 不换行
System.out.print("[" + e + ", ");
} else if (i == arr.length - 1) {
// 4.2 如果该元素是最后一个元素,打印格式: 元素 + ]
System.out.println(e + "]");
} else {
// 4.3 如果该元素是中间元素,打印格式为: 元素 + 逗号空格 不换行
System.out.print(e + ", ");
}
}

System.out.println("==================================");

// 面向对象:
// jdk的api中有一个Arrays类toString()方法,可以帮助我们按照这种格式打印数组中的所有元素
System.out.println(Arrays.toString(arr));
}
}

类的概述

类的概述

  • 类是用来描述一类具有共同属性和行为事物的统称。所以其实类在客观世界里是不存在的,是抽象的,只是用来描述数据信息的。
  • 手机类—描述手机
  • 人类—-描述人

类的组成

  • 属性:该类事物的状态信息,在类中通过成员变量来体现(类中方法外的变量)
  • 行为:该类事物有什么功能,在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)

举例

  • 手机类
    • 属性:品牌、价格…。
    • 行为:打电话、发短信…。
  • 人类:
    • 属性: 姓名,年龄,性别….
    • 行为:吃饭,睡觉,…..

对象的概述

对象的概念

  • 对象是类的一个实例(并不是你的女朋友哈),具体存在的,看得见摸得着的,并且具备该类事物的属性和行为
    • 对象的属性:对象的属性具有特定的值
    • 对象的行为:对象可以操作的行为

举例

  • 对象: 你手上拿的这台手机
    • 属性:华为、1999。 对象的属性具体的值,类中的属性没有具体的值
    • 行为:使用打电话功能,使用发短信功能。对象可以使用行为

类和对象的关系

  • 类是对一类具有共同属性和行为的事物的统称,是抽象的

  • 对象是一类事物的具体实例,看得见,摸的着的,真实存在的实体,是具体的

  • 类是对象的抽象,对象是类的实体

    image-20230927191952597

类的定义

1
2
3
4
5
6
7
8
9
10
public class 类名 {// 定义一个类
// 类里面:属性(成员变量),行为(成员方法)
// 定义成员变量
数据类型 变量名1;
数据类型 变量名2;
...

// 定义成员方法
方法; 去掉static
}

举例

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
// 定义一个手机类,类名为(Phone),类的属性有:品牌(brand),价格(price),类的行为:打电话(call),发短信(sendMessage)
public class Phone {
//属性(成员变量): 数据类型 变量名;
/**
* 品牌
*/
String brand;
/**
* 价格
*/
double price;

//行为(成员方法): 去掉static

/**
* 打电话的功能
* @param phoneNum 电话号码
*/
public void call(String phoneNum){
System.out.println("正则给"+phoneNum+"打电话...");
}

/**
* 发短信的功能
* @param phoneNum 电话号码
* @param message 短信内容
*/
public void sendMessage(String phoneNum,String message){
System.out.println("正在给"+phoneNum+"发送短信,短信内容是:"+message);
}
}

对象的创建和使用

创建对象的格式:

  • 类名 对象名 = new 类名();
  • 类其实就是对象的数据类型,类是引用数据类型
  • 例: Phone p1 = new Phone (); 创建了一个手机对象(Phone类的对象)

对象的使用

  • 访问成员变量
    • 获取成员变量的值: 对象名.成员变量名
    • 给成员变量赋值: 对象名.成员变量名=值;
  • 访问成员方法
    • 对象名.成员方法();

案例演示

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
public class Phone {
//属性(成员变量): 数据类型 变量名;
/**
* 品牌
*/
String brand;
/**
* 价格
*/
double price;

//行为(成员方法): 去掉static

/**
* 打电话的功能
* @param phoneNum 电话号码
*/
public void call(String phoneNum){
System.out.println("正在给"+phoneNum+"打电话...");
}

/**
* 发短信的功能
* @param phoneNum 电话号码
* @param message 短信内容
*/
public void sendMessage(String phoneNum,String message){
System.out.println("正在给"+phoneNum+"发送短信,短信内容是:"+message);
}

// 为了演示有返回值的方法调用
public int show(String str){
System.out.println("有参数有返回值的方法:"+str);
return 100;
}
}

public class Test {
public static void main(String[] args) {
/*
对象的创建和使用:
对象的创建:
对象创建格式: 类名 对象名 = new 类名();
结论: 类其实也是一种数据类型,是引用数据类型
对象的使用:
访问成员变量:
给成员变量赋值: 对象名.成员变量名 = 值;
获取成员变量的值: 对象名.成员变量名

访问成员方法:
调用方法: 有返回值的方法,无返回值的方法
无返回值的方法:
直接调用: 对象名.方法名(实参);
有返回值的方法:
直接调用: 对象名.方法名(实参);
赋值调用: 数据类型 变量名 = 对象名.方法名(实参);
输出调用: System.out.println(对象名.方法名(实参));
*/
// 创建Phone类的对象
Phone p1 = new Phone();
// 给p1对象的brand成员变量赋值
p1.brand = "华为";
// 给p1对象的price成员变量赋值
p1.price = 999.8;

// 获取p1对象的brand成员变量的值
System.out.println(p1.brand);
// 获取p1对象的price成员变量的值
System.out.println(p1.price);

// 无返回值的成员方法
// 使用p1对象调用call方法
p1.call("10086");
// 使用p1对象调用sendMessage方法
p1.sendMessage("10086","请问一下联通的客服电话号码是多少?");

System.out.println("==============================");
// 有返回值的方法
// 直接调用
p1.show("张三");

// 赋值调用
int res = p1.show("李四");// 100
System.out.println("res:"+res);// 100

// 输出调用
System.out.println(p1.show("java"));// 100

/*
之前访问变量
int num;
num = 10;
System.out.println(num);*/
/*
之前访问访问
方法分类: 无参数无返回值,有参数无返回值,有参数有返回值,无参数有返回值
调用方法: 有返回值的方法,无返回值的方法
无返回值的方法:
直接调用: 方法名(实参);
有返回值的方法:
直接调用: 方法名(实参);
赋值调用: 数据类型 变量名 = 方法名(实参);
输出调用: System.out.println(方法名(实参));
*/
}
}

学生对象-练习

需求

  • 首先定义一个学生类,然后定义一个学生测试类,在学生测试类中通过对象完成成员变量和成员方法的使用

分析

  • 定义学生类
    • 成员变量:姓名,年龄…
    • 成员方法:学习,做作业…
  • 测试类
    • 创建main方法,在main 方法中创建学生对象
    • 使用学生对象访问成员变量和访问成员方法

实现

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
public class Student {
// 成员变量: 属性
/**
* 姓名
*/
String name;
/**
* 年龄
*/
int age;

// 成员方法: 行为
/**
* 学习的功能
*/
public void study(){
System.out.println("学生正在学习Java...");
}

/**
* 做作业的功能
*/
public void doHomeWork(){
System.out.println("学生正在做作业敲代码...");
}
}


public class Test {
public static void main(String[] args) {
// 需求:首先定义一个学生类,然后定义一个学生测试类,在学生测试类中通过对象完成成员变量和成员方法的使用
// 创建学生对象
Student stu = new Student();

// 访问成员变量
stu.name = "冰冰";
stu.age = 18;
System.out.println(stu.name+","+stu.age);// 冰冰,18

// 访问成员方法
stu.study();
stu.doHomeWork();

}
}

成员变量默认值

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
public class Student {
/**
* 姓名
*/
String name;
/**
* 年龄
*/
int age;
/**
* 分数
*/
double score;
char c;
}


public class Test {
public static void main(String[] args) {
// 变量的要使用一定要赋值
//int num;// 局部变量:定义在方法中的变量
//System.out.println(num);// 编译报错,局部变量没有默认值

/*
成员变量的默认值:
整数类型: 默认值是0
小数类型: 默认值是0.0
布尔类型: 默认值是false
字符类型: 默认值是不可见字符 '\u0000'
引用类型: 默认值是null
*/
// 创建Student对象
Student stu = new Student();
// 访问成员变量
System.out.println(stu.name);// null
System.out.println(stu.age);// 0
System.out.println(stu.score);// 0.0
System.out.println("="+stu.c+"=");
}
}

单个对象内存图

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
public class Student {
// 成员变量: 属性
/**
* 姓名
*/
String name;
/**
* 年龄
*/
int age;

// 成员方法: 行为
/**
* 学习的功能
*/
public void study(){
System.out.println("学生正在学习Java...");
}

/**
* 做作业的功能
*/
public void doHomeWork(){
System.out.println("学生正在做作业敲代码...");
}
}

public class Test {
public static void main(String[] args) {
// 创建Student对象
Student stu = new Student();
System.out.println(stu);// 十六进制数地址值

// 访问成员变量
stu.name = "冰冰";
stu.age = 18;
System.out.println(stu.name+","+stu.age);

// 访问成员方法
stu.study();
stu.doHomeWork();
}
}

image-20200905121918328

字节码文件会被加载到方法区。堆内存中的存的是成员方法的地址,通过地址找到方法区的方法。

多个对象内存图

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
public class Student {
// 成员变量: 属性
/**
* 姓名
*/
String name;
/**
* 年龄
*/
int age;

// 成员方法: 行为
/**
* 学习的功能
*/
public void study(){
System.out.println("学生正在学习Java...");
}

/**
* 做作业的功能
*/
public void doHomeWork(){
System.out.println("学生正在做作业敲代码...");
}
}


public class Test {
public static void main(String[] args) {
// 创建Student对象 shift+f6+fn 批量修改名称
Student stu1 = new Student();
System.out.println(stu1);// 十六进制数地址值

// 访问成员变量
stu1.name = "冰冰";
stu1.age = 18;
System.out.println(stu1.name+","+stu1.age);// 冰冰,18

// 访问成员方法
stu1.study();
stu1.doHomeWork();

System.out.println("==========================");

Student stu2 = new Student();

System.out.println(stu2.name+","+stu2.age);// null,0
stu2.study();

}
}

绘制内存图

image-20200905123805520

注意:

  • 多个对象在堆内存中,都有不同的内存划分,成员变量存储在各自对象的内存区域中,成员方法多个对象共用的一份
  • 凡是new就会重新在堆区开辟一块新空间
  • 对象和对象之间的关系是相互独立的

多个变量指向相同对象内存图

查看程序案例

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
public class Student {
// 成员变量: 属性
/**
* 姓名
*/
String name;
/**
* 年龄
*/
int age;

// 成员方法: 行为
/**
* 学习的功能
*/
public void study(){
System.out.println("学生正在学习Java...");
}

/**
* 做作业的功能
*/
public void doHomeWork(){
System.out.println("学生正在做作业敲代码...");
}
}
public class Test {
public static void main(String[] args) {
// 创建Student对象
Student stu1 = new Student();

// 访问学生对象的成员变量
stu1.name = "冰冰";
stu1.age = 18;
System.out.println(stu1.name + "," + stu1.age);// 冰冰,18

// 访问学生对象的成员方法
stu1.study();

System.out.println("============================");
// 定义一个Student类型的变量,并把之前创建的学生对象赋值给该变量
Student stu2 = stu1;

// 再使用新的变量访问成员变量
System.out.println(stu2.name + "," + stu2.age);// 冰冰,18
// 再使用新的变量访问成员方法
stu2.study();
}
}

绘制内存图

image-20200905144926634

注意点:

  • 当多个对象的引用指向同一个内存空间(变量所记录的地址值是一样的)
  • 只要有任何一个对象修改了内存中的数据,随后,无论使用哪一个对象进行数据获取,都是修改后的数据。
  • 引用类型传递的是地址值

成员变量和局部变量的区别

image-20230927191943296

  • 类中位置不同:成员变量(类中方法外)局部变量(方法内部或方法声明上)
  • 内存中位置不同:成员变量(堆内存)局部变量(栈内存)
  • 生命周期不同:成员变量(随着对象的存在而存在,随着对象的消失而消失)局部变量(随着方法的调用而存在,随着方法的调用完毕而消失)
  • 初始化值不同:成员变量(有默认初始化值)局部变量(没有默认初始化值,必须先定义,赋值才能使用)
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
public class Car {
String color;// 成员变量

// 成员方法
public void drive(){
int speed = 80;
System.out.println("汽车正在以"+speed+"迈的速度行驶...");
}
}


public class Test {

/*
成员变量和局部变量的区别:
定义的位置不同: 成员变量定义在类中方法外,局部变量定义在方法中
在内存中的位置不同: 成员变量是在堆区,局部变量是在栈区
生命周期不同:
成员变量是随着对象的创建而存在,随着对象的销毁而销毁
局部变量是随着方法的调用而存在,随着方法调用完毕而销毁
默认值不同:
成员变量有默认值
局部变量没有默认值,不赋值不能直接使用
*/
public static void main(String[] args) {
// 创建Car对象
Car car = new Car();
// 调用方法
car.drive();
}
}

image-20200806121538241

封装

private关键字

private的含义

  • 概述: private是一个权限修饰符,代表最小权限。
  • 特点:
    • 可以修饰成员变量和成员方法。
    • 被private修饰后的成员变量和成员方法,只在本类中才能访问。

private的使用格式

1
2
3
4
5
6
7
// private关键字修饰成员变量
private 数据类型 变量名 ;

// private关键字修饰成员方法
private 返回值类型 方法名(参数列表){
代码
}

案例

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
public class Student {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;


private void study(){
System.out.println("正在学习java");
}

public void show(){
// 只能在本类中访问
System.out.println(name+","+age);
}
}


public class Test {
public static void main(String[] args) {
/*
private关键字:
概述:是一个权限修饰符,最小的权限
特点:
1.private可以修饰成员变量和成员方法
2.被private修饰后的成员变量和成员方法,只在本类中才能访问。
使用:
修饰成员变量格式: private 数据类型 变量名;
修饰成员方法格式: private 返回值类型 方法名(形参列表){方法体}
*/
// 创建Student类对象
Student stu1 = new Student();

// 直接访问stu1的成员变量
//stu1.name = "冰冰";// 编译报错,因为没有访问权限
//stu1.age = 18;// 编译报错,因为没有访问权限

// 直接访问stu1的成员方法
//stu1.study();// 编译报错,因为没有访问权限

}
}

为什么要对属性进行封装

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
public class Student {
/**
* 姓名
*/
String name;
/**
* 年龄
*/
int age;
}

public class Test {
public static void main(String[] args) {
/*
为什么要对属性进行封装:
通过对象名直接访问成员变量的方式来对属性赋值,会存在数据安全隐患,应该怎么解决呢?
解决方式: 不让外界直接访问成员变量(也就是要对属性进行封装\隐藏)
对成员变量隐藏的步骤:
1.使用private关键字修饰成员变量
2.提供公共的访问方法:
给成员变量赋值的公共方法(set方法)
获取成员变量值的公共方法(get方法)
*/
// 创建Student对象
Student stu1 = new Student();

// 访问成员变量
stu1.name = "冰冰";
// 通过对象名直接访问成员变量的方式来对属性赋值,会存在数据安全隐患,应该怎么解决呢?
stu1.age = -18;
System.out.println(stu1.name + "," + stu1.age);// 冰冰,-18

}
}

set和get方法

set和get方法的介绍

  • 由于属性使用了private关键字修饰,在其他类中无法直接访问,所以得提供公共的访问方法,我们把这张方法叫做set和get方法

    • get方法: 提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
    • set方法: 提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰

set和get方法的书写

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
public class Student {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;

// 提供给成员变量赋值的方法-set方法
public void setName(String s){
name = s;
}

public void setAge(int a){
if (a < 0 || a > 150){
age = -1;
System.out.println("您的数据不合法!");
}else{
age = a;
}
}
// 提供获取成员变量值的方法-get方法
public String getName(){
return name;
}

public int getAge(){
return age;
}
}

public class Test {
public static void main(String[] args) {
/*
通过对象名直接访问成员变量的方式来对属性赋值,会存在数据安全隐患,应该怎么解决呢?
解决方式: 使用private修饰,并提供公共的访问方法
*/
// 创建Student对象
Student stu1 = new Student();

// 访问成员变量
// 隐藏属性后的方式
stu1.setName("冰冰");
stu1.setAge(-18);
System.out.println(stu1.getName()+","+stu1.getAge());// 冰冰,-1

// 没有隐藏属性之前的方式
//stu1.name = "冰冰";
//stu1.age = -18;
//System.out.println(stu1.name + "," + stu1.age);// 冰冰,-18
}
}

this关键字

问题

我们发现 setXxx 方法中的形参名字并不符合见名知意的规定,那么如果修改与成员变量名一致,是否就见名知意了呢?代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class Student {
private String name;
private int age;

public void setName(String name) {
name = name;
}

public void setAge(int age) {
age = age;
}
}

经过修改和测试,我们发现新的问题,成员变量赋值失败了。也就是说,在修改了setXxx() 的形参变量名后,方法并没有给成员变量赋值!这是由于形参变量名与成员变量名重名,导致成员变量名被隐藏,方法中的变量名,无法访问到成员变量,从而赋值失败。所以,我们只能使用this关键字,来解决这个重名问题。

this的含义和使用

  • this含义: this代表当前调用方法的引用,哪个对象调用this所在的方法,this就代表哪一个对象

  • this关键字其主要作用是区分同名的局部变量和成员变量

    • 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
    • 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
  • this的使用格式:

1
this.成员变量名
  • 使用 this 修饰方法中的变量,解决成员变量被隐藏的问题,代码如下:
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
public class Student {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;

// 提供给成员变量赋值的方法-set方法
public void setName(String name){
this.name = name;
}

public void setAge(int age){
if (age < 0 || age > 150){
this.age = -1;
System.out.println("您的数据不合法!");
}else{
this.age = age;
}
}
// 提供获取成员变量值的方法-get方法
public String getName(){
return name;
}

public int getAge(){
return age;
}
}

public class Test {
public static void main(String[] args) {
/*
问题1:set方法的形参名不能起到知名达意(不符合标识符命名规范)
解决1:把形参名修改成符合命名规范
问题2:set方法的形参名改为符合命名规范后,发现set方法无法给成员变量赋值
解决2:使用this关键字来区别同名的成员变量和局部变量
格式: this.成员变量名
this表示谁: 哪个对象调用this所在的方法,this就表示哪个对象

结论:
1.如果成员方法中有与成员变量同名的局部变量,那么就需要使用this关键字来区分
2.如果成员方法中没有与成员变量同名的局部变量,那么就不需要使用this关键字来区分(直接使用成员变量即可)
*/
// 创建Student对象
Student stu1 = new Student();

// 访问成员变量
// 隐藏属性后的方式
stu1.setName("冰冰");
stu1.setAge(-18);
System.out.println(stu1.getName()+","+stu1.getAge());// 冰冰,-1

Student stu2 = new Student();
stu2.setName("空空");

}
}

小贴士:方法中只有一个变量名时,默认也是使用 this 修饰,可以省略不写。

this内存原理

代码

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
public class Test {
public static void main(String[] args) {
/*
问题1:set方法的形参名不能起到知名达意(不符合标识符命名规范)
解决1:把形参名修改成符合命名规范
问题2:set方法的形参名改为符合命名规范后,发现set方法无法给成员变量赋值
解决2:使用this关键字来区别同名的成员变量和局部变量
格式: this.成员变量名
this表示谁: 哪个对象调用this所在的方法,this就表示哪个对象

结论:
1.如果成员方法中有与成员变量同名的局部变量,那么就需要使用this关键字来区分
2.如果成员方法中没有与成员变量同名的局部变量,那么就不需要使用this关键字来区分(直接使用成员变量即可)
*/
// 创建Student对象
Student stu1 = new Student();

// 访问成员变量
// 隐藏属性后的方式
stu1.setName("冰冰");
stu1.setAge(-18);
System.out.println(stu1.getName()+","+stu1.getAge());// 冰冰,-1

Student stu2 = new Student();
stu2.setName("空空");
System.out.println(stu2.getName()+","+stu2.getAge());// 空空,0

}
}

public class Student {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;

// 提供给成员变量赋值的方法-set方法
public void setName(String name){
this.name = name;
}

public void setAge(int age){
if (age < 0 || age > 150){
this.age = -1;
System.out.println("您的数据不合法!");
}else{
this.age = age;
}
}
// 提供获取成员变量值的方法-get方法
public String getName(){
return name;
}

public int getAge(){
return age;
}
}

image-20200905162906642

封装概述

封装概述

  • 是面向对象三大特征之一(封装,继承,多态)
  • 是面向对象编程语言对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界是无法直接操作的

封装原则

  • 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问
  • 例如:成员变量使用private修饰,提供对应的getXxx()/setXxx()方法

封装好处

  • 通过方法来控制成员变量的操作,提高了代码的安全性
  • 把代码用方法进行封装,提高了代码的复用性

构造方法

  • 概述

构造方法是一种特殊的方法,主要是完成对象的创建和对象数据的初始化

  • 格式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 空参构造方法
    修饰符 类名(){

    }

    // 有参构造方法
    修饰符 类名(参数列表){
    // 方法体
    }
  • 特点:

    • 构造方法的写法上,方法名与它所在的类名相同
    • 构造方法没有返回值,所以不需要返回值类型,甚至不需要void
  • 示例代码:

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
public class Student {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;

// 构造方法
public Student(){
System.out.println("空参方法");
}

public Student(String name,int age){
this.name = name;
this.age = age;
}

public String getName(){
return name;
}

public int getAge(){
return age;
}
}

public class Test {
public static void main(String[] args) {
/*
构造方法:
概述:构造方法是一个特殊的方法,主要用来创建对象并给属性赋值.
定义:
无参构造方法:
权限修饰符 类名(){
}
有参构造方法:
权限修饰符 类名(形参列表){
给属性赋值
}
特点:
1.构造方法没有返回值类型,连void不能写
2.构造方法的名字就是类名
3.通过new来调用构造方法
使用: 通过new来调用
*/
// 通过调用空参构造方法创建对象
Student stu1 = new Student();
System.out.println(stu1.getName()+","+stu1.getAge());// null,0

// 通过调用有参构造方法创建对象
Student stu2 = new Student("冰冰",18);
System.out.println(stu2.getName()+","+stu2.getAge());// 冰冰,18

}
}

注意事项

  • 构造方法的创建

    • 如果没有定义构造方法,系统将给出一个默认的无参数构造方法
    • 如果定义了构造方法,系统将不再提供默认的构造方法
  • 构造方法可以重载,既可以定义参数,也可以不定义参数。

  • 示例代码

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
public class Student {
/**
* 姓名
*/
private String name;
/**
* age
*/
private int age;

// 空参构造方法
public Student(){

}
// 有参构造方法(满参构造方法)
public Student(String name,int age){
this.name = name;
this.age = age;
}

// 有参构造方法
public Student(String name){
this.name = name;
}

// 有参构造方法
public Student(int age){
this.age = age;
}

public void setAge(int age){
this.age = age;
}

public int getAge(){
return age;
}
}

public class Test {
public static void main(String[] args) {
/*
构造方法的注意事项:
1.构造方法没有返回值,连void都不能写
2.构造方法名和类名一致
3.如果一个类没有定义构造方法,系统会自动生成一个空参构造方法
4.如果一个类定义了构造方法,系统就不会自动生成一个空参构造方法
5.构造方法可以重载
6.构造方法只能给属性赋值一次,而set方法可以给属性赋值无数次
因为调用构造方法,就会创建一个新的对象

*/
//调用空参构造方法创建对象
Student stu1 = new Student();

// 通过有参构造方法创建对象
Student stu2 = new Student("冰冰",18);
Student stu3 = new Student("冰冰",18);

System.out.println(stu2.getAge());// 18
// 通过set方法给属性赋值
stu2.setAge(19);
System.out.println(stu2.getAge());// 19
stu2.setAge(20);
System.out.println(stu2.getAge());// 20

}
}

小结

1
2
3
4
5
6
7
8
构造方法的注意事项:
- 构造方法的创建
- 如果没有定义构造方法,系统将给出一个默认的无参数构造方法
- 如果定义了构造方法,系统将不再提供默认的构造方法
- 构造方法只能给属性赋值一次,不能重复赋值,可以谁有set方法给属性重复赋值
- 构造方法可以重载,既可以定义参数,也可以不定义参数。
- 定义构造方法的时候,不要写返回值,连void都不能有
- 定义构造方法的时候,构造方法名和类名一定要一致

标准类制作

标准类的组成

JavaBean 是 Java语言编写类的一种标准规范。符合JavaBean 的类,要求类必须是公共的,属性使用private修饰,并且具有无参数的构造方法,提供用来操作成员变量的setget 方法。

1
2
3
4
5
6
7
8
9
public class ClassName{
//成员变量 private
//构造方法
//无参构造方法【必须】
//满参构造方法【建议】
//getXxx()
//setXxx()
//成员方法
}

案例演示

  • 需求:定义标准学生类,要求分别使用空参和有参构造方法创建对象,空参创建的对象通过setXxx赋值,有参创建的对象直接赋值,并通过show方法展示数据。
  • 示例代码:
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
public class Student {
// 成员变量--private
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;

// 空参构造方法 alt+insert--->Constructor
public Student() {
}

// 满参构造方法(建议)
public Student(String name, int age) {
this.name = name;
this.age = age;
}

// set\get方法 alt+insert---> setter and getter
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

// 成员方法
public void show(){
System.out.println(name+","+age);
}

}

API

什么是API

​ API (Application Programming Interface) :应用程序编程接口。Java API是一本程序员的字典 ,是JDK中提供给我们使用的类的说明文档。这些类将底层的代码实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习这些类如何使用即可。所以我们可以通过查询API的方式,来学习Java提供的类,并得知如何使用它们。

  • API其实就是jdk中核心类库的说明文档
  • 对于jdk中的核心类库只需要知道如何使用,无须关心他是如何实现的

使用步骤

  1. 打开API帮助文档。
  2. 点击显示,找到索引,看到输入框。
  3. 你要找谁?在输入框里输入,然后回车。
  4. 看包。java.lang下的类不需要导包,其他需要。
  5. 看类的解释和说明。
  6. 看构造方法。
  7. 看成员方法。

演示API的使用

  • 打开帮助文档

image-20230927192006278

  • 找到索引选项卡中的输入框

    image-20230927192013235

  • 在输入框中输入Random

image-20230927192017438

  • 看类在哪个包下

image-20230927192021307

  • 看类的描述

image-20230927192024872

  • 看构造方法

image-20230927192029139

  • 看成员方法

image-20230927192032716

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
public class Test {
public static void main(String[] args) {
/*
api的使用步骤:
1.打开api文档
2.点击显示
3.点击索引,在输入框中,输入要查找的类\接口
4.查看类的包 如果在java.lang包就不需要导包,其余都需要导包
5.查看类的解释说明
6.查看类的构造方法
7.查看类的成员方法

举例: Scanner类
1.查看类的包 java.util 导包
2.查看类的解释说明 是一个文本扫描器,可以扫描基本类型的数据和字符串
3.查看类的构造方法 Scanner(InputStream source)
4.查看类的成员方法
byte nextByte()
short nextShort()
short nextInt()
Long nextLong()
boolean nextBoolean()
double nextDouble()
float nextFloat()

String nextLine() 可以获取一行字符串 空格,回车,tab键都可以获取
String next() 可以获取单个字符串 空格,回车,tab键都不可以获取

*/
Scanner sc = new Scanner(System.in);
/*System.out.println("请输入一个整数:");
int num = sc.nextInt();
System.out.println(num);*/

/* System.out.println("请输入一个小数:");
double numD = sc.nextDouble();
System.out.println(numD);*/

//System.out.println("请输入一个字符串:");
/* String str = sc.nextLine();
System.out.println(str);*/

/*String str = sc.next();
System.out.println(str);*/

System.out.println("请输入年龄:");
int age = sc.nextInt();
System.out.println("年龄:"+age);// 18

System.out.println("请输入姓名:");
String name = sc.next();
System.out.println("姓名:"+name);

//String name = sc.nextLine();
//System.out.println("姓名:"+name);
}
}

对象的内存图

image-20230927191842883

注意点:

  • 只要是new对象就会在堆区开辟一块独立的空间
  • 只要调用方法,方法就会被加载进栈
  • 只要方法执行完毕,方法就会被弹栈

匿名对象

什么是匿名对象:就是指”没有名字”的对象。

1
2
3
4
5
6
有名字的对象:
Student stu = new Student();
stu.show();
stu.study();
匿名对象:
new Student();

特点:匿名对象只能使用一次

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
public class Test {
public static void main(String[] args) {
/*
匿名对象:
概述:没有名字的对象
特点:匿名对象只能使用一次
使用场景:当某个类的对象只需要使用一次的时候,就可以使用该类的匿名对象
例如:方法的参数,方法的返回值
*/
// 创建对象
Student stu1 = new Student("热巴",18);// 有名字的对象
stu1.show();
stu1.show();

System.out.println("==================================");
//匿名对象
new Student("热巴",18).show();// 没有名字的对象
new Student("热巴",18).show();// 没有名字的对象

System.out.println("==================================");
// 调用method1方法
Student stu2 = new Student("热巴",18);// 0x11901
method1(stu2);// 有名字的对象传参
method1(new Student("热巴",18));// 匿名对象的方式传参数

System.out.println("==================================");
Student stu3 = method2();// 0x11908
stu3.show();// 丽颖,18

}

public static void method1(Student stu){// 0x11901
stu.show();
}


public static Student method2(){
//Student stu = new Student("丽颖",18);// 0x11908
//return stu;// 0x11908

return new Student("丽颖",18);
}


}

继承

如果满足 is a的时候,可以考虑时候继承

继承概述

为什么要有继承

现实生活中,为什么要有继承?

image-20230927191920500

程序中为什么要有继承?

image-20230927191929108

继承的含义

继承:在java中指的是“一个类”可以“继承自”“另一个类”。 “被继承的类”叫做: 父类/超类/基类,”继承其他类的类”叫做:子类。继承后,“子类”中就“拥有”了“父类”中所有的成员(成员变量、成员方法)。 “子类就不需要再定义了”。

继承的好处

  1. 提高代码的复用性(减少代码冗余,相同代码重复利用)。
  2. 使类与类之间产生了关系。

继承的格式

通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:

1
2
3
4
5
6
7
class 父类 {
...
}

class 子类 extends 父类 {
...
}

需要注意:Java是单继承的,一个类只能继承一个直接父类,并且满足is-a的关系,例如:Dog is a Animal, Student is a Person

继承的演示

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
人类:
public class Person {
// 成员变量
String name;
int age;

// 功能方法
public void eat(){
System.out.println("吃东西...");
}

public void sleep(){
System.out.println("睡觉...");
}
}
老师类: extends 人类
public class Teacher extends Person {
double salary;// 独有的属性
public void teach(){}// 独有的方法
}
学生类: extends 人类
public class Student extends Person{

}
Dog: extends 人类
public class Dog extends Person{// 语法上是可以的,但不符合现实逻辑(不符合is a的关系)

}
测试:
public class Test {
public static void main(String[] args) {
Teacher t = new Teacher();
System.out.println(t.name);
System.out.println(t.age);
t.eat();
t.sleep();
}
}

继承后成员访问规则

继承后构造方法的访问规则

  • 构造方法不能被继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Fu {
    // 构造方法
    Fu(){}
    Fu(String name,int age){}
    }

    class Zi extends Fu{

    }

    public class Test {
    public static void main(String[] args) {
    /*
    构造方法的访问规则:父类的构造方法不能被子类继承
    私有成员的访问规则:
    非私有成员的访问规则:
    */
    //Zi zi = new Zi("张三",18);// 编译报错,因为没有继承
    }
    }

继承后私有成员的访问规则

  • 父类的“私有成员”可以被子类继承,但子类不能被直接访问。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class Fu{
    private int num = 100;//私有成员,只能在父类内部使用。
    private void method(){
    System.out.println("私有成员方法");
    }
    }
    public class Zi extends Fu{

    }
    public class Demo {
    public static void main(String[] args) {
    Zi z = new Zi();
    System.out.println(z.num);// 编译错误
    z.method();// 编译错误
    }
    }

继承后非私有成员的访问规则

  • 当通过“子类”访问非私有成员时,先在子类中找,如果找到就使用子类的,找不到就继续去“父类”中找。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Fu{
    int money = 100;
    public void method(){
    System.out.println("Fu 类中的成员方法method");
    }
    }
    public class Zi extends Fu{
    int money = 1;
    public void method(){
    System.out.println("Zi 类中的成员方法method");
    }
    }
    public class Demo{
    public static void main(String[] args){
    Zi z = new Zi();
    System.out.println(z.money);//1
    z.method();// Zi 类中的成员方法method
    }
    }

方法重写

方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现

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
class Fu{
public void method(){
System.out.println("Fu method");
}
}
class Zi extends Fu{

@Override
public void method() {
System.out.println("Zi method");
}

public void show(){
System.out.println("Zi show");
}
}
public class Test {
public static void main(String[] args) {
/*
方法重写:
方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),
会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
注意事项:
1.一定要是父子类关系
2.子类中重写的方法返回值类型,方法名,参数列表一定要和父类一模一样
3.子类中重写的方法可以使用@Override注解进行标识,如果不是重写的方法使用@Override注解标识就会报错
建议开发中重写的方法使用@Override注解标识,这样可以提高代码的可读性
4.子类重写父类的方法的访问权限不能低于父类的访问权限
访问权限: public > protected > 默认(空) > private
*/
Zi zi = new Zi();
zi.method();
}
}

注意事项

  • 方法重写是发生在子父类之间的关系。

  • 子类方法重写父类方法,返回值类型、方法名和参数列表都要一模一样。

  • 子类方法重写父类方法,必须要保证权限大于等于父类权限。

    • 访问权限从大到小: public protected (默认) private
  • 使用@Override注解,检验是否重写成功,重写注解校验!

    • 建议重写方法都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错!

使用场景

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
class Fu{
public void sport(){
System.out.println("Fu 运动的方式跑步");
}

public void run(){
System.out.println("Fu 第1圈");
System.out.println("Fu 第2圈");
System.out.println("Fu 第3圈");
}
}

class Zi extends Fu{
// 子类方法的实现和父类方法的实现完全不同
@Override
public void sport() {
System.out.println("Zi 运动的方式游泳");
}

// 子类方法的实现要保留父类方法的功能,但要在父类功能的基础之上额外增加功能
@Override
public void run() {
// 让父类的方法执行=====复制父类的代码过来
super.run();// 调用父类的方法

// 额外增加的代码
System.out.println("Zi 第4圈");
System.out.println("Zi 第5圈");
System.out.println("Zi 第6圈");
System.out.println("Zi 第7圈");
System.out.println("Zi 第8圈");
System.out.println("Zi 第9圈");
System.out.println("Zi 第10圈");
}
}

public class Test {
public static void main(String[] args) {
/*
方法重写的使用场景:
当父类的方法无法满足子类的需求的时候,子类就会去重写父类的方法
*/
// 创建子类对象
Zi zi = new Zi();
// 调用运动的方法
zi.sport();
// 调用跑步的方法
zi.run();


}
}

this和super关键字

this和super关键字的介绍

  • this:存储的“当前对象”的引用;
    • this可以访问:本类的成员属性、成员方法、构造方法;
  • super:存储的“父类对象”的引用;
    • super可以访问:父类的成员属性、成员方法、构造方法;

this关键字的三种用法

  • this访问本类成员变量: this.成员变量

    1
    2
    3
    4
    5
    6
    7
    8
    public class Student{
    String name = "张三";
    public void show(){
    String name = "李四";
    System.out.println("name = " + name);// 李四
    System.out.println("name = " + this.name);// 张三
    }
    }
  • this访问本类成员方法: this.成员方法名();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Student{
    public void show(){
    System.out.println("show方法...");
    this.eat();
    }
    public void eat(){
    System.out.println("eat方法...");
    }
    }
  • this访问本类构造方法: this()可以在本类的一个构造方法中,调用另一个构造方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Student{
    public Student(){
    System.out.println("空参构造方法...");
    }

    public Student(String name) {
    this();//当使用this()调用另一个构造方法时,此代码必须是此构造方法的第一句有效代码。
    System.out.println("有参构造方法...");
    }
    }
    public class Demo {
    public static void main(String[] args) {
    Student stu2 = new Student();
    }
    }

super关键字的三种用法

  • super访问父类的成员变量: super.父类成员变量名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Fu{
    int num = 100;
    }

    class Zi extends Fu{
    int num = 10;

    public void show(){
    int num = 1;
    System.out.println("局部变量num:"+num);// 1
    System.out.println("Zi 类中的num:"+this.num);// 10
    System.out.println("Fu 类中的num:"+super.num);// 100

    }
    }
  • super访问父类的成员方法: super.成员方法名();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Fu{
    public void method1(){
    System.out.println("Fu method1...");
    }
    }

    class Zi extends Fu{
    public void show(){
    // 访问父类的method1方法
    super.method1();
    }

    @Override
    public void method1(){
    super.method1();// 调用父类的method1方法
    System.out.println("Zi method1...");
    }
    }
  • super访问父类的构造方法: super()

    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
    public class Fu{
    public Fu(){
    System.out.println("Fu 类的空参构造方法..");
    }
    public Fu(String name, int age) {
    System.out.println("Fu 类的有参构造方法..");
    }
    }
    public class Zi extends Fu{
    public Zi(){
    super();// 调用父类的空参构造方法
    System.out.println("Zi 类的空参构造方法..");
    }
    public Zi(String name,int age){
    super(name,age);// 调用父类的有参构造方法
    System.out.println("Zi 类的有参构造方法..");
    }
    }
    public class Demo {
    public static void main(String[] args) {
    Zi zi = new Zi();
    System.out.println("----------------------");
    Zi z2 = new Zi("刘德华", 17);
    }
    }

小结

  • this关键字的三种用法:
       this可以访问本类的成员变量: this.成员变量         一般用来区分同名的成员变量和局部变量
       this可以访问本类的成员访问: this.成员方法名(实参);   
       this可以访问本类的构造方法:
            空参构造: this();
            有参构造: this(实参);
                注意:
                     1.只能在本类的构造方法中使用this调用其他构造方法
                     2.在本类的构造方法中使用this调用其他构造方法,必须放在该构造方法的第一行,否则会报错
                     3.两个构造方法不能使用this同时相互调用
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    - ```java
    super关键字的三种用法:
    super可以访问父类的成员变量: super.成员变量 一般用来区分父子类中同名的成员变量
    super可以访问父类的成员方法: super.成员方法(实参); 一般用来在子类中访问父类的成员方法
    super可以访问父类的构造方法:
    空参构造: super();
    有参构造: super(实参);
    注意:
    1.子类的构造方法默认会调用父类的空参构造方法
    2.super访问父类的构造方法,可以用来初始化从父类继承过来的属性
    3.在子类的构造方法中,使用super调用父类的构造方法,必须放在子类构造方法的第一行

super的注意事项

  • super访问成员变量和成员方法: 优先去父类中找,如果有就直接使用,如果没有就去爷爷类中找,如果有,就用,依次类推…

    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
    class Ye{
    int num = 10;
    public void method(){
    System.out.println("Ye method");
    }
    }
    class Fu extends Ye{
    int num = 100;
    public void method(){
    System.out.println("Fu method");
    }
    }
    class Zi extends Fu{
    int num = 1000;
    public void show(){
    System.out.println(super.num);
    super.method();
    }
    }

    public class Test {
    public static void main(String[] args) {

    Zi zi = new Zi();
    zi.show();
    }
    }
  • 子类的构造方法默认会调用父类的空参构造方法,如果父类中的没有空参构造方法,只定义了有参构造方法,会编译报错

    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
    class Fu1{
    public Fu1(){
    System.out.println("Fu1 空参构造");
    }

    public Fu1(int num){
    System.out.println("Fu1 有参构造");
    }
    }

    class Zi1 extends Fu1{
    public Zi1(){
    // super();
    }

    public Zi1(int num){
    // super();
    }
    }

    // 问题: super调用父类的构造方法有什么用?
    class Person{
    private String name;
    private int age;

    public Person(String name, int age) {
    this.name = name;
    this.age = age;
    }

    public void show(){
    System.out.println(name+","+age);
    }
    }

    class Student extends Person{
    public Student(String name,int age){
    super(name,age);
    }
    }

    public class Test2 {
    public static void main(String[] args) {
    /*
    super的注意事项二
    1.子类的构造方法默认会调用父类的空参构造方法
    2.如果父类中的没有空参构造方法,只定义了有参构造方法,会编译报错
    问题: super调用父类的构造方法有什么用?
    结果: 为了在创建子类对象的时候,初始化从父类继承过来的属性
    */
    // 通过调用子类的空参构造方法,创建子类对象
    // Zi1 zi = new Zi1();

    // 通过调用子类的有参构造方法,创建子类对象
    //Zi1 zi = new Zi1(100);

    // 创建Student类的对象
    Student stu = new Student("张三", 18);
    stu.show();

    }
    }
  • 子类构造方法中使用super调用父类的构造方法,是为了在创建子类对象的时候,初始化从父类继承过来的属性

继承体系对象的内存图

  • 继承体系内存图原理—父类空间优先于子类对象产生

    在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构造方法调用时,一定先调用父类的构造方法。

  • 书写继承案例

    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
    class Fu{
    int num = 10;
    int numFu = 100;
    public void method(){
    System.out.println("Fu method...");
    }
    }
    class Zi extends Fu{
    int num = 20;
    int numZi = 200;
    public void method(){
    System.out.println("Zi method...");
    }
    public void show(){
    int num = 30;
    System.out.println("局部变量num:"+num);// 30
    System.out.println("本类成员变量num:"+this.num);// 20
    System.out.println("父类成员变量num:"+super.num);// 10
    // 访问本类的method方法
    this.method();// Zi method...
    // 访问父类的method方法
    super.method();// Fu method...
    }
    }
    public class Test {
    public static void main(String[] args) {
    Zi zi = new Zi();
    zi.show();
    }
    }


  • 根据案例绘制内存图

    image-20200906123749033

继承的特点

  1. Java只支持单继承,不支持多继承。但是可以多层继承,java中所有类都是直接或者间接继承Object,所有类都是Object类的子类
1
2
3
4
5
6
7
8
9
10
11
12
13
  // 一个类只能有一个父类,不可以有多个父类。
class A {

}
class B {

}
class C1 extends A {// ok

}
class C2 extends A, B {// error

}
  1. 一个类只能有一个父类,但可以有多个子类。
1
2
3
4
5
6
7
8
9
10
  // A可以有多个子类
class A {

}
class C1 extends A {

}
class C2 extends A {

}
  1. 可以多层继承。
1
2
3
4
5
6
7
8
9
class A /*extends Object*/{// 爷爷   默认继承Object类

}
class B extends A {// 父亲

}
class C extends B {// 儿子

}

补充: 顶层父类是Object类。所有的类默认继承Object,作为父类。

class A {} 默认继承Object类 直接继承Object类

class B extends A{} B的父类就是A,但是A的父类是Object类 间接继承Object类

java中所有类都是直接或者间接继承Object,所有类都是Object类的子类

抽象类

抽象类的概述和定义

抽象类的概述

  • 概述: 使用abstract关键字修饰的类就是抽象类
  • 特点: 这种类不能被创建对象,它就是用来做父类的,被子类继承的

抽象类的定义

  • 格式:

    1
    2
    3
    修饰符 abstract class 类名{

    }
  • 例如:

    1
    2
    3
    public abstract class Person{

    }

抽象类中的成员

  • 成员变量
  • 成员方法
  • 构造方法
  • 抽象方法
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
public abstract class Animal {
// 成员变量
private String name;
private int age;
// 构造方法
public Animal(){

}
public Animal(String name,int age){
this.name = name;
this.age = age;
}
// 成员方法

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public void show(){
System.out.println(name+","+age);
}
// 抽象方法 ---??

}

public class Test {
public static void main(String[] args) {
/*
抽象类:
概述:使用abstract关键字修饰的类就是抽象类
特点:抽象类不能创建对象,主要用来给子类继承的
格式:
public abstract class 类名{
成员变量
构造方法
成员方法
抽象方法
}
抽象类成员:
成员变量
构造方法
成员方法
抽象方法
普通类和抽象类的区别:
1.普通类可以创建对象,抽象类不可以创建对象
2.普通类没有抽象方法,抽象类有抽象方法
*/
//Animal anl1 = new Animal();// 编译报错,抽象类不能创建对象
//Animal anl2 = new Animal("旺财",2);// 编译报错,抽象类不能创建对象
}
}

抽象方法的概述和定义

抽象方法的概述

  • 没有方法体,使用abstract修饰的方法就是抽象方法

抽象方法的定义

1
2
3
修饰符 abstract 返回值类型 方法名(形参列表);
例如:
public abstract void work();

抽象方法的作用: 强制要求子类重写的

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
public abstract class Animal {
// 成员变量
private String name;
private int age;
// 构造方法
public Animal(){

}
public Animal(String name, int age){
this.name = name;
this.age = age;
}
// 成员方法

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

// 所有子类显示信息的方法实现都是一样的
public void show(){
System.out.println(name+","+age);
}

// 抽象方法 ---
// 因为所有子类吃东西的方法实现不一样
public abstract void eat();

}


public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
}

package com.itheima.demo14_抽象方法的概述和定义;

/**
* @Author:pengzhilin
* @Date: 2020/9/6 14:55
*/
public class Cat extends Animal {

@Override
public void eat() {
System.out.println("猫吃鱼...");
}
}

public class Test {
public static void main(String[] args) {
/*
抽象方法:
概述: 使用abstract修饰,并且没有方法体的方法
格式: 修饰符 abstract 返回值类型 方法名(形参列表);
抽象方法的使用场景:如果父类中某个方法,所有子类都有不同的实现,那么就可以把该方法定义为抽象方法
抽象方法的作用: 强制要求子类重写

*/
Dog d = new Dog();
d.eat();

Cat c = new Cat();
c.eat();
}
}

抽象类的注意事项

  • 抽象类不能被创建对象,就是用来做“父类”,被子类继承的。
  • 抽象类不能被创建对象,但可以有“构造方法”——为成员变量初始化。
  • 抽象类中可以没有抽象方法,但抽象方法必须定义在抽象类中
  • 子类继承抽象类后,必须重写抽象类中所有的抽象方法,否则子类必须也是一个抽象类
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
abstract class Animal{
private String name;
private int age;

public Animal() {
}

public Animal(String name, int age) {
this.name = name;
this.age = age;
}

public void show(){
System.out.println(name+","+age);
}

// 抽象类没有抽象方法
}
class Dog extends Animal{
public Dog() {
super();
}

public Dog(String name, int age) {
super(name, age);
}
}

abstract class Person{
// 抽象方法
public abstract void eat();
public abstract void drink();

}

//普通子类继承抽象类后,必须重写抽象类中所有的抽象方法
class Student extends Person{

@Override
public void eat() {
// ...
}

@Override
public void drink() {
// ...
}
}

//抽象子类继承抽象类后,可以不用重写抽象类中的抽象方法
abstract class Teacher extends Person{
@Override
public void eat() {
// ... 可以重写...
}
}


public class Test {
public static void main(String[] args) {
/*
抽象类的注意事项:
- 抽象类不能被创建对象,就是用来做“父类”,被子类继承的。
- 抽象类不能被创建对象,但可以有“构造方法”——为成员变量初始化。
- 抽象类中可以没有抽象方法,但抽象方法必须定义在抽象类中(抽象类中不一定有抽象方法,但抽象方法一定在抽象类中)
- 子类继承抽象类后,必须重写抽象类中所有的抽象方法,否则子类必须也是一个抽象类

*/
// 抽象类不能被创建对象,就是用来做“父类”,被子类继承的。
//Animal anl = new Animal();

// 抽象类不能被创建对象,但可以有“构造方法”——为成员变量初始化。
Dog d = new Dog("旺财", 2);
d.show();// 旺财,2
}
}

模板设计模式

设计模式概述

  • 设计模式就是解决一些问题时的固定思路,也就是代码设计思路经验的总结。

模板设计模式概述

  • 针对某些情况,在父类中指定一个模板,然后根据具体情况,在子类中灵活的具体实现该模板
1
2
3
4
5
6
7
8
9
public abstract class Person{
// 有方法体的方法: 通用模板
public void sleep(){
System.out.println("两眼一闭,就睡觉...");
}

// 没有方法体的方法(抽象方法): 填充模板(要子类重新实现的)
public abstract void eat();
}
  • 抽象类体现的就是模板设计思想模板是将通用的东西在抽象类中具体的实现,而模板中不能决定的东西定义成抽象方法,让使用模板(继承抽象类的类)的类去重写抽象方法实现需求

模板模式的实现步骤

  • 定义抽象父类作为模板
  • 在父类中定义”模板方法”— 实现方法(通用模板)+抽象方法(填充模板)
  • 子类继承父类,重写抽象方法(填充父类的模板)
  • 测试类:
    • 创建子类对象,通过子类调用父类的“实现的方法”+ “子类重写后的方法” e

案例演示

假如我现在需要定义新司机和老司机类,新司机和老司机都有开车功能,开车的步骤都一样,只是驾驶时的姿势有点不同,新司机:开门,点火,双手紧握方向盘,刹车,熄火老司机:开门,点火,右手握方向盘左手抽烟,刹车,熄火。那么这个时候我们就可以将固定流程写到父类中,不同的地方就定义成抽象方法,让不同的子类去重写

分析:

  • 司机类
    • 开车方法: 确定实现–通用模板
      • 开门
      • 点火
      • (姿势)
      • 刹车
      • 熄火
    • 姿势方法: 不确定实现–填充模板
  • 新司机类继承司机类,重写姿势方法
  • 老司机类继承司机类,重写姿势方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 父类
public abstract class Driver {
// 开车方法 通用模板
public void driveCar(){
System.out.println("开门");
System.out.println("点火");
// 姿势??
ziShi();
System.out.println("刹车");
System.out.println("熄火");
}

// 姿势方法 填充模板
public abstract void ziShi();
}

现在定义两个使用模板的司机:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class NewDriver extends Driver {
@Override
public void ziShi() {
System.out.println("双手紧握方向盘");
}
}

public class OldDriver extends Driver {
@Override
public void ziShi() {
System.out.println("右手握方向盘左手抽烟");
}
}

编写测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {

public static void main(String[] args) {
// 创建新司机对象
NewDriver d1 = new NewDriver();
d1.driveCar();

// 创建老司机对象
OldDriver d2 = new OldDriver();
d2.driveCar();
}

}

运行效果

image-20230927191911123

可以看出,模板模式的优势是,模板已经定义了通用架构,使用者只需要关心自己需要实现的功能即可!非常的强大!

final关键字

final: 不可改变。可以用于修饰类、方法和变量。

  • 类:被修饰的类,不能被继承。
  • 方法:被修饰的方法,不能被重写。
  • 变量:被修饰的变量,就只能赋值一次,不能被重新赋值。

修饰类

格式如下:

1
2
3
4
5
6
7
8
9
修饰符 final class 类名 {

}
例如:
public final class FinalClassFu {
}
public class FinalClassZi /*extends FinalClassFu*/ {
// FinalClassFu类被final修饰了,所以不能被继承
}

查询API发现像 public final class Stringpublic final class Mathpublic final class Scanner 等,很多我们学习过的类,都是被final修饰的,目的就是供我们使用,而不让我们所以改变其内容。

修饰方法

格式如下:

1
2
3
修饰符 final 返回值类型 方法名(参数列表){
//方法体
}

重写被 final修饰的方法,编译时就会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class FinalMethodFu {
public final void show(){

}
}
public class FinalMethodZi extends FinalMethodFu {

/*@Override
public void show() {

}*/
// 无法重写父类中的show方法,因为父类中的show方法被final修饰了
}

修饰变量

局部变量——基本类型

基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改。代码如下:

1
2
3
4
5
6
7
public class FinalDemo1 {
public static void main(String[] args) {
// final修饰基本数据类型
final int NUM = 10;
// NUM = 20;// 编译报错,final修饰的变量只能赋值一次,不能重复赋值
}
}

局部变量——引用类型

引用类型的局部变量,被final修饰后,只能指向一个对象,地址不能再更改。但是不影响对象内部的成员变量值的修改,代码如下:

1
2
3
4
5
6
7
8
9
public class FinalDemo2 {
public static void main(String[] args) {
// 引用类型
final Student stu = new Student("张三",18);
//stu = new Student("李四",19);// 编译报错
stu.setAge(19);
}
}

成员变量

成员变量涉及到初始化的问题,初始化方式有两种,只能二选一:

  1. 显示初始化;

    1
    2
    3
    public class FinalVariable {
    final int NUM1 = 10;
    }
  2. 构造方法初始化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class FinalVariable {
    final int NUM2;
    public FinalVariable(int NUM2){
    this.NUM2 = NUM2;
    }
    public FinalVariable(){
    this.NUM2 = 10;
    }
    }

被final修饰的常量名称,一般都有书写规范,所有字母都大写

static关键字

之前咋们写main方法的时候,使用过了一个static关键字,接下来我们来学习一下static关键字

static关键字概述

static是一个静态修饰符关键字,表示静态的意思,可以修饰成员变量和成员方法以及代码块。

static关键字的使用

static修饰成员变量

static 修饰成员变量时,该变量称为类变量。该类的每个对象都共享同一个类变量的值。任何对象都可以更改该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作。

定义格式

1
static 数据类型 变量名; 

静态成员变量的访问方式

1
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
public class Person {
// 非静态变量
String name;// 姓名
// 静态变量
static String country;// 国籍

// 构造方法

public Person() {
}

public Person(String name, String country) {
this.name = name;
this.country = country;
}
}

public class Test {
public static void main(String[] args) {
// 创建Person对象
Person p1 = new Person("张三", "中国");
System.out.println(p1.name+","+p1.country);// 张三,中国

System.out.println("=======================");

// 创建Person对象
Person p2 = new Person();
// 没有使用static修饰country
// System.out.println(p2.name+","+p2.country);// null,null
// 使用static修饰country
System.out.println(p2.name+","+p2.country);// null,中国

System.out.println("=======================");

System.out.println(Person.country);// 中国
}
}

image-20200708153852521

static修饰成员方法

被static修饰的方法会变成静态方法,也称为类方法,该静态方法可以使用类名直接调用。

格式

1
2
3
修饰符 static 返回值类型 方法名 (参数列表){ 
// 执行语句
}

访问方式

1
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
public class Person {
// 非静态方法
public void method1(){
System.out.println("Person method1...");
}

// 静态方法
public static void method2(){
System.out.println("Person method2...");
}
}
public class Test {
public static void main(String[] args) {
/*
static修饰成员方法:
格式:修饰符 static 返回值类型 方法名(形参列表){方法体}
特点:被static修饰的成员方法叫做静态成员方法
使用:
对象名.静态成员方法名(实参);
类名.静态成员方法名(实参); ----->推荐
*/
Person p = new Person();
p.method2();

// 类名.静态成员方法名(实参);
Person.method2();

}
}

静态方法调用的注意事项:

  • 静态方法中不能出现this关键字
  • 静态方法中只能直接访问静态成员变量和静态成员方法
  • 静态方法中不能直接访问非静态成员变量和非静态成员方法
  • 非静态方法中可以直接访问一切成员变量和成员方法
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
public class ChinesePeople {
// 非静态成员变量
String name;// 姓名
// 静态成员变量
static String country;// 国籍

// 非静态方法
public void method1(){
System.out.println("非静态 method2方法");
}

public void method2(){
// 非静态方法中可以直接访问一切成员变量和成员方法
System.out.println(name);
System.out.println(country);
method1();
method4();

System.out.println("非静态 method2方法");
}

// 静态方法
public static void method3(){
//静态方法中不能直接访问非静态成员变量和非静态成员方法
//System.out.println("非静态的成员变量:"+name);// 编译报错
//method1();// 编译报错

//静态方法中只能直接访问静态成员变量和静态成员方法
System.out.println("静态成员变量:"+country);
method4();

// 静态方法中不能出现this关键字
//System.out.println(this.name);// 编译报错
//System.out.println(this.country);// 编译报错
System.out.println("非静态 method3方法");
}

public static void method4(){
System.out.println("非静态 method4方法");
}
}

public class Test {
public static void main(String[] args) {
/*
概述: 被static修饰的方法就是静态方法,否则就是非静态方法
static修饰成员方法: 在方法的返回值类型前面加上static
访问静态方法:
对象名.静态方法名(参数); 不推荐
类名.静态方法名(参数); 推荐
注意事项:
1.静态方法中只能直接访问静态成员变量和静态成员方法
2.静态方法中不能直接访问非静态成员变量和非静态成员方法
3.非静态方法中可以直接访问一切成员变量和成员方法
4.静态方法中不能出现this关键字
*/
ChinesePeople.method3();

//ChinesePeople p = new ChinesePeople();
//p.method2();

/*// 对象名.静态方法名(参数); 不推荐
ChinesePeople p1 = new ChinesePeople();
p1.method3();
p1.method4();

// 类名.静态方法名(参数); 推荐
ChinesePeople.method3();
ChinesePeople.method4();*/
}

}

以后开发中static的应用

概述

以后的项目中,通常会需要一些“全局变量”或者“全局的工具方法”,这些全局变量和方法,可以单独定义在一个类中,并声明为static(静态)的,可以很方便的通过类名访问

例如:

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
public class Utils {
// "全局变量"
public static final int WIDTH = 800;
public static final int HEIGHT = 800;


// "全局方法"
// 找int数组中的最大值
public static int getArrayMax(int[] arr){
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if(arr[i] > max){
max = arr[i];
}
}
return max;
}
}

public class Test {
public static void main(String[] args) {
/*
以后的项目中,通常会需要一些“全局变量”或者“全局的工具方法”,这些全局变量和方法,
可以单独定义在一个类中,并声明为static(静态)的,可以很方便的通过类名访问

工具类
*/
System.out.println(Utils.width);
System.out.println(Utils.height);

int[] arr = {23,34,545,56};
System.out.println(Utils.getArrayMax(arr));
}
}

小结

1
2
3
4
5
6
7
8
9
10
11
static修饰成员方法:
格式: 在返回值类型前面加static关键字
使用: 类名.静态方法名(实参);
注意事项:
1.静态方法中不能出现this
2.静态方法中只能直接访问静态成员变量和成员方法
3.非静态方法中可以直接访问一切成员变量和成员方法
static修饰成员变量:
格式: static 数据类型 变量名;
使用; 类名.静态成员变量名
特点; 被static修饰的变量会被该类的所有对象共享

接口

概述

引用数据类型除了类其实还有接口,接下来学习接口的概述

概述: 接口是Java语言中的一种引用类型,是方法的”集合”,所以接口的内部主要就是定义方法,包含常量,抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法(jdk9)。

接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。

public class 类名{}–>.class

public interface 接口名{}->.class

引用数据类型:数组,类,接口。

接口的使用,它不能创建对象,但是可以被实现(implements ,类似于被继承)。一个实现接口的类(可以看做是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。

小结

  • 接口是java语言中的一种引用数据类型
  • 接口中的成员:
    • 常量(jdk7及其以前)
    • 抽象方法(jdk7及其以前)
    • 默认方法和静态方法(jdk8额外增加)
    • 私有方法(jdk9额外增加)
  • 定义接口使用interface关键字—接口编译后产生class文件
  • 接口不能创建对象,需要使用实现类实现接口(类似于继承),实现接口的类叫做实现类(子类)

定义格式

格式

1
2
3
4
5
6
7
public interface 接口名称 {
// 常量(jdk7及其以前)
// 抽象方法(jdk7及其以前)
// 默认方法(jdk8)
// 静态方法(jdk8)
// 私有方法(jdk9)
}

image-20230325144156770

案例

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
public interface IA {
// 常量(jdk7及其以前) 使用public static final关键字修饰,这三个关键字都可以省略
public static final int NUM1 = 10;
int NUM2 = 20;

// 抽象方法(jdk7及其以前) 使用public abstract关键字修饰,这2个关键字都可以省略
public abstract void method1();
void method2();

// 默认方法(jdk8) 使用public default关键字修饰,public可以省略,default不可以省略
public default void method3(){
System.out.println("默认方法 method3");
}

// 静态方法(jdk8) 使用public static关键字修饰,public可以省略,static不可以省略
public static void method4(){
System.out.println("静态方法 method4");
}
// 私有方法(jdk9) 使用private关键字修饰,private不可以省略
private static void method5(){
System.out.println("私有静态方法 method5");
}

private void method6(){
System.out.println("私有非静态方法 method6");
}
}

实现接口

实现概述

类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字。

实现格式

  • 类可以实现一个接口,也可以同时实现多个接口。

    • 类实现接口后,必须重写接口中所有的抽象方法,否则该类必须是一个“抽象类”。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public interface IA{
      public void show1();
      }
      public interface IB{
      public void show2();
      }
      public class Zi implements IA ,IB{
      public void show1(){
      }
      public void show2(){
      }
      }
  • 类可以在“继承一个类”的同时,实现一个、多个接口;

    1
    2
    3
    4
    5
    6
    public class Fu{}
    public interface IA{}
    public interface IB{}
    public class Zi extends Fu implements IA,IB{//一定要先继承,后实现
    }

接口中成员的访问特点

接口中成员访问特点概述

1
2
3
4
5
6
接口中成员的访问特点:
接口中的常量: 主要是供接口直接使用
接口中的抽象方法: 供实现类重写的
接口中的默认方法: 供实现类继承的(实现类中可以直接调用,实现类对象也可以直接调用)
接口中的静态方法: 只供接口直接调用,实现类继承不了
接口中的私有方法: 只能在接口中直接调用,实现类继承不了

案例演示

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
public interface IA {
// 常量
public static final int NUM = 10;

// 抽象方法
public abstract void method1();

// 默认方法
public default void method2(){
//method4();
//method5();
System.out.println("IA 接口中的默认方法method2");
}

// 静态方法
public static void method3(){
//method5();
System.out.println("IA 接口中的静态方法method3");
}

// 私有方法
private void method4(){
System.out.println("IA 接口中的私有方法method4");
}

private static void method5(){
System.out.println("IA 接口中的私有方法method5");
}
}


public class Imp implements IA {
// 重写接口的抽象方法
@Override
public void method1() {
System.out.println("实现类重写IA接口中的抽象方法");
}

// 重写接口的默认方法
@Override
public void method2() {
System.out.println("实现类重写IA接口中的默认方法");
}

}

public class Test {
public static void main(String[] args) {
/*
接口中成员的访问特点:
常量:主要是供接口名直接访问
抽象方法:就是供实现类重写
默认方法:就是供实现类重写或者实现类对象直接调用
静态方法: 只供接口名直接调用
私有方法: 只能在本接口中调用

*/
// 访问接口常量
System.out.println(IA.NUM);// 10 推荐
//System.out.println(Imp.NUM);// 10 不推荐 常量被实现类继承了

// 创建实现类对象调用方法
Imp imp = new Imp();

// 访问抽象方法
imp.method1();

// 访问默认方法
imp.method2();

// 接口名访问静态方法
IA.method3();
//Imp.method3();// 编译报错,没有继承
}
}

小结

  • 接口中成员访问特点:
    • 常量:主要是供接口名直接访问
    • 抽象类:就是用来给实现类重写的
    • 默认方法:只供实现类重写或者实现类对象直接调用
    • 静态方法:只供接口名直接调用
    • 私有方法:只能在本接口中调用

多实现时的几种冲突情况

公有静态常量的冲突

  • 实现类不继承冲突的常量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface A{
public static final int NUM1 = 10;
}
interface B{
public static final int NUM1 = 20;
public static final int NUM2 = 30;
}
class Imp implements A,B{

}
public class Test {
public static void main(String[] args) {
/*
公有静态常量的冲突: 如果多个接口中有相同的常量,那么实现类就无法继承
*/
//System.out.println(Imp.NUM1);// 编译报错,无法访问
System.out.println(Imp.NUM2);// 30
}
}

公有抽象方法的冲突

  • 实现类只需要重写一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface A{
public abstract void method();
}
interface B{
public abstract void method();
}
class Imp implements A,B{
@Override
public void method() {
System.out.println("实现类重写");
}
}
public class Test {
public static void main(String[] args) {
/*
公有抽象方法的冲突:实现类只需要重写一个
*/
}
}

公有默认方法的冲突

  • 实现类必须重写一次最终版本
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
interface A{
public default void method(){
System.out.println("A 接口的默认方法method");
}
}
interface B{
public default void method(){
System.out.println("B 接口的默认方法method");
}
}
class Imp implements A,B{
@Override
public void method() {
System.out.println("实现类重写的默认方法");
}
}
public class Test {
public static void main(String[] args) {
/*
公有默认方法的冲突:实现类必须重写一次最终版本
*/
Imp imp = new Imp();
imp.method();
}
}

公有静态方法的冲突

  • 静态方法是直接属于接口的,不能被继承,所以不存在冲突

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    interface A{
    public static void method(){
    System.out.println("A接口的静态方法method");
    }
    }
    interface B{
    public static void method(){
    System.out.println("B接口的静态方法method");
    }
    }
    class Imp implements A,B{

    }
    public class Test {
    public static void main(String[] args) {
    /*
    公有静态方法的冲突:静态方法是直接属于接口的,不能被继承,所以不存在冲突
    */
    }
    }

私有方法的冲突

  • 私有方法只能在本接口中直接使用,不存在冲突

小结

1
2
3
4
5
6
多实现时的几种冲突情况:
- 公有静态常量的冲突:实现类不继承冲突的常量
- 公有抽象方法的冲突:实现类只需要重写一个
- 公有默认方法的冲突:实现类必须重写一次最终版本
- 公有静态方法的冲突:静态方法是直接属于接口的,不能被继承,所以不存在冲突
- 私有方法的冲突:私有方法只能在本接口中直接使用,不存在冲突

接口和接口的关系

接口与接口之间的关系

  • 接口可以“继承”自另一个“接口”,而且可以“多继承”。

    1
    2
    3
    4
    interface IA{}
    interface IB{}
    interface IC extends IA,IB{//是“继承”,而且可以“多继承”
    }

接口多继承接口的冲突情况

公有静态常量的冲突
  • 子接口无法继承父接口中冲突的常量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface A{
public static final int NUM1 = 10;
}
interface B{
public static final int NUM1 = 20;
public static final int NUM2 = 30;
}
interface C extends A,B{

}
public class Test {
public static void main(String[] args) {
/*
公有静态常量的冲突: 子接口无法继承父接口中冲突的常量
*/
//System.out.println(C.NUM1);// 编译报错,说明无法继承
System.out.println(C.NUM2);// 30
}
}

公有抽象方法冲突
  • 子接口只会继承一个有冲突的抽象方法
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
interface A{
public abstract void method();
}
interface B{
public abstract void method();
}
interface C extends A,B{

}
class Imp implements C{
@Override
public void method() {
System.out.println("实现接口的抽象方法");
}
}

public class Test {
public static void main(String[] args) {
/*
公有抽象方法的冲突:子接口只会继承一个有冲突的抽象方法
*/
Imp imp = new Imp();
imp.method();
}
}

公有默认方法的冲突
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
interface A{
public default void method(){
System.out.println("A 接口中的默认方法method");
}
}
interface B{
public default void method(){
System.out.println("B 接口中的默认方法method");
}
}

interface C extends A,B{

@Override
public default void method() {
System.out.println("重写父接口中的method方法");
}
}

class Imp implements C{

}

public class Test {
public static void main(String[] args) {
/*
公有默认方法的冲突:子接口中必须重写一次有冲突的默认方法
面试题:
实现类重写接口中的默认方法,不需要加default
子接口重写父接口中的面容方法,必须加default
*/
Imp imp = new Imp();
imp.method();// 重写父接口中的method方法
}
}

公有静态方法和私有方法
  • 不冲突,因为静态方法是直接属于接口的,只能使用本接口直接访问,而私有方法只能在接口中访问,也没有冲突
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface A{
public static void method(){
System.out.println("A 接口的静态方法method");
}
}
interface B{
public static void method(){
System.out.println("B 接口的静态方法method");
}
}
interface C extends A,B{

}
public class Test {
public static void main(String[] args) {
/*
公有静态方法的冲突: 不存在冲突,因为静态方法是直接属于接口的,只供本接口直接调用
*/
//C.method();// 编译报错,因为没有继承
}
}

小结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  - 接口与接口之间的关系: 继承关系
单继承: A接口继承B接口
多继承: A接口同时继承B接口,C接口,...
多层继承: A接口继承B接口,B接口,继承C接口
格式:
public interface 接口名 extends 接口名1,接口名2,...{

}

- 接口多继承时的冲突情况
- 公有静态常量的冲突:子接口无法继承父接口中冲突的常量
- 公有抽象方法的冲突:子接口只会继承一个有冲突的抽象方法
- 公有默认方法的冲突:子接口中必须重写一次有冲突的默认方法(注意要加default)
- 公有静态方法和私有方法的冲突:
不冲突,因为静态方法是直接属于接口的,只能使用本接口直接访问,而私有方法只能在接口中访问,也没有冲突

面试题:
实现类重写接口中的默认方法,不需要加default
子接口重写父接口中的默认方法,必须加default

实现类继承父类又实现接口时的冲突

父类和接口的公有静态常量的冲突

  • 子类无法继承有冲突的常量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Fu{
public static final int NUM1 = 10;
public static final int NUM2 = 100;
}
interface A{
public static final int NUM1 = 20;

}
class Zi extends Fu implements A{

}
public class Test {
public static void main(String[] args) {
/*
公有静态常量的冲突:子类无法继承有冲突的常量
*/
//System.out.println(Zi.NUM1);// 编译报错
System.out.println(Zi.NUM2);

}
}

父类和接口的抽象方法冲突

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
abstract class Fu{
public abstract void method();
}
interface A{
public abstract void method();
}
class Zi extends Fu implements A{
@Override
public void method() {
System.out.println("Zi 重写有冲突的抽象方法");
}
}
public class Test {
public static void main(String[] args) {
/*
公有抽象方法的冲突:子类必须重写一次有冲突的抽象方法
*/
Zi zi = new Zi();
zi.method();
}
}

父类和接口的公有默认方法的冲突

  • 优先访问父类的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Fu{
public void method(){
System.out.println("Fu 类中的默认方法method");
}
}
interface A{
public default void method(){
System.out.println("A 接口中的默认方法method");
}
}
class Zi extends Fu implements A{

}
public class Test {
public static void main(String[] args) {
/*
公有默认方法的冲突:优先访问父类的
*/
Zi zi = new Zi();
zi.method();// Fu 类中的默认方法method
}
}

父类和接口的公有静态方法

  • 只会访问父类的静态方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Fu{
public static void method(){
System.out.println("Fu 类中的静态方法method");
}
}
interface A{
public static void method(){
System.out.println("A 接口中的静态方法method");
}
}
class Zi extends Fu implements A{

}
public class Test {
public static void main(String[] args) {
/*
公有静态方法的冲突:只会访问父类的静态方法
*/
Zi.method();
}
}

父类和接口的私有方法

  • 不存在冲突

小结

1
2
3
4
5
6
实现类继承父类又实现接口时的冲突:
- 公有静态常量的冲突:子类无法继承有冲突的常量
- 公有抽象方法的冲突:子类必须重写一次有冲突的抽象方法
- 公有默认方法的冲突:优先访问父类的
- 公有静态方法的冲突:只会访问父类的静态方法
- 私有方法的冲突: 不存在冲突

抽象类和接口的练习

需求

通过实例进行分析和代码演示抽象类和接口的用法。

1、举例:

​ 犬: —抽象父类

​ 行为:吼叫;吃饭;

​ 缉毒犬:继承犬类,实现缉毒接口

​ 行为:吼叫;吃饭;缉毒;

​ 缉毒接口:

​ 缉毒

  • 如果一个父类中的某个方法,所有子类都有不同的实现,那么该方法就应该定义成抽象方法,所以该父类就是抽象类 (父类一般都是抽象类)
  • 如果某个功能是一个类额外增加的,那么就可以把这个额外的功能定义到接口中,再这个类去实现

分析

​ 由于犬分为很多种类,他们吼叫和吃饭的方式不一样,在描述的时候不能具体化,也就是吼叫和吃饭的行为不能明确。当描述行为时,行为的具体动作不能明确,这时,可以将这个行为写为抽象行为,那么这个类也就是抽象类。

​ 可是有的犬还有其他额外功能,而这个功能并不在这个事物的体系中 , 例如 : 缉毒犬。缉毒的这个功能有好多种动物都有 , 例如 : 缉毒猪 , 缉毒鼠。我们可以将这个额外功能定义接口中 ,让缉毒犬继承犬且实现缉毒接口 , 这样缉毒犬既具备犬科自身特点也有缉毒功能。

  • 额外的功能—> 在接口中定义,让实现类实现
  • 共性的功能—> 在父类中定义,让子类继承

实现

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
// 抽象父类
public abstract class Dog {
public abstract void houJiao();
public abstract void eat();
}


public interface JiDu {
public abstract void jiDu();
}

public class JiDuDog extends Dog implements JiDu{
@Override
public void houJiao() {
System.out.println("缉毒犬找到了毒品,开始吼叫,汪汪汪....");
}

@Override
public void eat() {
System.out.println("缉毒之前,开始吃骨头...");
}

@Override
public void jiDu() {
System.out.println("吃完东西后,开始使用鼻子查找毒品....");
}
}

public class Test {
public static void main(String[] args) {
// 创建缉毒狗对象
JiDuDog jd = new JiDuDog();
jd.eat();
jd.jiDu();
jd.houJiao();
}
}

小结

  • 额外的功能—> 在接口中定义,让实现类实现
    • 如果可以确定的通用功能,使用默认方法
    • 如果不能确定的功能,使用抽象方法
  • 共性的功能—> 在父类中定义,让子类继承
    • 如果可以确定的通用功能,使用默认方法
    • 如果不能确定的功能,使用抽象方法

新JDK接口使用思路:

image-20240302100707189

多态

概述

多态是继封装、继承之后,面向对象的第三大特性。

生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。

定义

  • 多态: 是指同一行为,对于不同的对象具有多个不同表现形式。
  • 程序中多态: 是指同一方法,对于不同的对象具有不同的实现.

前提条件【重点】

  1. 继承或者实现【二选一】
  2. 父类引用指向子类对象\接口引用指向实现类对象【格式体现】
  3. 方法的重写【意义体现:不重写,无意义】

实现多态

多态的体现:父类的引用指向它的子类的对象

1
2
父类类型 变量名 = new 子类对象;
变量名.方法名();

父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

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
class Animal{
public void eat(){
System.out.println("吃东西");
}
}

class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
}

class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
}

public class Test1 {
public static void main(String[] args) {
// 父类引用指向子类对象
Animal anl = new Dog();// 多态
anl.eat();// 狗吃骨头...

Animal anl1 = new Cat();
anl1.eat();// 猫吃鱼...
}
}

多态时访问成员的特点

image-20230927191853841

  • 多态时成员变量的访问特点
    • 编译看左边,运行看左边
      • 简而言之:多态的情况下,访问的是父类的成员变量
  • 多态时成员方法的访问特点
    • 非静态方法:编译看左边,运行看右边
      • 简而言之:编译的时候去父类中查找方法,运行的时候去子类中查找方法来执行
    • 静态方法:编译看左边,运行看左边
      • 简而言之:编译的时候去父类中查找方法,运行的时候去父类中查找方法来执行
  • 注意:多态的情况下是无法访问子类独有的方法

除了非静态方法是编译看父类,运行看子类,其余都是看父类

演示代码:

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
class Animal{
int num = 10;
public void method1(){
System.out.println("Animal 非静态method1方法");
}
public static void method2(){
System.out.println("Animal 静态method2方法");
}
}
class Dog extends Animal{
int num = 20;

public void method1(){
System.out.println("Dog 非静态method1方法");
}

public static void method2(){
System.out.println("Dog 静态method2方法");
}
}

public class Test {
public static void main(String[] args) {
Animal anl = new Dog();
System.out.println(anl.num);// 10

anl.method1();// Dog 非静态method1方法
anl.method2();// Animal 静态method2方法
}
}

多态的表现形式

普通父类多态

1
2
3
4
5
6
7
public class Fu{}
public class Zi extends Fu{}
public class Demo{
public static void main(String[] args){
Fu f = new Zi();//左边是一个“父类”
}
}

抽象父类多态

1
2
3
4
5
6
7
public abstract class Fu{}
public class Zi extends Fu{}
public class Demo{
public static void main(String[] args){
Fu f = new Zi();//左边是一个“父类”
}
}

父接口多态

1
2
3
4
5
6
7
public interface A{}
public class AImp implements A{}
public class Demo{
public static void main(String[] args){
A a = new AImp();
}
}

多态的应用场景

变量多态 —–> 意义不大

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
class Animal{
public void eat(){
System.out.println("吃东西...");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test {
public static void main(String[] args) {
// 变量多态: 父类类型的变量指向子类类型的对象
// 如果变量的类型为父类类型,该变量就可以接收该父类类型的对象或者其所有子类对象
Animal anl = new Dog();
anl.eat();

anl = new Cat();
anl.eat();
}
}

形参多态—-> 常用

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
class Animal{
public void eat(){
System.out.println("吃东西...");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test {
public static void main(String[] args) {
// 形参多态:参数类型为父类类型,该参数就可以接收该父类类型的对象或者其所有子类对象
Dog d = new Dog();
method(d);

System.out.println("===============================");

Cat c = new Cat();
method(c);
}

// 需求: 定义一个方法,带有一个参数,该参数可以接收Animal类对象以及Animal类的所有子类对象
// method(d); ====实参赋值给形参的时候==> Animal anl = new Dog();
// method(c); ====实参赋值给形参的时候==> Animal anl = new Cat();
public static void method(Animal anl){
anl.eat();
}

}

返回值多态—> 常用

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
class Animal{
public void eat(){
System.out.println("吃东西...");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test {
public static void main(String[] args) {
// 返回值多态:如果返回值类型为父类类型,那么就可以返回该父类类型的对象或者其所有子类对象
Animal anl = method();
anl.eat();
}

public static Animal method(){
//return new Animal();
//return new Dog();
return new Cat();
}

public static Animal method1(){
if (1==1){
// 条件1成立
return new Animal();
}else if (2==2){
// 条件2成立
return new Dog();
}else{
// 否则
return new Cat();
}
}
}

多态的好处和弊端

好处

  • 提高了代码的扩展性

弊端

  • 多态的情况下,无法访问子类独有的方法或者成员变量,因为多态成员访问的特点是,编译看父类

示例代码

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
class Animal{
public void eat(){
System.out.println("吃东西...");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}

// 特有的功能
public void lookHome(){
System.out.println("狗在看家...");
}
}
public class Test {
public static void main(String[] args) {
/*
多态的好处和弊端:
好处:提高代码的复用性
弊端:无法访问子类独有的方法或者成员变量,因为多态成员访问的特点是,编译看父类
*/
// 父类的引用指向子类的对象
Animal anl = new Dog();
anl.eat();
//anl.lookHome();// 编译报错,因为多态成员访问的特点是,编译看父类,而父类中没有子类独有的功能
}
}

引用类型转换

向上转型

子类类型向父类类型向上转换的过程,这个过程是默认的。

1
Aniaml anl = new Cat();  

向下转型

父类类型向子类类型向下转换的过程,这个过程是强制的。

1
2
Aniaml anl = new Cat();  
Cat c = (Cat)anl;//向下转型

示例代码

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
class Animal{
public void eat(){
System.out.println("吃东西...");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}

// 特有的功能
public void lookHome(){
System.out.println("狗在看家...");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
}

class Person{}
public class Test {
public static void main(String[] args) {
/*
引用类型转换:
向上转型:子类类型向父类类型向上转换的过程,这个过程是默认\自动的。
向下转型:父类类型向子类类型向下转换的过程,这个过程是强制\手动的。
格式: 子类类型 对象名 = (子类类型)父类类型的变量;
注意:
1.向下转型的时候:右边父类类型的变量一定要指向要转型的子类类型的对象
2.不管是向上转型还是向下转型,一定满足父子类关系或者实现关系
*/
// 向上转型:
Animal anl = new Dog();

// 向下转型:
Dog dog = (Dog)anl;

System.out.println("===================================");
// 注意:右边父类类型的变量一定要指向要转型的子类类型的对象
//Animal anl1 = new Animal();
//Dog d1 = (Dog)anl1;// 运行报错,类型转换异常ClassCastException


//Animal anl2 = new Cat();
//Dog d2 = (Dog)anl2;// 运行报错,类型转换异常ClassCastException

//Animal anl3 = new Person();// 编译报错,因为Animal和Person不是父子关系
//Animal anl3 = (Animal) new Person();// 编译报错,因为Animal和Person不是父子关系

}
}

instanceof关键字

向下强转有风险,最好在转换前做一个验证 :

格式:

1
2
3
4
5
6
7
变量名 instanceof 数据类型 
如果变量属于该数据类型,返回true
如果变量不属于该数据类型,返回false

if( anl instanceof Cat){//判断anl是否能转换为Cat类型,如果可以返回:true,否则返回:false
Cat c = (Cat)anl;//安全转换
}

示例代码

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
class Animal{
public void eat(){
System.out.println("吃东西...");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}

// 特有的功能
public void lookHome(){
System.out.println("狗在看家...");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
}
public class Test {
public static void main(String[] args) {
/*
instanceof关键字:
为什么要有instanceof关键字?
因为在引用类型转换的时候很容易出现类型转换异常,所以为了提高代码的严谨性,转型之前得先判断一下
怎么使用instanceof关键字判断呢?
if(变量名 instanceof 数据类型){

}
执行:
判断前面变量指向的对象类型是否是后面的数据类型:
如果前面变量指向的对象类型是属于后面的数据类型,那么就返回true
如果前面变量指向的对象类型不是属于后面的数据类型,那么就返回false
*/
// 向上转型
Animal anl = new Cat();

// 向下转型
//Dog d = (Dog)anl;// 运行的时候会出现类型转换异常
// 先判断,再转型
if (anl instanceof Dog){
Dog d = (Dog)anl;
}

System.out.println("正常结束");
}
}

小结

1
2
3
4
5
6
7
8
9
10
11
12
 引用类型转换:
向上转型:子类类型向父类类型向上转换的过程,这个过程是默认\自动的。
向下转型:父类类型向子类类型向下转换的过程,这个过程是强制\手动的。
格式: 子类类型 对象名 = (子类类型)父类类型的变量;
注意:
1.向下转型的时候:右边父类类型的变量一定要指向要转型的子类类型的对象
2.不管是向上转型还是向下转型,一定满足父子类关系或者实现关系

instanceof关键字:
if(变量名 instanceof 数据类型){}
如果变量属于该数据类型,返回true
如果变量不属于该数据类型,返回false

解决多态的弊端

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
class Animal{
public void eat(){
System.out.println("吃东西...");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}

// 特有的功能
public void lookHome(){
System.out.println("狗在看家...");
}
}
public class Test {
public static void main(String[] args) {
/*
解决多态的弊端:
弊端:无法访问子类独有的方法或者成员变量,因为多态成员访问的特点是,编译看父类
*/
// 父类的引用指向子类的对象
Animal anl = new Dog();// 向上转型
anl.eat();// 狗吃骨头...

//anl.lookHome();// 编译报错,因为多态成员访问的特点是,编译看父类,而父类中没有子类独有的功能

// 先判断,后转型
if (anl instanceof Dog){
Dog d = (Dog)anl;// 向下转型
d.lookHome();// 狗在看家...
}

System.out.println("正常结束");
}
}

多态的应用场景综合案例

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
class Animal{
public void eat(){
System.out.println("吃东西...");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}

// 特有的功能
public void lookHome(){
System.out.println("狗在看家...");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
// 特有的功能
public void catchMouse(){
System.out.println("猫抓老鼠...");
}
}
public class Test {
public static void main(String[] args) {
Dog d = new Dog();
method(d);

System.out.println("==========================");

Cat c = new Cat();
method(c);
}

// 形参多态: 如果父类类型作为方法的形参类型,那么就可以接收该父类类型的对象或者其所有子类的对象
public static void method(Animal anl){
anl.eat();
//anl.lookHome();// 编译报错
// anl.catchMouse();// 编译报错
if (anl instanceof Dog){
Dog d = (Dog)anl;// 向下转型 Dog类型
d.lookHome();
}

if (anl instanceof Cat){
Cat c = (Cat)anl;// 向下转型 Cat类型
c.catchMouse();
}

}
}

内部类

什么是内部类

将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类

成员内部类

  • 成员内部类 :定义在类中方法外的类。

定义格式:

1
2
3
4
5
class 外部类 {
class 内部类{

}
}

在描述事物时,若一个事物内部还包含其他事物,就可以使用内部类这种结构。比如,汽车类Car 中包含发动机类Engine ,这时,Engine 就可以使用内部类来描述,定义在成员位置。

代码举例:

1
2
3
4
5
class Car { //外部类
class Engine { //内部类

}
}

访问特点

  • 内部类可以直接访问外部类的成员,包括私有成员。
  • 外部类要访问内部类的成员,必须要建立内部类的对象。

创建内部类对象格式:

1
外部类名.内部类名 对象名 = new 外部类型().new 内部类型();

访问演示,代码如下:

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
// 外部类
public class Body {

public void methodW1(){
// 访问内部类的成员
//Body.Heart bh = new Body().new Heart();
Heart bh = new Heart();
System.out.println(bh.numN);// 10
bh.methodN1();// 内部类的成员方法 methodN1
}

// 成员变量
private int numW = 100;

// 成员方法
private void methodW2(){
System.out.println("外部类的成员方法 methodW2");
}


// 内部类
public class Heart{
// 成员变量
int numN = 10;

// 成员方法
public void methodN1(){
System.out.println("内部类的成员方法 methodN1");
}

public void methodN2(){
// 访问外部类的成员
System.out.println(numW);
methodW2();
}
}


}


public class Test {
public static void main(String[] args) {
/*
- 什么是内部类:将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,外面的那个B类则称为外部类。
- 成员内部类的格式:
public class 外部类{
public class 内部类{

}
}
- 成员内部类的访问特点:
在其他类中,访问内部类的成员,得先创建内部类对象:
外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
在外部类中,访问内部类的成员,得先创建内部类对象:
外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
内部类名 对象名 = new 内部类名();

在内部类中,可以直接访问外部类的一切成员(包含私有的):

*/
// 创建内部类的对象
Body.Heart bh = new Body().new Heart();
System.out.println(bh.numN);// 10
bh.methodN1();// 内部类的成员方法 methodN1

System.out.println("=======================");
// 创建外部类对象
Body b = new Body();
b.methodW1();

System.out.println("=======================");
bh.methodN2();// 100 外部类的成员方法 methodW2

}
}

匿名内部类

是内部类的简化写法。它的本质是一个带具体实现的 父类或者父接口的 匿名的 子类对象

代码一:

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
abstract class Animal{
public abstract void eat();
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
}
public class Test {
public static void main(String[] args) {
/*
匿名内部类:
概述:本质其实就是一个类的匿名子类的对象
作用:就是用来简化代码的,没有其他的功能
格式:
new 类名(){
实现抽象方法
};
*/
// 需求:调用Animal类的eat方法
// 1.创建一个子类继承Animal类
// 2.在子类中重写eat抽象方法
// 3.创建子类对象
// 4.使用子类对象调用eat方法
Dog d = new Dog();// 创建Animal子类对象
d.eat();// d---->是Animal类的子类的对象
// 问题:以上4步一步都不能少,有点麻烦,是否可以简化代码?
// 解决:匿名内部类可以简化代码,因为它可以不创建子类的情况下,直接得到一个类的子类对象

System.out.println("==========================");
// 创建Animal子类对象<=====>Animal类的匿名内部类
// 父类的引用指向子类的对象
Animal anl = new Animal() {
@Override
public void eat() {
System.out.println("匿名内部类");
}
};// 是Animal类的子类的对象
anl.eat();
}
}

代码二:

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
interface A{
public abstract void show();
}
class Imp implements A{
public void show(){
System.out.println("实现类实现show方法");
}
}
public class Test {
public static void main(String[] args) {
/*
匿名内部类:
概述:本质是一个接口的匿名实现类的对象
格式:
new 接口名(){
实现抽象方法
};
*/
// 需求:调用A接口的show方法
// 1.创建实现类实现A接口
// 2.在实现类中重写show方法
// 3.创建实现类对象
// 4.使用实现类对象调用show方法
Imp imp = new Imp();// imp就是接口的实现类的对象
imp.show();

System.out.println("==============================");
// 简化: 匿名内部类
A a = new A() {
@Override
public void show() {
System.out.println("匿名内部类");
}
};
a.show();
}
}

小结:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
对于类:
概述:本质其实就是一个类的匿名子类的对象
格式:
new 类名(){
实现抽象方法
};
对于接口:
概述:本质是一个接口的匿名实现类的对象
格式:
new 接口名(){
实现抽象方法
};
匿名内部类作用:就是用来简化代码的,没有其他的功能
使用场景:
如果方法的形参类型为抽象类或者接口类型,那么为了简化代码,可以直接传入该抽象类或者接口的匿名内部类

补充

1
2
3
4
5
6
7
8
// 匿名子类的匿名对象
new Imp().show();// 实现类的匿名对象调用show方法
new A() {
@Override
public void show() {
System.out.println("匿名内部类");
}
}.show();// 匿名实现类的匿名对象调用show方法

引用类型使用

​ 实际的开发中,引用类型的使用非常重要,也是非常普遍的。我们可以在理解基本类型的使用方式基础上,进一步去掌握引用类型的使用方式。基本类型可以作为成员变量、作为方法的参数、作为方法的返回值,那么当然引用类型也是可以的。在这我们使用两个例子 , 来学习一下。

类名作为方法参数和返回值

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
class Person{
public String name;
public int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public void show(){
System.out.println(name+","+age);
}
}
public class Test {
public static void main(String[] args) {
/*
类名作为方法参数和返回值:
*/
// 创建Person
Person p = new Person("冰冰",18);
method1(p);
System.out.println("=========================================");
// 调用method2;
Person person = method2(p);
person.show();// 冰冰,20
}

// 类作为方法的参数类型
public static void method1(Person p){
p.show();// 冰冰,18
}

// 类作为方法的参数类型和返回值类型
public static Person method2(Person p){
p.age = 20;// 把age改为20
return p;
}
}

抽象类作为方法参数和返回值

  • 抽象类作为形参:表示可以接收任何此抽象类的”子类对象”作为实参;
  • 抽象类作为返回值:表示”此方法可以返回此抽象类的任何子类对象”;
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
abstract class Animal{
public abstract void eat();
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
}
public class Test {
public static void main(String[] args) {
// 调用method1,就得传入Animal抽象类的子类对象
method1(new Dog());

System.out.println("========================");
// 调用method1,就得传入Animal抽象类的子类对象
method1(new Animal() {
@Override
public void eat() {
System.out.println("匿名内部类的方式...");
}
});

System.out.println("========================");
// 调用method2方法,会返回一个Animal类的子类对象
//Animal anl = method2();
Dog d = (Dog)method2();
}

// 抽象类作为方法参数类型
public static void method1(Animal anl){
anl.eat();
}

// 抽象类作为方法返回值类型
public static Animal method2(){
return new Dog();
}
}

接口作为方法参数和返回值

  • 接口作为方法的形参:【同抽象类】
  • 接口作为方法的返回值:【同抽象类】
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
interface A{
void show();
}
class Imp implements A{
public void show(){
System.out.println("实现类的方式实现show方法");
}
}
public class Test {
public static void main(String[] args) {
// 接口作为方法参数和返回值
// 调用method1方法,就得传入A接口的实现类对象
method1(new Imp());

System.out.println("===================");

// 调用method1方法,就得传入A接口的匿名内部类
method1(new A() {
@Override
public void show() {
System.out.println("匿名内部类的方式实现show方法");
}
});

System.out.println("===================");

// 调用method2方法,就会返回A接口的实现类对象
//A a = method2();
Imp imp = (Imp) method2();


}

// 接口作为方法参数
public static void method1(A a){
a.show();
}

// 接口作为方法返回值
public static A method2(){
return new Imp();
}
}

类名作为成员变量

​ 我们每个人(Person)都有一个身份证(IDCard) , 为了表示这种关系 , 就需要在Person中定义一个IDCard的成员变量。定义Person类时,代码如下:

1
2
3
4
class Person {
String name;//姓名
int age;//年龄
}

​ 使用String 类型表示姓名 , int 类型表示年龄。其实,String本身就是引用类型,我们往往忽略了它是引用类型。如果我们继续丰富这个类的定义,给Person 增加身份证号 , 身份证签发机关等属性,我们将如何编写呢?这时候就需要编写一个IDCard类了

修改Person类:

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
class Person{
String name;// 引用数据类型定义成员变量 String类
int age;// 基本类型定义成员变量
IdCard idCard;

public Person(String name, int age, IdCard idCard) {
this.name = name;
this.age = age;
this.idCard = idCard;
}
// ...
}
class IdCard{
String idNum;// 身份证号码
String address;// 地址

public IdCard(String idNum, String address) {
this.idNum = idNum;
this.address = address;
}
// ....
}
public class Test {
public static void main(String[] args) {
// 创建IdCard对象
IdCard idCard = new IdCard("440330200010101919","广东省深圳市宝安区公安局");
// 创建Person对象
Person p = new Person("张三",18,idCard);
System.out.println(p.name+","+p.age+","+p.idCard.idNum+","+p.idCard.address);// java支持链式编程
}
}

类作为成员变量时,对它进行赋值的操作,实际上,是赋给它该类的一个对象。同理 , 接口也是如此 , 例如我们笔记本案例中使用usb设备。在此我们只是通过小例子 , 让大家熟识下引用类型的用法 , 后续在咱们的就业班学习中 , 这种方式会使用的很多。

抽象类作为成员变量

  • 抽象类作为成员变量——为此成员变量赋值时,可以是任何它的子类对象
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
abstract class Pet{
String name;

public Pet(String name) {
this.name = name;
}
}
class Dog extends Pet{

public Dog(String name) {
super(name);
}
}
class Person{
String name;
int age;
Pet pet;

public Person(String name, int age, Pet pet) {
this.name = name;
this.age = age;
this.pet = pet;
}
}
public class Test {
public static void main(String[] args) {
// 抽象类作为成员变量:传入抽象类的子类对象
Pet pet = new Dog("旺财");
Person p = new Person("张三",18,pet);
System.out.println(p.name);
System.out.println(p.age);
System.out.println(p.pet.name);

}
}

接口作为成员变量

  • 接口类型作为成员变量——【同抽象类】
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
abstract interface Pet{

}
class Dog implements Pet{


}
class Person{
String name;
int age;
Pet pet;

public Person(String name, int age, Pet pet) {
this.name = name;
this.age = age;
this.pet = pet;
}
}
public class Test {
public static void main(String[] args) {
// 接口作为成员变量:传入接口的实现类对象
Pet pet = new Dog();
Person p = new Person("张三",18,pet);
System.out.println(p.name);
System.out.println(p.age);
System.out.println(p.pet);

}
}

英雄: name,皮肤,法术(接口)

小结

1
2
3
4
5
6
7
8
- 类名作为方法参数和返回值:可以直接传入该类的对象;返回该类的对象
- 抽象类作为方法参数和返回值:只能传入该类的子类对象;返回该类的子类对象
- 接口作为方法参数和返回值:只能传入该接口的实现类对象;返回该接口的实现类对象
传递的都是地址值,返回的也是地址值

- 类作为成员变量 : 赋该类的对象
- 抽象类作为成员变量 ; 赋该类的子类对象
- 接口作为成员变量 : 赋该接口的实现类对象

权限修饰符

在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,

  • public:公共的
  • protected:受保护的
  • (空的):默认的
  • private:私有的

不同权限的访问能力

public protected (空的) private
同一类中
同一包中(子类与无关类)
不同包的子类
不同包中的无关类
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
包:com.nbchen.demo9_权限修饰符
public class AAA {
public void method1(){}
protected void method2(){}
void method3(){}
private void method4(){}

// 同一个类中
public void method(){
method1();
method2();
method3();
method4();
}
}
public class Test {
public static void main(String[] args) {
AAA a = new AAA();
a.method1();
a.method2();
a.method3();
// a.method4(); 私有方法 编译报错
}
}

包:com.nbchen.demo10_权限修饰符
public class Zi extends AAA {
public void show(){
method1();
method2();
//method3();编译报错
//method4();编译报错
}
}
public class Test {
public static void main(String[] args) {
AAA a = new AAA();
a.method1();
//a.method2();// 编译报错
//a.method3();// 编译报错
//a.method4();// 编译报错
}
}

可见,public具有最大权限。private则是最小权限。

编写代码时,如果没有特殊的考虑,建议这样使用权限:

  • 成员变量使用private ,隐藏细节。

  • 构造方法使用 public ,方便创建对象。

  • 成员方法使用public ,方便调用方法。

代码块

构造代码块

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
格式: {}
位置: 类中,方法外
执行: 每次在调用构造方法的时候,就会执行
使用场景: 统计创建了多少个该类对象

例如:
public class Person{
{
构造代码块执行了
}
}

public class Test {
public static void main(String[] args) {
/*
构造代码块:
格式: {}
位置: 类中,方法外
执行: 每次执行构造方法之前都会执行一次
使用场景: 例如统计对象的个数 也就是每次执行构造方法之前要执行的代码就可以放在构造代码块中
*/
Person p1 = new Person();
Person p2 = new Person();
}
}

静态代码块

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
格式:static{}
位置: 类中,方法外
执行: 当类被加载的时候执行,并只执行一次
使用场景: 例如加载驱动,这种只需要执行一次的代码就可以放在静态代码块中

public class Person {
static {
System.out.println("Person 静态代码块");
}

{
System.out.println("Person 构造代码块");
}

public Person(){
System.out.println("Person 构造方法");
}
}

public class Test {
/*
静态代码块:
格式: static{}
位置: 类中,方法外
执行: 随着类的加载而执行,并且只执行一次
使用场景: 例如读取配置文件中的数据,加载驱动,也就是说程序中只需要执行一次的代码就可以放在静态代码块中

执行优先级: 静态代码块 > 构造代码块 > 构造方法
*/
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();

}
}

局部代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
格式:{}
位置: 方法中
执行: 调用方法,执行到局部代码块的时候就执行
使用场景: 节省内存空间,没有多大的意义
例如:
public class Test {
public static void main(String[] args) {
/*
局部代码块:
格式: {}
位置: 方法中
执行: 调用方法,执行到了局部代码块的时候执行
使用场景: 节省内存空间,没有太多意义
*/
System.out.println("开始");
{
int num1 = 10;
System.out.println("局部代码块");
}// 把局部代码块中的变量占用的空间会释放

System.out.println("结束");
}
}