Java 基础(中篇)

Java 基础(中篇)


API 与 Scanner 类

API 的概述

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

API 的使用步骤

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

什么是 Scanner 类

Scanner 类: 一个可以解析基本类型和字符串的简单 文本扫描器 。例如,以下代码使用户能够从 System.in 中读取一个数:

1
2
Scanner sc = new Scanner(System.in);    // System.in 系统输入指的是通过键盘录入数据。
int i = sc.nextInt(); // 把输入的数值赋值给变量 i

引用类型使用步骤

第一步:导包 。使用 import 关键字导包,在类的所有代码之前导包。java.lang 包下的所有类无需导包就可使用 。格式和举例如下:

1
2
3
4
5
// 导包格式
import 包名.类名;

// 导包举例,导入 Scanner 类的包
import java.util.Scanner;

第二步:创建对象。 使用该类的构造方法,创建一个该类的对象。格式和举例如下:

1
2
3
4
5
// 创建对象格式
数据类型 变量名 = new 数据类型(参数列表);

// 创建对象举例
Scanner sc = new Scanner(System.in);

第三步: 调用方法。 调用该类的成员方法,完成指定功能。格式和举例如下:

1
2
3
4
5
// 调用方法格式
变量名.方法名();

// 调用方法举例
int i = sc.nextInt(); // 接收一个键盘录入的整数

Scanner 练习: 使用 Scanner 类,完成接收键盘录入数据的操作 。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 导包语句需放在类代码的上方,即在类的所有代码之前导包
import java.util.Scanner;

public class RandomTest {
/**
* 查看类 java.util.Scanner :该类需要 import 导入后使用。
* 查看构造方法 public Scanner(InputStream source):构造一个新的 Scanner ,它生成的值是从指定的输入流扫描的。
* 查看成员方法 public int nextInt():将输入信息的下一个标记扫描为一个值。
*/
public static void main(String[] args) {

// 1、创建键盘录入数据的对象
Scanner sc = new Scanner(System.in);

// 2、接收数据
System.out.println("请录入一个整数:");
int i = sc.nextInt();

// 3、输出数据
System.out.println("i = " + i);

}
}

匿名对象

匿名对象: 没有变量名的对象。创建对象时,只有创建对象的语句,却没有把对象地址值赋值给某个变量。虽然是创建对象的简化写法,但是应用场景非常有限:作为方法的参数和作为返回值


创建匿名对象的格式以及举例如下:

1
2
3
4
5
// 创建匿名对象的格式
new 类名(参数列表);

// 创建匿名对象的举例
new Scanner(System.in);

创建匿名对象直接调用方法,没有变量名。一旦调用两次方法 ,就是创建了两个对象,造成浪费。如以下代码:

1
2
new Scanner(System.in).nextInt();
new Scanner(System.in).nextInt();

匿名对象 作为方法的参数 ,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
public static void main(String[] args) {

// 普通方式
Scanner sc = new Scanner(System.in);
show(sc);

// 匿名对象作为方法接收的参数
show(new Scanner(System.in));

}

public static void show(Scanner sc) {
System.out.println(sc);
}
}

匿名对象 作为返回值 ,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
public static void main(String[] args) {
Scanner sc = getScanner();
System.out.println(sc);
}

public static Scanner getScanner() {

// 普通方式
// Scanner sc = new Scanner(System.in);
// return sc;

// 匿名对象作为方法返回值
return new Scanner(System.in);

}
}

Random 类

Random 类概述

Random 类 的实例用于 生成伪随机数 ,该类需要 import 导包后才能使用。其 构造方法 public Random() 用于创建一个新的随机数生成器。常用 成员方法 public int nextInt(int n) 返回一个伪随机数,范围在0(包括)和指定 n (不包括)之间【左闭右开】 。现在使用 Random 类,完成 生成 20 个 100 以内的随机整数 的操作,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Random;

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

Random r = new Random();

for (int i = 0; i < 20; i++) {
int number = r.nextInt(100);
System.out.println("number: " + number);
}

}
}

TIPS: 创建一个 Random 对象,每次调用 nextInt() 方法,都会生成一个随机数。

获取随机数

获取 1~50 之间的随机数,包含 50 ,代码如下:

1
2
3
4
5
6
7
8
9
10
11
import java.util.Random;

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

Random r = new Random();
int number = r.nextInt(50) + 1;
System.out.println("number: " + number);

}
}

猜数字小游戏

游戏开始时,会随机生成一个 1~100 之间的整数 number 。玩家猜测一个数字 guestNumber ,会与 number 作比较,系统提示大了或者小了,直到玩家猜中,游戏结束。代码如下:

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
import java.util.Random;
import java.util.Scanner;

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

// 生产 1~100 的随机数
Random r = new Random();
int number = r.nextInt(100) + 1;

while (true) {

// 键盘录入我们要猜的数据
Scanner sc = new Scanner(System.in);
System.out.println("请输入你要猜的数字(1~100):");
int guessNumber = sc.nextInt();

// 比较这两个数据(用 if 语句)
if (guessNumber > number) {
System.out.println("你猜的数据" + guessNumber + "大了");
} else if (guessNumber < number) {
System.out.println("你猜的数据" + guessNumber + "小了");
} else {
System.out.println("恭喜你,猜中了");
break;
}

}

}
}

ArrayList 类

ArrayList 引入

如何存储多个对象呢?例如 存储三个学生对象 。这个时候就要用到 ArrayList 类了。代码如下:

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
import java.util.ArrayList;

public class ArrayListTest {
public static void main(String arg[]) {

// 01-创建 ArrayList 对象
ArrayList<Student> list = new ArrayList<>();

// 02-创建学生对象
Student stu1 = new Student("洪七公", 20);
Student stu2 = new Student("欧阳锋", 21);
Student stu3 = new Student("黄药师", 22);
Student stu4 = new Student("段智兴", 23);

// 03-把学生对象加入到集合中,即存储学生对象
list.add(stu1);
list.add(stu2);
list.add(stu3);
list.add(stu4);

// 04-遍历集合,取出学生对象
for (int i = 0; i < list.size(); i++) {
Student stu = list.get(i);
System.out.println("姓名:" + stu.getName() + " <==> 年龄:" + stu.getAge());
}

}
}

class Student {

private String name;
private int age;

public Student() {
}

public Student(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;
}

}

什么是 ArrayList 类

ArrayList 是一个集合,类似于数组,都属于容器。详情及特性参照如下注释,代码如下:

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
import java.util.ArrayList;

/**
* 1、数组的长度不可以发生改变。但是 ArrayList 集合的长度是可以随意变化的。
* 2、对于 ArrayList 来说,有一个尖括号 <E> 代表泛型。
* 3、泛型:也就是装在集合当中的所有元素,全都是统一的类型。注意:泛型只能是引用类型,不能是基本类型。
* 4、注意事项:
* 1、对于 ArrayList 集合来说,直接打印得到的不是地址值,而是内容。
* 2、如果内容是空,得到的是空的中括号:[]
*/
public class ArrayListTest {

public static void main(String[] args) {

// 创建一个 ArrayList 集合,名字为 list,里面装的全都是 String 类型的数据。注意:从 JDK 1.7+ 开始,右侧的尖括号内部可以不写内容,但是 <> 本身还是要写的。
ArrayList<String> list = new ArrayList<>();
System.out.println(list); // []

// 向集合当中添加一些数据,需要用到 add 方法
list.add("赵丽颖");
System.out.println(list); // [赵丽颖]

list.add("迪丽热巴");
list.add("古力娜扎");
list.add("玛尔扎哈");
System.out.println(list); // [赵丽颖, 迪丽热巴, 古力娜扎, 玛尔扎哈]

// list.add(100); // 错误写法!因为创建的时候尖括号泛型已经说了是 String 类型,添加进去的元素就必须都是字符串才行

}

}

常用方法和遍历

常用的方法 都写在注释里面了,遍历请看代码:

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
import java.util.ArrayList;

/**
* ArrayList 当中的常用方法有:
* 1、public boolean add(E e):向集合当中添加元素,参数的类型和泛型一致。返回值代表添加是否成功。
* 注意事项:对于 ArrayList 集合来说,add 添加动作一定是成功的,所以返回值可用可不用。但是对于其他集合(今后学习)来说,add 添加动作不一定成功。
* 2、public E get(int index):从集合当中获取元素,参数是索引编号,返回值就是对应位置的元素。
* 3、public E remove(int index):从集合当中删除元素,参数是索引编号,返回值就是被删除掉的元素。
* 4、public int size():获取集合的尺寸长度,返回值是集合中包含的元素个数。
*/
public class ArrayListTest {

public static void main(String[] args) {

ArrayList<String> list = new ArrayList<>();
System.out.println(list); // []

// 01-向集合中添加元素,使用 add() 方法
boolean success = list.add("柳岩");
System.out.println(list); // [柳岩]
System.out.println("添加的动作是否成功:" + success); // true

list.add("高圆圆");
list.add("赵又廷");
list.add("李小璐");
list.add("贾乃亮");
System.out.println(list); // [柳岩, 高圆圆, 赵又廷, 李小璐, 贾乃亮]

// 02-从集合中获取元素,使用 get() 方法,索引值从 0 开始
String name = list.get(2);
System.out.println("第2号索引位置:" + name); // 赵又廷

// 03-从集合中删除元素,使用 remove() 方法,索引值从 0 开始。
String whoRemoved = list.remove(3);
System.out.println("被删除的人是:" + whoRemoved); // 李小璐
System.out.println(list); // [柳岩, 高圆圆, 赵又廷, 贾乃亮]

// 04-获取集合的长度尺寸,也就是其中元素的个数
int size = list.size();
System.out.println("集合的长度是:" + size);

}

}

如何存储基本数据类型

ArrayList 对象 不能存储基本类型,只能存储引用类型的数据 。类似 <int> 写法是错误的,但是存储 基本数据类型对应的包装类型 是可以的。所以,想要存储基本类型数据,< > 中的数据类型,必须转换后才能编写,转换写法如下:

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
import java.util.ArrayList;

/**
* 如果希望向集合 ArrayList 当中存储基本类型数据,必须使用基本类型对应的“包装类”。
*
* 基本类型 包装类(引用类型,包装类都位于 java.lang 包下)
* byte Byte
* short Short
* int Integer 【特殊】
* long Long
* float Float
* double Double
* char Character 【特殊】
* boolean Boolean
*
* 从 JDK 1.5+ 开始,支持自动装箱、自动拆箱。
* 自动装箱:基本类型 --> 包装类型
* 自动拆箱:包装类型 --> 基本类型
*/
public class ArrayListTest {

public static void main(String[] args) {

// 错误写法!泛型只能是引用类型,不能是基本类型
// ArrayList<int> list = new ArrayList<>();

ArrayList<Integer> list = new ArrayList<>();
list.add(100);
list.add(200);
System.out.println(list); // [100, 200]

int num = list.get(1);
System.out.println("第1号元素是:" + num);

}

}

ArrayList 练习

数值添加到集合

生成 6 个 1~33 之间的随机整数 ,然后添加到集合并遍历集合。思路分析及代码如下:

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
import java.util.ArrayList;
import java.util.Random;

/**
* 思路分析:
* 1、需要存储 6 个数字,创建一个集合,<Integer>
* 2、产生随机数,需要用到 Random
* 3、用循环 6 次,来产生 6 个随机数字:for 循环
* 4、循环内调用 nextInt(int n) 方法,参数是 33,0~32,整体 +1 才是 1~33
* 5、把数字添加到集合中:add
* 6、遍历集合:for、size、get
*/
public class RandomTest {

public static void main(String[] args) {

ArrayList<Integer> list = new ArrayList<>();
Random random = new Random();

for (int i = 0; i < 6; i++) {
int num = random.nextInt(33) + 1;
list.add(num);
}

// 遍历集合
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}

}

}

对象添加到集合

自定义 4 个学生对象,添加到集合并遍历。代码如下:

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
import java.util.ArrayList;

public class ArrayListTest {
public static void main(String arg[]) {

// 01-创建 ArrayList 对象
ArrayList<Student> list = new ArrayList<>();

// 02-创建学生对象
Student stu1 = new Student("洪七公", 20);
Student stu2 = new Student("欧阳锋", 21);
Student stu3 = new Student("黄药师", 22);
Student stu4 = new Student("段智兴", 23);

// 03-把学生对象加入到集合中,即存储学生对象
list.add(stu1);
list.add(stu2);
list.add(stu3);
list.add(stu4);

// 04-遍历集合,取出学生对象
for (int i = 0; i < list.size(); i++) {
Student stu = list.get(i);
System.out.println("姓名:" + stu.getName() + " <==> 年龄:" + stu.getAge());
}

}
}

class Student {

private String name;
private int age;

public Student() {
}

public Student(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;
}

}

打印集合方法

定义以指定格式打印集合的方法(ArrayList 类型作为参数) ,使用 {} 把集合元素括起来,使用 @ 符号 分隔集合里面每个元素。格式参照:{元素@元素@元素} 。代码如下:

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
import java.util.ArrayList;

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

ArrayList<String> list = new ArrayList<>();

list.add("张三丰");
list.add("宋远桥");
list.add("张无忌");
list.add("张翠山");

System.out.println(list); // [张三丰, 宋远桥, 张无忌, 张翠山]

printArrayList(list); // {张三丰@宋远桥@张无忌@张翠山}

}

/**
* 定义方法的三要素:
* 返回值类型:只是进行打印而已,没有运算,没有结果;所以用 void
* 方法名称:printArrayList
* 参数列表:ArrayList
*/
public static void printArrayList(ArrayList<String> list) {

System.out.print("{");

for (int i = 0; i < list.size(); i++) {
String name = list.get(i);
if (i == list.size() - 1) {
System.out.println(name + "}");
} else {
System.out.print(name + "@");
}
}

}
}

获取集合方法

用一个 大集合存入 20 个随机数字 ,然后 筛选其中的偶数元素 ,放到小集合当中。要求: 使用自定义的方法来实现筛选。代码如下:

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
import java.util.ArrayList;
import java.util.Random;

/**
* 思路分析:
* 1、需要创建一个大集合,用来存储 int 数字:<Integer>
* 2、随机数字就用 Random nextInt
* 3、循环 20 次,把随机数字放入大集合:for 循环、add 方法
* 4、定义一个方法,用来进行筛选。筛选:根据大集合,筛选符合要求的元素,得到小集合。
* 方法三要素:
* 1、返回值类型:ArrayList 小集合(里面元素个数不确定)
* 2、方法名称:getSmallList
* 3、参数列表:ArrayList 大集合(装着 20 个随机数字)
* 5. 判断(if)是偶数:num % 2 == 0
* 6. 如果是偶数,就放到小集合当中,否则不放。
*/
public class RandomTest {

public static void main(String[] args) {

ArrayList<Integer> bigList = new ArrayList<>();
Random random = new Random();

for (int i = 0; i < 20; i++) {
int num = random.nextInt(100) + 1; // 1~100
bigList.add(num);
}

ArrayList<Integer> smallList = getSmallList(bigList);

System.out.println("偶数总共有多少个:" + smallList.size());

for (int i = 0; i < smallList.size(); i++) {
System.out.println(smallList.get(i));
}

}

// 这个方法,接收大集合参数,返回小集合结果
public static ArrayList<Integer> getSmallList(ArrayList<Integer> bigList) {

// 创建一个小集合,用来装偶数结果
ArrayList<Integer> smallList = new ArrayList<>();

for (int i = 0; i < bigList.size(); i++) {
int num = bigList.get(i);
if (num % 2 == 0) {
smallList.add(num);
}
}

return smallList;

}

}

String 类

String 类的概述

String 类 代表字符串,位于 java.lang 包下 。Java 程序中所有的字符串文字(例如 “abc”)都可以被看作是实现此类的实例。String 类中包括用于检查各个字符串的方法,比如用于 比较字符串,搜索字符串,提取子字符串以及创建具有翻译为大写或小写的所有字符的字符串 的各种方法。

String 类的特点

第一个特点: 字符串不变。字符串的值在创建后不能被更改。代码如下:

1
2
3
4
5
String str = "abc";
str += "d";

System.out.println(str); // abcd
// 内存中有 "abc","abcd" 两个对象,str 原先指向 "abc",后改变指向,指向了 "abcd" 。

第二个特点: 共享 。因为 String 对象是不可变的,所以它们可以被共享。代码如下:

1
2
3
String s1 = "abc";
String s2 = "abc";
// 内存中只有一个 "abc" 对象被创建,同时被 s1 和 s2 共享。

第三个特点: 与字符数组等效 。”abc” 等效于 char[] arr = {‘a’,’b’,’c’} 。代码如下:

1
2
3
4
5
6
7
8
9
String str1 = "abc";
// 相当于下面的 data[] 数组,这段代码 String 源码注释里有写
char data[] = {'a', 'b', 'c'};

String str2 = new String(data);

System.out.println(str1.equals(str2)); // true
System.out.println(str1 == (str2)); // false
// String 底层是靠字符数组实现的。

String 类的使用和常量池

String 类的特点及创建方式都写于注释之中 ,这里就不过多介绍。代码如下:

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
/**
* java.lang.String 类代表字符串。
* API当中说:Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现。
* 其实就是说:程序当中所有的双引号字符串,都是 String 类的对象。(就算没有 new,也照样是)
*
* 字符串的特点:
* 1、字符串的内容永不可变。【重点】
* 2、正是因为字符串不可改变,所以字符串是可以共享使用的。
* 3、字符串效果上相当于是char[]字符数组,但是底层原理是byte[]字节数组。
*
* 创建字符串的常见 3+1 种方式。三种构造方法:
* public String():创建一个空白字符串,不含有任何内容。
* public String(char[] array):根据字符数组的内容,来创建对应的字符串。
* public String(byte[] array):根据字节数组的内容,来创建对应的字符串。
* 一种直接创建:String str = "Hello"; 注意:直接写上双引号,就是字符串对象。
*/
public class StringTest {
public static void main(String[] args) {

// 使用空参构造
String str1 = new String(); // 小括号留空,说明字符串什么内容都没有。
System.out.println("第1个字符串:" + str1);

// 根据字符数组创建字符串
char[] charArray = {'A', 'B', 'C'};
String str2 = new String(charArray);
System.out.println("第2个字符串:" + str2);

// 根据字节数组创建字符串
byte[] byteArray = {97, 98, 99};
String str3 = new String(byteArray);
System.out.println("第3个字符串:" + str3);

// 直接创建
String str4 = "Hello";
System.out.println("第4个字符串:" + str4);

}
}

String 类常量池的介绍及使用都写于注释之中 ,这里就不过多介绍。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 1、字符串常量池:在程序当中,直接写上双引号的字符串,就在字符串常量池中。
* 2、对于基本类型来说,== 是进行数值的比较。
* 3、对于引用类型来说,== 是进行【地址值】的比较。
*/
public class StringTest {
public static void main(String[] args) {

String str1 = "abc";
String str2 = "abc";

char[] charArray = {'a', 'b', 'c'};
String str3 = new String(charArray);

System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // false
System.out.println(str2 == str3); // false

}
}

常用方法

内容比较功能

String 类内容比较功能的介绍及使用都写于注释之中 ,这里就不过多介绍。代码如下:

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
/**
* == 是进行对象的地址值比较,如果确实需要字符串的内容比较,可以使用两个方法:
* public boolean equals(Object obj):参数可以是任何对象,只有参数是一个字符串并且内容相同的才会给true;否则返回false。
* 注意事项:
* 1、任何对象都能用 Object 进行接收。
* 2、equals 方法具有对称性,也就是 a.equals(b) 和 b.equals(a) 效果一样。
* 3、如果比较双方一个常量一个变量,推荐把常量字符串写在前面。推荐:"abc".equals(str) 不推荐:str.equals("abc")
* public boolean equalsIgnoreCase(String str):忽略大小写,进行内容比较。
*/
public class StringTest {
public static void main(String[] args) {

String str1 = "Hello";
String str2 = "Hello";

char[] charArray = {'H', 'e', 'l', 'l', 'o'};
String str3 = new String(charArray);

System.out.println(str1.equals(str2)); // true
System.out.println(str2.equals(str3)); // true
System.out.println(str3.equals("Hello")); // true
System.out.println("Hello".equals(str1)); // true

String str4 = "hello";
System.out.println(str1.equals(str4)); // false
System.out.println("=================");

String str5 = null;
System.out.println("abc".equals(str5)); // false
// System.out.println(str5.equals("abc")); // 报错,空指针异常 NullPointerException
System.out.println("=================");

String strA = "Java";
String strB = "java";
System.out.println(strA.equals(strB)); // false,严格区分大小写
System.out.println(strA.equalsIgnoreCase(strB)); // true,忽略大小写

// 注意,只有英文字母区分大小写,其他都不区分大小写
System.out.println("abc一123".equalsIgnoreCase("abc壹123")); // false

}
}

获取功能

String 类获取功能的介绍及使用都写于注释之中 ,这里就不过多介绍。代码如下:

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
/**
* String 当中与获取相关的常用方法有:
*
* public int length():获取字符串当中含有的字符个数,拿到字符串长度。
* public String concat(String str):将当前字符串和参数字符串拼接成为返回值新的字符串。
* public char charAt(int index):获取指定索引位置的单个字符。(索引从 0 开始。)
* public int indexOf(String str):查找参数字符串在本字符串当中首次出现的索引位置,如果没有返回 -1 值。
*/
public class StringTest {
public static void main(String[] args) {

// 获取字符串的长度
int length = "asdasfeutrvauevbueyvb".length();
System.out.println("字符串的长度是:" + length);

// 拼接字符串
String str1 = "Hello";
String str2 = "World";
String str3 = str1.concat(str2);

System.out.println(str1); // Hello,原封不动
System.out.println(str2); // World,原封不动
System.out.println(str3); // HelloWorld,新的字符串
System.out.println("==============");

// 获取指定索引位置的单个字符
char ch = "Hello".charAt(1);
System.out.println("在 1 号索引位置的字符是:" + ch);
System.out.println("==============");

// 查找参数字符串在本来字符串当中出现的第一次索引位置。如果没有,返回 -1 值
String original = "HelloWorldHelloWorld";
int index = original.indexOf("llo");
System.out.println("第一次索引值是:" + index); // 2

System.out.println("HelloWorld".indexOf("abc")); // -1

// 字符串匹配算法,以后有 “暴力匹配” 和 “KMP 算法” ,当前 matchString 是我自己写的
matchString("HelloWorldHelloWorld","llo");

}

public static int matchString(String str1, String str2) {

int i = str1.indexOf(str2);

if(!(i == -1)){
System.out.println("match is succeed , the location is : " + i);
return i;
} else {
System.out.println("the match is failure");
return -1;
}

}

}

截取功能

String 类截取功能的介绍及使用都写于注释之中 ,这里就不过多介绍。代码如下:

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
/**
* 字符串的截取方法:
* public String substring(int index):截取从参数位置一直到字符串末尾,返回新字符串。
* public String substring(int begin, int end):截取从 begin 开始,一直到 end 结束,中间的字符串。
* 备注:[begin,end),包含左边,不包含右边。
*/
public class StringTest {
public static void main(String[] args) {

String str1 = "HelloWorld";
String str2 = str1.substring(5);

System.out.println(str1); // HelloWorld,原封不动
System.out.println(str2); // World,新字符串
System.out.println("================");

String str3 = str1.substring(4, 7);
System.out.println(str3); // oWo
System.out.println("================");

// 下面这种写法,字符串的内容仍然是没有改变的
// 下面有两个字符串:"Hello","Java" 。str 保存的是地址值。
// 假设 Hello 的地址值是 0x666 ,Java 的地址值是 0x999 。一开始 str 指向 0x666 ,后来指向了 0x999
String str = "Hello";
System.out.println(str); // Hello

str = "Java";
System.out.println(str); // Java

}
}

转换功能

String 类转换功能的介绍及使用都写于注释之中 ,这里就不过多介绍。代码如下:

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
/**
* String 当中与转换相关的常用方法有:
*
* public char[] toCharArray():将当前字符串拆分成为字符数组作为返回值。
* public byte[] getBytes():获得当前字符串底层的字节数组。
* public String replace(CharSequence oldString, CharSequence newString):
* 将所有出现的老字符串替换成为新的字符串,返回替换之后的结果新字符串。
* 备注:CharSequence 意思就是说可以接受字符串类型。
*/
public class StringTest {
public static void main(String[] args) {

// 转换成为字符数组
char[] chars = "Hello".toCharArray();

System.out.println(chars[0]); // H
System.out.println(chars.length); // 5
System.out.println("==============");


// 转换成为字节数组
byte[] bytes = "abc".getBytes();

for (int i = 0; i < bytes.length; i++) {
System.out.println(bytes[i]);
}
System.out.println("==============");


// 字符串的内容替换
String str1 = "How do you do?";
String str2 = str1.replace("o", "*");

System.out.println(str1); // How do you do?
System.out.println(str2); // H*w d* y*u d*?
System.out.println("==============");

String lang1 = "会不会玩儿呀!你大爷的!你大爷的!你大爷的!!!";
String lang2 = lang1.replace("大爷", "**");
System.out.println(lang2); // 会不会玩儿呀!你**的!你**的!你**的!!!

}
}

分割功能

String 类分割功能的介绍及使用都写于注释之中 ,这里就不过多介绍。代码如下:

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 String[] split(String regex):按照参数的规则,将字符串切分成为若干部分。
* 注意事项:
* 1、split 方法的参数其实是一个 “正则表达式” ,今后学习。
* 2、如果按照英文句点 “.” 进行切分,必须写 "\\." (两个反斜杠)
*/
public class StringTest {
public static void main(String[] args) {

String str1 = "aaa,bbb,ccc";
String[] array1 = str1.split(",");

for (int i = 0; i < array1.length; i++) {
System.out.println(array1[i]);
}
System.out.println("===============");


String str2 = "aaa bbb ccc";
String[] array2 = str2.split(" ");

for (int i = 0; i < array2.length; i++) {
System.out.println(array2[i]);
}
System.out.println("===============");


String str3 = "XXX.YYY.ZZZ";
String[] array3 = str3.split("\\.");

System.out.println(array3.length);
for (int i = 0; i < array3.length; i++) {
System.out.println(array3[i]);
}

}
}

String 类练习

拼接字符串

拼接字符串的介绍和思路分析都写于注释之中 ,这里就不过多介绍。代码如下:

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
/**
* 1、拼接字符串介绍:定义一个方法,把数组{1,2,3}按照指定格式拼接成一个字符串。格式参照如下:[word1#word2#word3]。
* 2、思路分析:
* 1、首先准备一个int[]数组,内容是:1、2、3
* 2、定义一个方法,用来将数组变成字符串
* 3、方法三要素: 返回值类型:String 方法名称:fromArrayToString 参数列表:int[]
* 4、用到 for 循环、字符串拼接、每个数组元素之前都有一个 word 字样、分隔使用的是 # 、区分一下是不是最后一个
* 5、调用方法,得到返回值,并打印结果字符串
*/
public class StringTest {
public static void main(String[] args) {

int[] array = {1, 2, 3, 4};
String result = fromArrayToString(array);
System.out.println(result);

}

public static String fromArrayToString(int[] array) {

String str = "[";

for (int i = 0; i < array.length; i++) {

if (i == array.length - 1) {
str += "word" + array[i] + "]";
} else {
str += "word" + array[i] + "#";
}

}

return str;

}
}

统计字符个数

统计字符个数的介绍和思路分析都写于注释之中 ,这里就不过多介绍。代码如下:

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
import java.util.Scanner;

/**
* 1、统计字符个数介绍:键盘输入一个字符串,并且统计其中各种字符出现的次数。字符种类有:大写字母、小写字母、数字、其他
* 2、思路思路:
* 1、既然用到键盘输入,肯定是 Scanner
* 2、键盘输入的是字符串,那么:String str = sc.next();
* 3、定义四个变量,分别代表四种字符各自的出现次数。
* 4、需要对字符串一个字、一个字检查,String-->char[],方法就是 toCharArray()
* 5、遍历 char[] 字符数组,对当前字符的种类进行判断,并且用四个变量进行 ++ 动作。
* 6、打印输出四个变量,分别代表四种字符出现次数。
*/
public class StringTest {
public static void main(String[] args) {

Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String str = sc.next(); // 获取键盘输入的一个字符串

int countUpper = 0; // 大写字母
int countLower = 0; // 小写字母
int countNumber = 0; // 数字0~9
int countOther = 0; // 其他字符

char[] charArray = str.toCharArray();

for (int i = 0; i < charArray.length; i++) {

char ch = charArray[i]; // 当前单个字符

if ('A' <= ch && ch <= 'Z') {
countUpper++;
} else if ('a' <= ch && ch <= 'z') {
countLower++;
} else if ('0' <= ch && ch <= '9') {
countNumber++;
} else {
countOther++;
}
}

System.out.println("大写字母有:" + countUpper);
System.out.println("小写字母有:" + countLower);
System.out.println("数字0~9有:" + countNumber);
System.out.println("其他字符有:" + countOther);

}
}

static 关键字

static 概述

static 关键字: 它可以用来修饰 成员变量成员方法 ,被修饰的成员是属于类的,而不是单单是属于某个对象的。也就是说,既然属于类,就可以不靠创建对象来调用了。


static 特殊用法: 用 static 修饰内部类,普通类是不允许声明为静态的,只有内部类才可以 。被 static 修饰的内部类可以直接作为一个普通类来使用,而不需实例一个外部类。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class StaticTest {
public static void main(String[] args) {

// 直接使用,不需要创建一个外部类实例
new OuterClass.InnerClass();

}
}

class OuterClass {
public static class InnerClass {
InnerClass() {
System.out.println("===== 我是一个内部类 InnerClass =====");
}
}
}

注意啦!!!

当一个内部类没有使用 static 修饰的时候,是不能直接使用内部类创建对象,必须要 外部类对象.new 内部类
举例: new Outer().new Inner();

定义和使用格式

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

+++ 类变量定义格式
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
* 如果一个成员变量使用了 static 关键字,那么这个变量不再属于对象自己,而是属于所在的类。多个对象共享同一份数据。
*/
public class StaticTest {

public static void main(String[] args) {

Student two = new Student("黄蓉", 16);
two.room = "101教室";
System.out.println("姓名:" + two.getName() + ",年龄:" + two.getAge() + ",教室:" + two.room + ",学号:" + two.getId());

Student one = new Student("郭靖", 19);
System.out.println("姓名:" + one.getName() + ",年龄:" + one.getAge() + ",教室:" + one.room + ",学号:" + one.getId());

}

}

class Student {

public static String room;

private String name;
private int age;
private int id;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public Student() {
}

public Student(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;
}

}

静态方法

当 static 修饰成员方法时, 该方法称为 类方法 。静态方法在声明中有 static ,建议使用类名来调用,而不需要创建类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
+++ 静态方法的定义格式
修饰符 static 返回值类型 方法名(参数列表) {
// 执行语句
}

+++ 静态方法调用的注意事项
+ 静态方法可以直接访问类变量和静态方法
+ 静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类变量或静态方法
+ 静态方法中,不能使用 this 关键字
+ 静态方法只能访问静态成员

+++ 结束!!!
1
2
3
4
5
6
7
8
9
10
11
+++ 静态方法的调用格式
被 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
36
37
38
39
40
41
42
43
44
45
46
/**
* 1、一旦使用 static 修饰成员方法,那么这就成为了静态方法。静态方法不属于对象,而是属于类的。
* 2、如果没有 static 关键字,那么必须首先创建对象,然后通过对象才能使用它。
* 3、如果有了 static 关键字,那么不需要创建对象,直接就能通过类名称来使用它。
* 4、无论是成员变量,还是成员方法。如果有了 static,都推荐使用类名称进行调用。
* 1、静态变量:类名称.静态变量
* 2、静态方法:类名称.静态方法()
* 5、注意事项:
* 1、静态不能直接访问非静态。原因:因为在内存当中是【先】有的静态内容,【后】有的非静态内容。“先人不知道后人,但是后人知道先人。”
* 2、静态方法当中不能用 this 。原因:this 代表当前对象,通过谁调用的方法,谁就是当前对象。
*/
public class StaticMethodTest {

public static void main(String[] args) {

// 1、首先创建对象,然后才能使用没有 static 关键字的内容
MyClass obj = new MyClass();
obj.method();

// 2、对于静态方法来说,可以通过对象名进行调用,也可以直接通过类名称来调用。
obj.methodStatic(); // 正确,不推荐,这种写法在编译之后也会被 javac 翻译成为“类名称.静态方法名”
MyClass.methodStatic(); // 正确,推荐

// 3、对于本类当中的静态方法,可以省略类名称
myMethod();
StaticMethodTest.myMethod(); // 完全等效

}

public static void myMethod() {
System.out.println("自己的方法!");
}

}

class MyClass {

public void method() {
System.out.println("我是没有 static 修饰的方法");
}

static void methodStatic() {
System.out.println("我是有 static 修饰的方法");
}

}

静态原理图解

1
2
3
4
5
6
7
+++ 被 static 修饰的内容的特点

+ 随着类的加载而加载的,且只加载一次。
+ 存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。
+ 它优先于对象存在,所以,可以被所有对象共享。

+++ 被 static 修饰的内容的特点

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

/**
* 1、静态代码块的格式:
* public class 类名称 {
* static {
* // 静态代码块的内容
* }
* }
*
* 2、静态代码块的特点:当第一次用到本类时,静态代码块执行唯一的一次。
* 3、静态内容总是优先于非静态,所以静态代码块比构造方法先执行。
* 4、静态代码块的典型用途:用来一次性地对静态成员变量进行赋值。
*/
public class StaticBlock {

public static void main(String[] args) {
Person one = new Person();
Person two = new Person();
}

}

class Person {

// 静态代码块只会执行一次
static {
System.out.println("静态代码块执行!");
}

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

}

温馨小提示: 静态代码块的执行顺序:静态代码块—–>非静态代码块——–>构造函数

Arrays 类

Arrays 概述

java.util.Arrays 类 是一个与数组相关的工具类,里面提供了大量静态方法,用来实现数组常见的操作。


Arrays 类 中的 常用方法 以及方法的使用都写在代码的注释里了,请参阅如下代码 :

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
import java.util.Arrays;

/**
* java.util.Arrays 是一个与数组相关的工具类,里面提供了大量静态方法,用来实现数组常见的操作。
*
* public static String toString(数组):将参数数组变成字符串(按照默认格式:[元素1, 元素2, 元素3...])
* public static void sort(数组):按照默认升序(从小到大)对数组的元素进行排序。
*
* 注意事项:
* 1、如果是数值,sort 默认按照升序从小到大
* 2、如果是字符串,sort 默认按照字母升序
* 3、如果是自定义的类型,那么这个自定义的类需要有 Comparable 或者 Comparator 接口的支持。(今后学习)
*/
public class ArraysMethods {
public static void main(String[] args) {

int[] arr = {10, 20, 30};
String str = Arrays.toString(arr); // 将 int[] 数组按照默认格式变成字符串
System.out.println(str); // [10, 20, 30]

int[] array = {2, 1, 3, 10, 6};
Arrays.sort(array); // 对 int[] 数组进行排序
System.out.println(Arrays.toString(array)); // [1, 2, 3, 6, 10]

String[] strArr = {"bbb", "aaa", "ccc"};
Arrays.sort(strArr); // 对 String[] 数组进行排序
System.out.println(Arrays.toString(strArr)); // [aaa, bbb, ccc]

}
}

Arrays 练习

请使用 Arrays 相关的 API ,将一个随机字符串中的所有字符升序排列,并倒序打印。 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.Arrays;

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

/**
* 思路分析:
* 1、如何进行升序排列:sort 方法
* 2、必须是一个数组,才能用 Arrays.sort 方法。所以需要把 String 转化成数组
* 3、转化:String --> 数组,用 toCharArray 方法
*/

String str = "asv76agfqwdfvasdfvjh";
char[] chars = str.toCharArray();
Arrays.sort(chars); // 对字符数组进行升序排列

// 倒序遍历
for (int i = chars.length - 1; i >= 0; i--) {
System.out.print(chars[i]);
}

}
}

Math 类

Math 类概述

java.util.Math 类 是一个很有用的数学帮助类,该类包含用于执行基本数学运算的方法,如 初等指数、对数、平方根和三角函数 。其使用也非常简单,但是这个类又比较特殊。首先它和 String 类一样都是用 final 修饰,所以不能有子类。还有就是它的构造方法是私有的,也就是我们不能通过 new 的方法在其它类中构造 Math 对象。 那么我们怎样调用它的方法呢?原来它的所有的方法都是静态方法,也就是可以直接使用类名就可以访问方法了。


Math 类 中的 常用方法 以及方法的使用都写在代码的注释里了,请参阅如下代码 :

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
/**
* java.util.Math 类是数学相关的工具类,里面提供了大量的静态方法,完成与数学运算相关的操作。
*
* public static double abs(double num):获取绝对值。有多种重载。
* public static double ceil(double num):向上取整。
* public static double floor(double num):向下取整。
* public static long round(double num):四舍五入。
*
* Math.PI 代表近似的圆周率常量(double)。
*/

public static void main(String[] args) {

// public static double abs(double num):获取绝对值。有多种重载。
System.out.println("绝对值:" + Math.abs( -235.345));
System.out.println("绝对值:" + Math.abs(235.345));

// public static double ceil(double num):向上取整。
System.out.println("---------------");
System.out.println("向上取整:" + Math.ceil(545.001));
System.out.println("向上取整:" + Math.ceil(545.999));

// public static double floor(double num):向下取整。
System.out.println("---------------");
System.out.println("向下取整:" +Math.floor(545.001));
System.out.println("向下取整:" +Math.floor(545.999));

// public static long round(double num):四舍五入。
System.out.println("---------------");
System.out.println("四舍五入:" +Math.round(545.499));
System.out.println("四舍五入:" +Math.round(545.500));
System.out.println("四舍五入:" +Math.round(-54.499));
System.out.println("四舍五入:" +Math.round(-54.501));
System.out.println("四舍五入:" +Math.round(-1.5));
System.out.println("四舍五入:" +Math.round(-1.51));

// Math.PI 代表近似的圆周率常量(double),Math.E 代表自然常数(double)。
System.out.println("---------------");
System.out.println("圆周率:" + Math.PI);
System.out.println("自然常数:" + Math.E);

// public static double pow(double a, double b):返回 a 的 b 次幂的值,即 a^b
System.out.println("---------------");
System.out.println("2的10次方:" + Math.pow(2,10));

// 余弦函数 正弦函数 正切函数
System.out.println("---------------");
double degree = 45.0; // 角度为 45 度
double radians = Math.toRadians(degree); // 把 45 度转化为弧度
System.out.println(Math.cos(radians)); // cos45
System.out.println(Math.sin(radians)); // sin45
System.out.println(Math.tan(radians)); // tan45

// 开平方 开立方
System.out.println("---------------");
System.out.println("4的平方根:" + Math.sqrt(4));
System.out.println("27的立方根:" + Math.cbrt(27));

// 随机数 public static double random():返回带正号的 double 值,大于或等于 0.0,小于 1.0。
// 返回一个 1 ~ 100 的整数 【1-100】
System.out.println("生成一个1~100的随机数:" + (int)(Math.random() * 100 + 1));
// 返回一个 0 ~ 100 的整数 【0-100】
System.out.println("生成一个0~100的随机数:" + (int)(Math.random() * 101));

}

Math 练习

计算在 -10.8 到 5.9 之间,绝对值大于 6 或者小于 2.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
/**
* 思路分析:
* 1、既然已经确定了范围,for 循环
* 2、起点位置 -10.8 应该转换成为 -10 ,两种办法:
* 1、可以使用 Math.ceil 方法,向上(向正方向)取整
* 2、强转成为 int ,自动舍弃所有小数位
* 3、每一个数字都是整数,所以步进表达式应该是 num++,这样每次都是 +1 的。
* 4、如何拿到绝对值:Math.abs 方法。
* 5、一旦发现了一个数字,需要让计数器 ++ 进行统计。
*
* 备注:如果使用 Math.ceil 方法,-10.8 可以变成 -10.0 。注意 double 也是可以进行 ++ 操作的。
*/
public static void main(String[] args) {

int count = 0; // 符合要求的数量
double min = -10.8;
double max = 5.9;

// 这样处理,变量 i 就是区间之内所有的整数
for (int i = (int) min; i < max; i++) {
int abs = Math.abs(i); // 绝对值
if (abs > 6 || abs < 2.1) {
System.out.println(i);
count++;
}
}

System.out.println("总共有:" + count); // 9

}

继承

继承初识

继承 是 java 面向对象编程技术的一块基石,因为它允许创建分等级层次的类。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法。 或者子类从父类继承方法,使得子类具有父类相同的行为。

继承的由来

当多个类中存在相同属性和行为时,将这些相同的内容抽取到一个单独的类中 ,那么多个类无需再定义这些属性和行为,只要继承这一个类即可。其中,多个类可以称为 子类 ,单独那一个类称为 父类、超类(superclass)或者基类 。如图所示:

继承描述的是事物之间的所属关系,这种关系是:is-a 的关系。例如,图中兔子属于食草动物,食草动物属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。

继承的定义、好处与格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+++ 继承的定义
继承就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的"非私有"的属性和行为。

+++ 继承的好处
+ 提高代码的复用性。
+ 类与类之间产生了关系,是多态的前提。

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

+++ 结束!!!

继承示例

通过上述介绍,我们编写相关代码来实现继承操作。代码如下:

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
// 01-定义 Animal 父类
class Animal {

public String name;
public double weight;

public void eat() {
System.out.println("我要吃饭了!");
}

}

// 02-定义子类 Lion 继承父类
class Lion extends Animal {
// 里面什么内容都不写,由于继承了父类 Animal ,所以 Lion 类拥有了父类的属性和方法
}

// 03-定义 ExtendsTest 测试类
public class ExtendsTest {
public static void main(String[] args) {

Lion lion = new Lion();
String name = lion.name = "狮子";
double weight = lion.weight = 500;

System.out.println("我是" + name + ",我的体重是 " + weight);
lion.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
class Fu {

// Fu 中的成员变量
int numFu = 5;

}

class Zi extends Fu {

// Zi 中的成员变量
int numZi = 10;

// Zi 中的成员方法
public void show(){
// 访问父类的成员变量
System.out.println("numFu = " + numFu); // 继承而来,可以直接访问
// 访问子类自身的成员变量
System.out.println("numZi = " + numZi);
}

}

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

Zi zi = new Zi(); // 创建子类对象
zi.show(); // 调用子类中的 show() 方法

}
}

// 演示结果:
// numFu = 5
// numZi = 10

成员变量重名

如果子类父类中出现重名的成员变量,这时的访问是有影响的。子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用 super 关键字,修饰父类成员变量super 类似于之前学过的 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
class Fu {

int num = 10;

}

class Zi extends Fu {

int num = 20;

public void method() {
int num = 30;
System.out.println(num); // 30,局部变量
System.out.println(this.num); // 20,本类的成员变量
System.out.println(super.num); // 10,父类的成员变量
}

}

/**
* 局部变量: 直接写成员变量名
* 本类的成员变量: this.成员变量名
* 父类的成员变量: super.成员变量名
*/
public class ExtendsTest {
public static void main(String[] args) {

Zi zi = new Zi();
zi.method();

}
}

// 演示结果:
// 30
// 20
// 10

小提示: 父类中的成员变量是非私有的,子类中可以直接访问。若父类中的成员变量私有了,子类是不能直接访问的 。通常编码时,我们遵循封装的原则,使用 private 修饰成员变量 ,那么如何访问父类的私有成员变量呢?对!可以在父类中提供公共的 getXxx 方法和 setXxx 方法。

成员方法不重名

如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。 代码如下:

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 Fu {

public void showFu(){
System.out.println("Fu 类方法执行!");
}

}

class Zi extends Fu {

public void showZi(){
System.out.println("Zi 类方法执行!");
}

}

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

Zi zi = new Zi();

//子类中没有 showFu() 方法,但是可以找到父类方法去执行
zi.showFu();
zi.showZi();

}
}

成员方法重名

如果子类和父类中出现重名的成员方法 ,这时的访问是一种特殊情况,叫做 方法重写(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
class Fu {

public String method() {
return null;
}

}

class Zi extends Fu {

@Override
public String method() {
return "The method has been Overridden!!!";
}

}
/**
* 方法覆盖重写的注意事项:
* 1、必须保证父子类之间方法的名称相同,参数列表也相同。
* 2、注解 @Override 写在方法前面,用来检测是不是有效的正确覆盖重写。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。
* 3、子类方法的返回值必须【小于等于】父类方法的返回值范围。
*
* 小扩展提示:java.lang.Object 类是所有类的公共最高父类(祖宗类),java.lang.String 就是 Object 的子类。
*
* 4、子类方法的权限必须【大于等于】父类方法的权限修饰符。
*
* 小扩展提示:public > protected > (default) > private
* 备注:(default)不是关键字 default ,而是什么都不写,留空。
*/
public class OverrideTest {

}

重写的应用

子类可以根据需要,定义特定于自己的行为。 既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。比如新的手机增加来电显示头像的功能,代码如下:

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
// 老款手机
class Phone {

public void call() {
System.out.println("打电话");
}

public void send() {
System.out.println("发短信");
}

public void show() {
System.out.println("显示号码");
}

}

// 定义一个新手机,使用老手机作为父类
class NewPhone extends Phone {

@Override
public void show() {

// 把父类的show方法拿过来重复利用
super.show();

// 自己子类再来添加更多内容
System.out.println("显示姓名");
System.out.println("显示头像");

}

}

// 定义一个测试类
public class PhoneTest {
public static void main(String[] args) {

Phone phone = new Phone();
phone.call();
phone.send();
phone.show();
System.out.println("==========");

NewPhone newPhone = new NewPhone();
newPhone.call();
newPhone.send();
newPhone.show();

}
}

注意事项:

1、子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
2、子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。

构造方法

构造方法的继承可通给 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
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 继承关系中,父子类构造方法的访问特点:
* 1、子类构造方法当中有一个默认隐含的 “super()” 调用,所以一定是先调用的父类构造,后执行的子类构造。
* 2、子类构造可以通过 super 关键字来调用父类重载构造。
* 3、super 的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次 super 构造。
* 4、总结:子类必须调用父类构造方法,不写则赠送 super() ;写了则用写的指定的 super 调用,super 只能有一个,还必须是第一个。
*/
public class ConstructorTest {

public static void main(String[] args) {
Zi zi = new Zi();
}

}

class Fu {

public Fu() {
System.out.println("父类无参构造");
}

public Fu(int num) {
System.out.println("父类有参构造!");
}

}

class Zi extends Fu {

public Zi() {
super(); // 在调用父类无参构造方法,不写也可以,编译器会默认加上
// super(20); // 在调用父类重载的构造方法
System.out.println("子类构造方法!");
}

public void method() {
// super(); // 错误写法!只有子类构造方法,才能调用父类构造方法。
}

}

Java 继承的特点

Java 继承的特点:

1、子类拥有父类非 private 的属性、方法。
2、子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
3、子类可以用自己的方式实现父类的方法。
4、Java 的继承是单继承,也可以多重继承,但是不能是多继承。 单继承 就是一个子类只能继承一个父类,多重继承 就是:例如 A 类继承 B 类,B 类继承 C 类,所以按照关系就是 C 类是 B 类的父类,B 类是 A 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。图如下:

5 提高了类之间的耦合性(继承的缺点 ,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。

super 和 this

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

super 关键字

super 的三种用法如下:

1
2
3
4
5
6
7
+++ super 关键字的用法有三种:

+ 1、在子类的成员方法中,访问父类的成员变量。
+ 2、在子类的成员方法中,访问父类的成员方法。
+ 3、在子类的构造方法中,访问父类的构造方法。

+++ 结束!!!

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
28
29
30
31
32
33
34
35
36
37
public class SuperTest {
public static void main(String[] args) {

Zi zi = new Zi();
zi.method();
zi.methodZi();

}
}
class Fu {

int num = 10;

public void method() {
System.out.println("父类方法");
}

}

class Zi extends Fu {

int num = 20;

public Zi() {
super(); // 访问父类的构造方法
}

public void methodZi() {
System.out.println(super.num); // 父类中的 num
}

public void method() {
super.method(); // 访问父类中的 method
System.out.println("子类方法");
}

}

this 关键字

this 的三种用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
+++ 开始……

+++ super 关键字用来访问父类内容,而 this 关键字用来访问本类内容。用法也有三种:
+ 1、在本类的成员方法中,访问本类的成员变量。
+ 2、在本类的成员方法中,访问本类的另一个成员方法。
+ 3、在本类的构造方法中,访问本类的另一个构造方法。

+++ 在第三种用法当中要注意:
+ 1、this(...) 调用也必须是构造方法的第一个语句,唯一一个。
+ 2、super 和 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
public class ThisTest {
public static void main(String[] args) {

Zi zi = new Zi();
zi.showNum();
zi.methodB();

}
}

class Fu {

int num = 30;

Fu() {
System.out.println("father");
}

}

class Zi extends Fu {

int num = 20;

public Zi() {
// super(); // 这一行不再赠送
this(123); // 本类的无参构造,调用本类的有参构造
// this(1, 2); // 错误写法!
}

public Zi(int n) {
this(n, 2);
}

public Zi(int n, int m) {
System.out.println(n + m);
}

public void showNum() {
int num = 10;
System.out.println(num); // 局部变量
System.out.println(this.num); // 本类中的成员变量
System.out.println(super.num); // 父类中的成员变量
}

public void methodA() {
System.out.println("AAA");
}

public void methodB() {
this.methodA();
System.out.println("BBB");
}

}

注意一下: 两个方法不能相互调用,会出现死循环,导致的结果是栈内存溢出。 如下代码:

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 Test {
public static void main(String[] args) {

Zi zi = new Zi();
zi.method01();

}
}

// 父类
class Fu {
}

// 子类
class Zi extends Fu {

public void method01(){
this.method02();
System.out.println("Hello");
}

public void method02(){
this.method01();
System.out.println("World");
}

}

上述的 method01() 方法和 method02()方法 相互调用后,出现 栈内存溢出【StackOverflowError】 。如下所示:

1
2
3
4
5
6
7
8
9
10
Exception in thread "main" java.lang.StackOverflowError
at Zi.method02(Test.java:28)
at Zi.method01(Test.java:23)
at Zi.method02(Test.java:28)
at Zi.method01(Test.java:23)
at Zi.method02(Test.java:28)
at Zi.method01(Test.java:23)
at Zi.method02(Test.java:28)
at Zi.method01(Test.java:23)
……

注意啦!!!

子类的每个构造方法中均有默认的 super() ,调用父类的空参构造。手动调用父类构造会覆盖默认的 super() 。super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

两者的内存图

superthis 的内存图如下【您品,您细品】:

抽象类

抽象类概述

父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。我们把 没有方法主体的方法 称为 抽象方法 。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
40
41
42
43
44
45
/**
* 抽象方法:就是加上 abstract 关键字、然后去掉大括号、直接分号结束的方法。
* 抽象类:抽象方法所在的类,必须是抽象类才行。在 class 之前写上 abstract 即可。
* 使用抽象类和抽象方法:
* 1、不能直接 new 抽象类对象。
* 2、必须用一个子类来继承抽象父类。
* 3、子类必须覆盖重写抽象父类当中所有的抽象方法。覆盖重写(实现):子类去掉抽象方法的 abstract 关键字,然后补上方法体大括号。
* 4、创建子类对象进行使用。
*/

// 抽象类 Animal
abstract class Animal {

// 这是一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定。
public abstract void eat();

// 这是普通的成员方法
public void normalMethod() {
System.out.println("normal method");
}

}

// 抽象类 Animal 的实现类 Cat
class Cat extends Animal {

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

}

// 测试类
public class AbstractTest {

public static void main(String[] args) {

// Animal animal = new Animal(); // 错误写法!不能直接创建抽象类对象
Cat cat = new Cat();
cat.eat();

}

}

注意事项

关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+++ 注意事项

- 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
+ 理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

- 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
+ 理解:子类的构造方法中,有默认的 super() ,需要访问父类构造方法。

- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
+ 理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

- 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
+ 理解:假设子类不重写所有抽象方法,则子类中可能包含抽象方法。那么在子类创建对象后,调用抽象的方法,没有意义。

+++ 注意事项

综合案例

案例描述

1
2
3
4
5
6
7
8
+++ 案例描述

+ 某群有多名成员,群主给成员发普通红包。普通红包的规则:
- 1、群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取。
- 2、成员领取红包后,保存到成员余额中。
+ 请根据描述,完成案例中所有类的定义以及指定类之间的继承关系,并完成发红包的操作。

+++ 案例描述

案例分析

根据描述分析,得出如下继承体系:

代码实现

定义 User 用户类, 代码如下:

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 User {

private String name; // 姓名
private int money; // 余额,也就是当前用户拥有的钱数

public User() {
}

public User(String name, int money) {
this.name = name;
this.money = money;
}

// 展示一下当前用户有多少钱
public void show() {
System.out.println("我叫:" + name + ",我有多少钱:" + money);
}

public String getName() {
return name;
}

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

public int getMoney() {
return money;
}

public void setMoney(int money) {
this.money = money;
}

}

定义 Manager 群主类, 代码如下:

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
import java.util.ArrayList;

// 群主的类
public class Manager extends User {

public Manager() {
}

public Manager(String name, int money) {
super(name, money);
}

public ArrayList<Integer> send(int totalMoney, int count) {
// 首先需要一个集合,用来存储若干个红包的金额
ArrayList<Integer> redList = new ArrayList<>();

// 首先看一下群主自己有多少钱
int leftMoney = super.getMoney(); // 群主当前余额
if (totalMoney > leftMoney) {
System.out.println("余额不足");
return redList; // 返回空集合
}

// 扣钱,其实就是重新设置余额
super.setMoney(leftMoney - totalMoney);

// 发红包需要平均拆分成为count份
int avg = totalMoney / count;
int mod = totalMoney % count; // 余数,也就是甩下的零头

// 除不开的零头,包在最后一个红包当中
// 下面把红包一个一个放到集合当中
for (int i = 0; i < count - 1; i++) {
redList.add(avg);
}

// 最后一个红包
int last = avg + mod;
redList.add(last);

return redList;
}

}

定义 Member 群成员类, 代码如下:

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
import java.util.ArrayList;
import java.util.Random;

// 普通成员
public class Member extends User {

public Member() {
}

public Member(String name, int money) {
super(name, money);
}

public void receive(ArrayList<Integer> list) {

// 从多个红包当中随便抽取一个,给我自己。
// 随机获取一个集合当中的索引编号
int index = new Random().nextInt(list.size());

// 根据索引,从集合当中删除,并且得到被删除的红包,给我自己
int delta = list.remove(index);

// 当前成员自己本来有多少钱:
int money = super.getMoney();

// 加法,并且重新设置回去
super.setMoney(money + delta);

}
}

定义 LuckMoneyTest 测试类, 代码如下:

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
import java.util.ArrayList;

public class LuckMoneyTest {

public static void main(String[] args) {
Manager manager = new Manager("群主", 100);

Member one = new Member("成员A", 0);
Member two = new Member("成员B", 0);
Member three = new Member("成员C", 0);

manager.show(); // 100
one.show(); // 0
two.show(); // 0
three.show(); // 0
System.out.println("===============");

// 群主总共发 20 块钱,分成 3 个红包
ArrayList<Integer> redList = manager.send(20, 3);
// 三个普通成员收红包
one.receive(redList);
two.receive(redList);
three.receive(redList);

manager.show(); // 100 - 20 = 80
// 6、6、8,随机分给三个人
one.show();
two.show();
three.show();
}

}

最后的随机测试结果如下 【随机的不唯一】

1
2
3
4
5
6
7
8
9
我叫:群主,我有多少钱:100
我叫:成员A,我有多少钱:0
我叫:成员B,我有多少钱:0
我叫:成员C,我有多少钱:0
===========================
我叫:群主,我有多少钱:50
我叫:成员A,我有多少钱:16
我叫:成员B,我有多少钱:16
我叫:成员C,我有多少钱:18

接口

接口的概述

接口 是 Java 语言中的一种 引用数据类型 ,是方法的集合。如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法,包含 抽象方法(JDK7及以前),默认方法和静态方法(JDK8),私有方法(JDK9)。


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


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


生活中接口和 Java 中接口对比:

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

接口的定义格式

接口的定义格式,代码如下:

1
2
3
4
5
6
7
8
public interface 接口名称 {

//抽象方法
//默认方法
//静态方法
//私有方法

}

接口的定义的示例代码如下【下面的过程中会使用到】:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 接口的定义
public interface MyInterfaceAbstract {

// 01-定义一个抽象方法
public abstract void methodAbs1();

// 02-这也是抽象方法,省略了 public
abstract void methodAbs2();

// 03-这也是抽象方法,省略了 abstract
public void methodAbs3();

// 04-这也是抽象方法,省略了 public 和 abstract
void methodAbs4();

}

抽象方法

抽象方法的定义

抽象方法的定义 和相关介绍如下:

1
2
3
4
5
6
7
8
9
10
11
+++ 抽象方法的定义

+ 在任何版本的 Java 中,接口都能定义抽象方法。
+ 抽象方法定义格式:public abstract 返回值类型 方法名称(参数列表);

- 注意事项:
+ 1、接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract
+ 2、这两个关键字修饰符,可以选择性地省略。(如果是刚学,不推荐省略。)
+ 3、方法的三要素,可以随意定义。

+++ 抽象方法的定义

抽象方法的使用

第一步: 先定义 MyInterfaceAbstract 接口 的实现类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// MyInterfaceAbstract 接口的实现类
public class MyInterfaceAbstractImpl implements MyInterfaceAbstract {

@Override
public void methodAbs1() {
System.out.println("这是第一个方法!");
}

@Override
public void methodAbs2() {
System.out.println("这是第二个方法!");
}

@Override
public void methodAbs3() {
System.out.println("这是第三个方法!");
}

@Override
public void methodAbs4() {
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
/**
* 接口就是多个类的公共规范,是一种引用数据类型。最重要的内容就是其中的抽象方法。
* 如何定义一个接口的格式:
* public interface 接口名称 {
* // 接口内容
* }
*
* 备注:换成了关键字 interface 之后,编译生成的字节码文件仍然是:.java --> .class。
* 如果是 Java 7,那么接口中可以包含的内容有:1、常量 2、抽象方法
* 如果是 Java 8,还可以额外包含有: 3、默认方法 4、静态方法
* 如果是 Java 9,还可以额外包含有: 5、私有方法
*
* 接口使用步骤:
* 1、接口不能直接使用,必须有一个 “实现类” 来 “实现” 该接口。
* 实现类的格式:
* public class 实现类名称 implements 接口名称 {
* // ...
* }
* 2、接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。实现的过程就是去掉 abstract 关键字,加上方法体大括号。
* 3、创建实现类的对象,进行使用。
* 注意事项:如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。
*/

// 抽象方法测试类
public class AbstractMethodTest {
public static void main(String[] args) {

// 错误写法!不能直接 new 接口对象使用
// MyInterfaceAbstract inter = new MyInterfaceAbstract();

// 创建实现类的对象使用
MyInterfaceAbstractImpl impl = new MyInterfaceAbstractImpl();
impl.methodAbs1();
impl.methodAbs2();

}

}

默认方法

默认方法的定义

默认方法的定义 和相关介绍如下:

1
2
3
4
5
6
7
8
9
10
11
12
+++ 默认方法的定义

+ 从 Java 8 开始,接口里允许定义默认方法。

+ 默认方法的格式:
+ public default 返回值类型 方法名称(参数列表) {
+ // 内容
+ }

- 默认方法用处:接口当中的默认方法,可以解决接口升级的问题。

+++ 默认方法的定义

默认方法的使用

第一步: 先定义 MyInterfaceDefault 接口,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义 MyInterfaceDefault 接口
public interface MyInterfaceDefault {

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

// 新添加了一个抽象方法
// public abstract void methodAbs2();

// 新添加的方法,改成默认方法
public default void methodDefault() {
System.out.println("这是新添加的默认方法");
}

}

第二步: 定义 MyInterfaceDefault 接口 的两个实现类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 定义 MyInterfaceDefault 接口实现类 A
public class MyInterfaceDefaultA implements MyInterfaceDefault {

@Override
public void methodAbs() {
System.out.println("实现了抽象方法,AAA");
}

}

// 定义 MyInterfaceDefault 接口实现类 B
public class MyInterfaceDefaultB implements MyInterfaceDefault {

@Override
public void methodAbs() {
System.out.println("实现了抽象方法,BBB");
}

@Override
public void methodDefault() {
System.out.println("实现类B覆盖重写了接口的默认方法");
}

}

第三步: 定义测试类 ,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 定义测试类
* 1、接口的默认方法,可以通过接口实现类对象直接调用。
* 2、接口的默认方法,也可以被接口实现类进行覆盖重写。
*/
public class DefaultMethodTest {
public static void main(String[] args) {

// 创建了实现类的对象
MyInterfaceDefaultA a = new MyInterfaceDefaultA();
a.methodAbs(); // 调用抽象方法,实际运行的是右侧实现类。

// 调用默认方法,如果实现类当中没有,会向上找接口
a.methodDefault(); // 这是新添加的默认方法
System.out.println("==========");

MyInterfaceDefaultB b = new MyInterfaceDefaultB();
b.methodAbs();
b.methodDefault(); // 实现类 B 覆盖重写了接口的默认方法

}
}

静态方法

静态方法的定义

1
2
3
4
5
6
7
8
9
10
11
12
+++ 静态方法的定义

+ 从 Java 8 开始,接口当中允许定义静态方法。

+ 静态方法的格式:
+ public static 返回值类型 方法名称(参数列表) {
+ 方法体
+ }

+ 提示:就是将 abstract 或者 default 换成 static 即可,带上方法体。

+++ 静态方法的定义

静态方法的使用

第一步: 定义 MyInterfaceStatic 接口 ,代码如下:

1
2
3
4
5
6
7
8
// 定义接口
public interface MyInterfaceStatic {

public static void methodStatic() {
System.out.println("这是接口的静态方法!");
}

}

第二步: 定义 MyInterfaceStatic 接口 的实现类,代码如下:

1
2
3
public class MyInterfaceStaticImpl implements MyInterfaceStatic {
// 接口的静态方法不需要使用到接口实现类,只要接口名调用静态方法就可以了。
}

第三步: 定义测试类 ,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 定义测试类:
* 注意事项:不能通过接口实现类的对象来调用接口当中的静态方法。
* 正确用法:通过接口名称,直接调用其中的静态方法。
* 调用格式:接口名称.静态方法名(参数);
*/
public class StaticMethodTest {
public static void main(String[] args) {

// 创建了实现类对象
MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();

// 错误写法!
// impl.methodStatic();

// 直接通过接口名称调用静态方法
MyInterfaceStatic.methodStatic();

}
}

私有方法

私有方法的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+++ 私有方法的定义

+ 问题描述:我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题。但是这个共有方法不应该让实现类使用,应该是私有化的。
+ 解决方案:从 Java 9 开始,接口当中允许定义私有方法。
+
- 1、普通私有方法,解决多个默认方法之间重复代码问题
+ 普通私有方法的格式:
+ private 返回值类型 方法名称(参数列表) {
+ 方法体
+ }
+
- 2、静态私有方法,解决多个静态方法之间重复代码问题
+ 静态私有方法的格式:
+ private static 返回值类型 方法名称(参数列表) {
+ 方法体
+ }

+++ 私有方法的定义

私有方法的使用

第一步: 定义两个接口:MyInterfacePrivateA 和 MyInterfacePrivateB ,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义接口 A
public interface MyInterfacePrivateA {

public default void methodDefault1() {
System.out.println("默认方法1");
methodCommon();
}

public default void methodDefault2() {
System.out.println("默认方法2");
methodCommon();
}

private void methodCommon() {
System.out.println("AAA");
System.out.println("BBB");
System.out.println("CCC");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义接口 B
public interface MyInterfacePrivateB {

public static void methodStatic1() {
System.out.println("静态方法1");
methodStaticCommon();
}

public static void methodStatic2() {
System.out.println("静态方法2");
methodStaticCommon();
}

private static void methodStaticCommon() {
System.out.println("AAA");
System.out.println("BBB");
System.out.println("CCC");
}

}

第二步: 定义接口实现类 ,代码如下:

1
2
3
4
5
6
7
8
9
// 定义接口实现类
public class MyInterfacePrivateAImpl implements MyInterfacePrivateA {

public void methodAnother() {
// 直接访问到了接口中的默认方法,这样是错误的!
// methodCommon();
}

}

第二步: 定义测试类 ,代码如下:

1
2
3
4
5
6
7
8
9
10
11
// 定义测试类
public class PrivateMethodTest {
public static void main(String[] args) {

MyInterfacePrivateB.methodStatic1();
MyInterfacePrivateB.methodStatic2();
// 错误写法!
// MyInterfacePrivateB.methodStaticCommon();

}
}

常量定义和使用

1
2
3
4
5
6
7
8
9
10
11
12
+++ 常量定义和使用

+ 接口当中也可以定义“成员变量”,但是必须使用 public static final 三个关键字进行修饰。从效果上看,这其实就是接口的【常量】。
+ 格式:public static final 数据类型 常量名称 = 数据值;
+ 备注:一旦使用 final 关键字进行修饰,说明不可改变。

- 注意事项:
+ 1、接口当中的常量,可以省略 public static final,注意:不写也照样是这样。
+ 2、接口当中的常量,必须进行赋值;不能不赋值。
+ 3、接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则,并非强制)

+++ 常量定义和使用

常量定义和使用 的代码演示如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义接口
interface MyInterfaceConst {

// 这其实就是一个常量,一旦赋值,不可以修改
public static final int NUM_OF_MY_CLASS = 12;

}

// 定义测试类
public class InterfaceConstTest {
public static void main(String[] args) {

// 访问接口当中的常量
System.out.println(MyInterfaceConst.NUM_OF_MY_CLASS);

}
}

接口的内容小结

接口的内容小结如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+++ 接口的内容小结

+ 在 Java 9+ 版本中,接口的内容可以有:
+ 1、成员变量其实是常量,格式:[public] [static] [final] 数据类型 常量名称 = 数据值;
- 注意:常量必须进行赋值,而且一旦赋值不能改变。常量名称完全大写,用下划线进行分隔。

+ 2、接口中最重要的就是抽象方法,格式:[public] [abstract] 返回值类型 方法名称(参数列表);
- 注意:实现类必须覆盖重写接口所有的抽象方法,除非实现类是抽象类。

+ 3、从 Java 8 开始,接口里允许定义默认方法,格式:[public] default 返回值类型 方法名称(参数列表) { 方法体 }
- 注意:默认方法也可以被覆盖重写。

+ 4、从Java 8开始,接口里允许定义静态方法,格式:[public] static 返回值类型 方法名称(参数列表) { 方法体 }
- 注意:应该通过接口名称进行调用,不能通过实现类对象调用接口静态方法

+ 5、从 Java 9 开始,接口里允许定义私有很乏,格式:
+ 普通私有方法:private 返回值类型 方法名称(参数列表) { 方法体 }
+ 静态私有方法:private static 返回值类型 方法名称(参数列表) { 方法体 }
- 注意:private 的方法只有接口自己才能调用,不能被实现类或别人使用。

+++ 接口的内容小结

继承并实现多个接口

继承并实现多个接口 的代码演示如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface MyInterfaceA {

// 错误写法!接口不能有静态代码块
// static {
//
// }

// 错误写法!接口不能有构造方法
// public MyInterfaceA() {
//
// }

public abstract void methodA();

public abstract void methodAbs();

public default void methodDefault() {
System.out.println("默认方法AAA");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface MyInterfaceB {

// 错误写法!接口不能有静态代码块
// static {
//
// }

// 错误写法!接口不能有构造方法
// public MyInterfaceA() {
//
// }

public abstract void methodB();

public abstract void methodAbs();

public default void methodDefault() {
System.out.println("默认方法BBB");
}

}
1
2
3
4
5
6
7
public interface MyInterface {

public default void method() {
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
public class MyInterfaceImpl /*extends Object*/ implements MyInterfaceA, MyInterfaceB {

@Override
public void methodA() {
System.out.println("覆盖重写了A方法");
}


@Override
public void methodB() {
System.out.println("覆盖重写了B方法");
}

@Override
public void methodAbs() {
System.out.println("覆盖重写了AB接口都有的抽象方法");
}

@Override
public void methodDefault() {
System.out.println("对多个接口当中冲突的默认方法进行了覆盖重写");
}

}
1
2
3
4
5
6
7
public class Fu {

public void method() {
System.out.println("父类方法");
}

}
1
2
3
public class Zi extends Fu implements MyInterface {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* 使用接口的时候,需要注意:
* 1、接口是没有静态代码块或者构造方法的。
* 2、一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。
* 实现多个接口格式如下:
* public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
* // 覆盖重写所有抽象方法
* }
* 3、如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
* 4、如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。
* 5、如果实现类锁实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写。
* 6、一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法。
*/
public class ExtendsAndImplementsTest {
public static void main(String[] args) {

Zi zi = new Zi();
zi.method();

}
}

接口之间的多继承

接口之间的 多继承 的代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
public interface MyInterfaceA {

public abstract void methodA();

public abstract void methodCommon();

public default void methodDefault() {
System.out.println("AAA");
}

}
1
2
3
4
5
6
7
8
9
10
11
public interface MyInterfaceB {

public abstract void methodB();

public abstract void methodCommon();

public default void methodDefault() {
System.out.println("BBB");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 这个子接口当中有几个方法?答:4个。
* methodA 来源于接口 A
* methodB 来源于接口 B
* methodCommon 同时来源于接口 A 和 B
* method 来源于我自己
*/
public interface MyInterface extends MyInterfaceA, MyInterfaceB {

public abstract void method();

@Override
public default void methodDefault() {

}

}
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 MyInterfaceImpl implements MyInterface {

@Override
public void method() {

}

@Override
public void methodA() {

}

@Override
public void methodB() {

}

@Override
public void methodCommon() {

}

}
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 1、类与类之间是单继承的。直接父类只有一个。
* 2、类与接口之间是多实现的。一个类可以实现多个接口。
* 3、接口与接口之间是多继承的。
*
* 注意事项:
* 1、多个父接口当中的抽象方法如果重复,没关系。
* 2、多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,【而且带着 default 关键字】。
*/
public class Demo01Relations {

}

多态

多态的概述

多态 是指同一行为,具有多个不同表现形式,是 封装继承 之后,面向对象的第三大特性。 在生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,在不同的事物中,可以体现出来的不同的形态。 多态描述的就是这样的状态。

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

多态的体现

1
2
3
4
5
6
7
# 多态体现的格式

# 父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
父类类型 变量名 = 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
class Fu {

public void method() {
System.out.println("父类方法");
}

public void methodFu() {
System.out.println("父类特有方法");
}

}

class Zi extends Fu {

@Override
// 重写了父类的 method() 方法
public void method() {
System.out.println("子类方法");
}

}

/*
* 代码当中体现多态性,其实就是一句话:父类引用指向子类对象。
* 格式:父类名称 对象名 = new 子类名称();
* 或者:接口名称 对象名 = new 实现类名称();
*/

// 测试类
public class Demo01Multi {
public static void main(String[] args) {
// 使用多态的写法:左侧父类的引用,指向了右侧子类的对象
Fu obj = new Zi();

obj.method();
obj.methodFu();
}
}

引用类型转换

向上转型

向上转型: 多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。当父类引用指向子类对象时,便是向上转型。 代码如下:

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
class Fu /*extends Object*/ {

int num = 10;

public void showNum() {
System.out.println(num);
}

public void method() {
System.out.println("父类方法");
}

public void methodFu() {
System.out.println("父类特有方法");
}

}
class Zi extends Fu {

int num = 20;
int age = 16;

@Override
public void showNum() {
System.out.println(num);
}

@Override
public void method() {
System.out.println("子类方法");
}

public void methodZi() {
System.out.println("子类特有方法");
}

}
/*
* 访问成员变量的两种方式:
* 1、直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找。
* 2、间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找。
*/
public class Demo01MultiField {

public static void main(String[] args) {
// 使用多态的写法,父类引用指向子类对象
Fu obj = new Zi();
System.out.println(obj.num); // 父:10
// System.out.println(obj.age); // 错误写法!
System.out.println("=============");

// 子类没有覆盖重写,就是父:10
// 子类如果覆盖重写,就是子:20
obj.showNum();
}

}
/*
* 在多态的代码当中,成员方法的访问规则是:看 new 的是谁,就优先用谁,没有则向上找。口诀:编译看左边,运行看右边。
* 对比一下:
* 成员变量:编译看左边,运行还看左边。
* 成员方法:编译看左边,运行看右边。
*/
public class Demo02MultiMethod {
public static void main(String[] args) {
Fu obj = new Zi(); // 多态

obj.method(); // 父子都有,优先用子
obj.methodFu(); // 子类没有,父类有,向上找到父类

// 编译看左边,左边是 Fu,Fu 当中没有 methodZi 方法,所以编译报错。
// obj.methodZi(); // 错误写法!
}
}

向下转型

向下转型: 父类类型向子类类型向下转换的过程,这个过程是强制的。 相关代码如下:

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
public abstract class Animal {
public abstract void eat();
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}

// 子类特有方法
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃SHIT");
}

public void watchHouse() {
System.out.println("狗看家");
}
}
/*
* 向上转型一定是安全的,没有问题的,正确的。但是也有一个弊端:对象一旦向上转型为父类,那么就无法调用子类原本特有的内容。
* 解决方案:用对象的向下转型【还原】。
*/
public class Demo01Main {

public static void main(String[] args) {
// 对象的向上转型,就是:父类引用指向之类对象。
Animal animal = new Cat(); // 本来创建的时候是一只猫
animal.eat(); // 猫吃鱼

// animal.catchMouse(); // 错误写法!

// 向下转型,进行“还原”动作
Cat cat = (Cat) animal;
cat.catchMouse(); // 猫抓老鼠

// 下面是错误的向下转型,本来 new 的时候是一只猫,现在非要当做狗
// 错误写法!编译不会报错,但是运行会出现异常:java.lang.ClassCastException,类转换异常
Dog dog = (Dog) animal;
}

}
/*
* 如何才能知道一个父类引用的对象,本来是什么子类?
* 格式:对象 instanceof 类名称
* 这将会得到一个 boolean 值结果,也就是判断前面的对象能不能当做后面类型的实例。
*/
public class Demo02Instanceof {

public static void main(String[] args) {
Animal animal = new Dog(); // 本来是一只狗
animal.eat(); // 狗吃SHIT

// 如果希望掉用子类特有方法,需要向下转型
// 判断一下父类引用 animal 本来是不是 Dog
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}
// 判断一下 animal 本来是不是 Cat
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}

giveMeAPet(new Dog());
}

public static void giveMeAPet(Animal animal) {
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}
}

}

为什么要转型

1
2
3
4
5
6
+++ 为什么要转型

+ 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有而父类没有的方法。
+ 编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。

+++ 为什么要转型

多态综合案例

案例描述

笔记本电脑(laptop)通常具备使用 USB 设备的功能。在生产时,笔记本都预留了可以插入 USB 设备的 USB 接口,但具体是什么 USB 设备,笔记本厂商并不关心,只要符合 USB 规格的设备都可以。所以定义 USB 接口,必须具备最基本的开启功能和关闭功能。 鼠标和键盘要想能在电脑上使用,那么鼠标和键盘也必须遵守 USB 规范,实现 USB 接口,否则鼠标和键盘的生产出来也无法使用。

案例分析

要实现上述案例,需要经过如下分析:

1
2
3
4
5
6
7
8
9
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ 进行描述笔记本类,实现笔记本使用 USB 鼠标、USB 键盘等 USB 设备。
+ USB 接口: 包含开启功能、关闭功能
+ 笔记本类: 包含运行功能、关机功能、使用 USB 设备功能
+ 鼠标类: 要实现 USB 接口,并具备点击的方法
+ 键盘类: 要实现 USB 接口,具备敲击的方法

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

代码实现

为了完成上述案例,我们需要定义如下几个类或接口。 代码如下:

USB.java
1
2
3
4
5
6
public interface USB {

public abstract void open(); // 打开设备
public abstract void close(); // 关闭设备

}
Mouse.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 鼠标就是一个 USB 设备,所以需要实现 USB 接口
public class Mouse implements USB {
@Override
public void open() {
System.out.println("打开鼠标");
}

@Override
public void close() {
System.out.println("关闭鼠标");
}

public void click() {
System.out.println("鼠标点击");
}
}
Keyboard.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 键盘也是一个 USB 设备,也需要实现 USB 接口
public class Keyboard implements USB {
@Override
public void open() {
System.out.println("打开键盘");
}

@Override
public void close() {
System.out.println("关闭键盘");
}

public void type() {
System.out.println("键盘输入");
}
}
Computer.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
public class Computer {

public void powerOn() {
System.out.println("笔记本电脑开机");
}

public void powerOff() {
System.out.println("笔记本电脑关机");
}

// 使用 USB 设备的方法,使用接口作为方法的参数
public void useDevice(USB usb) {
usb.open(); // 打开设备
if (usb instanceof Mouse) { // 一定要先判断
Mouse mouse = (Mouse) usb; // 向下转型
mouse.click();
} else if (usb instanceof Keyboard) { // 先判断
Keyboard keyboard = (Keyboard) usb; // 向下转型
keyboard.type();
}
usb.close(); // 关闭设备
}

}

定义完上面的类和接口之后,就编写测试类。 代码如下:

DemoMain.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
public class DemoMain {
public static void main(String[] args) {

// 首先创建一个笔记本电脑
Computer computer = new Computer();
computer.powerOn();

// 准备一个鼠标,供电脑使用
// Mouse mouse = new Mouse();
// 首先进行向上转型
USB usbMouse = new Mouse(); // 多态写法
// 参数是USB类型,我正好传递进去的就是USB鼠标
computer.useDevice(usbMouse);

// 创建一个USB键盘
Keyboard keyboard = new Keyboard(); // 没有使用多态写法
// 方法参数是USB类型,传递进去的是实现类对象
computer.useDevice(keyboard); // 正确写法!也发生了向上转型
// 使用子类对象,匿名对象,也可以
// computer.useDevice(new Keyboard()); // 也是正确写法

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

method(10.0); // 正确写法,double --> double
method(20); // 正确写法,int --> double
int a = 30;
method(a); // 正确写法,int --> double

}

public static void method(double num) {
System.out.println(num);
}
}

TIPS: 到此,多态学习结束!!!

final 关键字

final 概述

学习了继承后,我们知道,子类可以在父类的基础上改写父类内容,比如,方法重写。那么我们能不能随意的继承API中提供的类,改写其内容呢? 显然这是不合适的。为了避免这种随意改写的情况,Java提供了 final 关键字,用于修饰不可改变的内容。 具体介绍如下:


1
2
3
4
5
6
7
8
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ final 关键字:意为不可改变。可以用于修饰类、方法和变量。
+ 类: 被修饰的类,不能被继承。
+ 方法: 被修饰的方法,不能被重写。
+ 变量: 被修饰的变量,不能被重新赋值。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

使用方式

第一种: final 修饰类。被修饰的这个类无法被继承。 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
* 当 final 关键字用来修饰一个类的时候,格式:
* public final class 类名称 {
* // ...
* }
*
* 含义:被修饰的这个类不能有任何的子类。(太监类)
* 注意:一个类如果是 final 修饰的,那么其中所有的成员方法都无法进行覆盖重写(因为没儿子。)
*/
public final class MyFinalClass /*extends Object*/ {

public void method() {
System.out.println("方法执行!");
}

}

第二种: final 修饰方法。被修饰的这个方法不能被重写。 代码如下:

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
/*
* 当 final 关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重写。格式如下:
* 修饰符 final 返回值类型 方法名称(参数列表) {
* // 方法体
* }
*
* 注意事项:对于类、方法来说,abstract 关键字和 final 关键字不能同时使用,因为矛盾。
*/
public abstract class Fu {

public final void method() {
System.out.println("父类方法执行!");
}

// 加上 final 就是错误的写法
public abstract /*final*/ void methodAbs() ;

}

public class Zi extends Fu {

@Override
public void methodAbs() {

}

// 错误写法!不能覆盖重写父类当中 final 的方法
// @Override
// public void method() {
// System.out.println("子类覆盖重写父类的方法!");
// }

}

第三种: final 修饰变量。被修饰的这个变量,不能被重新赋值,即成为了常量。 代码如下:

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
/*
* final 关键字代表最终、不可改变的。
* 常见四种用法:
* 1、可以用来修饰一个类
* 2、可以用来修饰一个方法
* 3、还可以用来修饰一个局部变量
* 4、还可以用来修饰一个成员变量
*/
public class Demo01Final {

public static void main(String[] args) {
int num1 = 10;
System.out.println(num1); // 10
num1 = 20;
System.out.println(num1); // 20

// 一旦使用 final 用来修饰局部变量,那么这个变量就不能进行更改。 “一次赋值,终生不变”
final int num2 = 200;
System.out.println(num2); // 200

// num2 = 250; // 错误写法!不能改变!
// num2 = 200; // 错误写法!

// 正确写法!只要保证有唯一一次赋值即可
final int num3;
num3 = 30;

// 对于基本类型来说,不可变说的是变量当中的数据不可改变
// 对于引用类型来说,不可变说的是变量当中的地址值不可改变
Student stu1 = new Student("赵丽颖");
System.out.println(stu1);
System.out.println(stu1.getName()); // 赵丽颖

stu1 = new Student("霍建华");
System.out.println(stu1);
System.out.println(stu1.getName()); // 霍建华
System.out.println("===============");

final Student stu2 = new Student("高圆圆");
// 错误写法!final 的引用类型变量,其中的地址不可改变
// stu2 = new Student("赵又廷");
System.out.println(stu2.getName()); // 高圆圆

stu2.setName("高圆圆圆圆圆圆");
System.out.println(stu2.getName()); // 高圆圆圆圆圆圆
}

}

TIPS: 到此,final 关键字学习结束!!!

权限修饰符

权限修饰符概述

在 Java 中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限。 具体介绍如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ public【公共的】
+ public 是权限最大的修饰符,他可以修饰类,成员变量,成员方法,构造方法。被 public 修饰后,可以在任何一个类中,不管同不同包,任意使用。

+++ protected【受保护的】
+ protected 可以修饰成员变量,成员方法,构造方法,但不能修饰类【此处指的是外部类,内部类不加以考虑】。
+ 被 protected 修饰后,只能被同包下的其他类访问。如果不同包下的类要访问被 protected 修饰的成员,这个类必须是其子类。

+++ 【default:默认的】
+ defalut 即不写任何关键字,它可以修饰类,成员变量,成员方法,构造方法。被默认权限修饰后,其只能被本类以及同包下的其他类访问。

+++ private 【私有的】
+ private 可以修饰成员变量,成员方法,构造方法,不能修饰类【此刻指的是外部类,内部类不加以考虑】。
+ 被 private 修饰的成员只能在其修饰的本类中访问,在其他类中不能调用。如果一定要访问,那么可以通过 set 和 get 方法向外界提供访问方式。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

访问能力

上面已经介绍过了四种权限修饰符,那么接下来介绍它们各自的访问能力。 如下:

1
2
3
4
5
6
7
8
9
10
11
12
########################################################################################################

# Java 中有四种权限修饰符,其访问能力如下:
public > protected > (default) > private
YES YES YES YES 同一个类
YES YES YES NO 同一个包【子类与其他类】
YES YES NO NO 不同包子类
YES NO NO NO 不同包非子类

# 注意事项:(default)并不是关键字 “default” ,而是什么都不写。例如:int i = 0; 这个就是 default 。

########################################################################################################

代码模板

介绍完四种权限修饰符的访问能力之后,我们就使用代码测试以下。 第一个包中的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package edu.jgsu.demo02;

// demo02 包中的 MyAnother 类
public class MyAnother {

public void anotherMethod() {
// System.out.println(new MyClass().num);
}

}

// demo02 包中的 Demo01Main 类
public class Demo01Main {
}

第二个包中的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package edu.jgsu.demo02.sub;

import cn.itcast.day11.demo02.MyClass;

public class MySon extends MyClass {


public void methodSon() {
// System.out.println(super.num);
}

}

package edu.jgsu.demo02.sub;

import cn.itcast.day11.demo02.MyClass;

public class Stranger {

public void methodStrange() {
System.out.println(new MyClass().num);
}
}

TIPS: 到此,权限修饰符学习结束!!!

内部类

内部类概述

将类 A 定义在类 B 里面,那么类 A 就称为 内部类 ,类 B 则称为 外部类 具体详情参考如下介绍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ 内部类的分类: 1、成员内部类 2、局部内部类 3、匿名内部类 4、静态内部类
+++ 成员内部类的定义格式如下:
+ 修饰符 class 外部类名称 {
+ 修饰符 class 内部类名称 {
+ // ...
+ }
+ // ...
+ }

+++ 注意:内用外,随意访问;外用内,需要内部类对象。 那么如何使用成员内部类?有如下两种方式:
+ 1、间接方式:在外部类的方法当中,使用内部类;然后 main 只是调用外部类的方法。
+ 2、直接方式,公式:类名称 对象名 = new 类名称();【外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();】

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Outer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Outer {    // 这个是外部类

private String name; // 外部类的私有属性

public class Inner { // 这个是内部类
int age = 10;

public void show() {
System.out.println("I am the Inner class " + age);
}
}
public static void main(String[] args) {
Outer outer = new Outer(); // 外部类创建对象
Inner inner = outer.new Inner(); // 内部类创建对象
inner.show();
}

}

成员内部类

成员内部类 是最常见的内部类,也称作普通内部类。 具体参考如下代码:

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

public class Heart { // 成员内部类
// 内部类的方法
public void beat() {
System.out.println("心脏跳动:蹦蹦蹦!");
System.out.println("我叫:" + name); // 正确写法!
}
}

// 外部类的成员变量
private String name;

// 外部类的方法
public void methodBody() {
System.out.println("外部类的方法");
new Heart().beat();
}

public String getName() {
return name;
}

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

如果外部类和成员内部类有重名的变量或方法,那该如何进行访问呢? 详情参考如下代码:

Outer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 如果出现了重名现象,那么格式是:外部类名称.this.外部类成员变量名
public class Outer {

int num = 10; // 外部类的成员变量

public class Inner /*extends Object*/ {

int num = 20; // 内部类的成员变量

public void methodInner() {
int num = 30; // 内部类方法的局部变量
System.out.println(num); // 局部变量,就近原则
System.out.println(this.num); // 内部类的成员变量
System.out.println(Outer.this.num); // 外部类的成员变量
}

}

}

上述定义了 Inner 内部类,那么我们如何去使用呢? 使用内部类的代码如下:

InnerClassTest.java
1
2
3
4
5
6
7
8
9
public class InnerClassTest {

public static void main(String[] args) {
// 外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
Outer.Inner obj = new Outer().new Inner();
obj.methodInner();
}

}

局部内部类

成员内部类 可以看作是外部类的一个成员,所以把它叫做成员内部类。 顾名思义:如果一个类定义在一个方法的内部,那么就把这个类叫做 内部类 注意: 只有当前所属的方法才能使用它,出了这个方法就不能用了。 更多详情介绍如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+ 如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。 “局部”:只有当前所属的方法才能使用它,出了这个方法就不能用了。
+ 局部内部类的定义格式:
+ 修饰符 class 外部类名称 {
+ 修饰符 返回值类型 外部类方法名称(参数列表) {
+ class 局部内部类名称 {
+ // ...
+ }
+ }
+ }
+ 小节一下类的权限修饰符: public > protected > (default) > private
+ 定义一个内部类的时候,权限修饰符规则:
+ 1、外部类:public / (default)
+ 2、成员内部类:public / protected / (default) / private
+ 3、局部内部类:什么都不能写

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

上述已经介绍了 局部内部类 的定义格式,那么我们就来定义一个吧。 代码如下:

Outer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Outer {

public void methodOuter() {
// 定义局部内部类
class Inner {
int num = 10;

public void methodInner() {
System.out.println(num); // 10
}
}

Inner inner = new Inner();
inner.methodInner();
}

}
Outer.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
/**
* 局部内部类如果想要访问所在方法的局部变量,那么这个局部变量必须是 final 修饰的。
* 备注:从 Java 8+ 开始,只要局部变量事实不变,那么 final 关键字可以省略。
* 原因:
* 1、new 出来的对象在堆内存当中。
* 2、局部变量是跟着方法走的,在栈内存当中。
* 3、方法运行结束之后,立刻出栈,局部变量就会立刻消失。
* 4、但是 new 出来的对象会在堆当中持续存在,直到垃圾回收消失。
*/
public class Outer {

public void methodOuter() {
int num = 10; // 所在方法的局部变量,由 final 修饰,Java8 之后 final 可省略

class MyInner {
public void methodInner() {
System.out.println(num);
// 下列语句编译直接报错,原因是 num 是常量
// System.out.println(num++);
}
}

MyInner inner = new MyInner();
inner.methodInner();
}

public static void main(String[] args) {
Outer outer = new Outer();
outer.methodOuter();
}

}

匿名内部类

匿名内部类 就是定义在其他类方法中的且没有名字的类【也算是局部内部类】,是很常用的一种内部类。 具体介绍如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ 定义匿名内部类的前提
+ 定义匿名内部类的前提是,该类必须继承一个外部类或者实现接口

+++ 定义匿名内部类的作用
+ 把定义子类、重写父类的方法、创建子类对象、合成一步完成;把定义实现类、重写接口的抽象方法、创建实现类对象、合成一步完成

+++ 定义匿名内部类的结果
+ 就是获得了一个子类对象,但是这个子类对象没有名字

+++ PS:
+ 因为内部类是匿名的,也就是没有名字,所以该类一次只能调用一个方法,通常在写程序时也是如此,这也正是他优点。
+ 在只写一个内部类调用方法时,它可以使整个代码看上去很简洁。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1
2
3
4
5
6
7
8
9
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

--- 匿名内部类的注意事项:
- 1、匿名内部类不能定义任何静态成员、方法
- 2、匿名内部类中的方法不能是抽象的
- 3、匿名内部类必须实现接口或抽象父类的所有抽象方法
- 4、匿名内部类访问的外部类成员变量或成员方法必须用 static 修饰

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

上述介绍了这么多,那就代码实现一下吧。 首先定义一个接口,代码如下:

Inter.java
1
2
3
interface Inter {
public void print();
}

然后我们就开始使用 匿名内部类 了。 代码如下:

Outer.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
public class Outer {

class InnerImpl implements Inter {
public void printStr() {
System.out.println("print");
}
}

public void method(){

// 匿名内部类【实现接口的方式】
new Inter() { // 实现 Inter 接口
public void printStr() { // 重写抽象方法
System.out.println("Java");
}
}.printStr();

// 匿名内部类【继承实现类的方式】
new InnerImpl() {}.printStr();

}

public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}

}

TIPS: 匿名内部类 还有更多的用法,需要了解更多相关知识,那就需要你自行查阅了。

静态内部类

静态内部类 就是用 static 修饰的内部类 ,这种内部类有如下特点:

1
2
3
4
5
6
7
8
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++ 静态内部类的特点:
+ 1、静态内部类不能直接访问外部类的非静态成员,但可以通过 new 外部类().成员 的方式访问
+ 2、如果外部类的静态成员与内部类的静态成员相同, 可以通过 类名.静态成员 来访问外部类的静态成员;如果不同,可以直接调用外部类的静态成员名
+ 3、创建静态内部类的对象时,不需要通过外部类的对象便可以直接创建

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

介绍完 静态内部类 的特点,那就代码体验一下吧!!! 代码如下:

Outer.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
public class Outer {

private String name = "Jack";
private static Integer age = 23;
private static String address = "HangZhou";
private static String province = "BeiJing";


public static class Inner {
static String name = "Lucy";
private static String address = "HangZhou";

private void show() {
System.out.println("外部类的 name 【无 static 修饰】:" + new Outer().name);
System.out.println("外部类的 age 【有 static 修饰】:" + Outer.age);
System.out.println("外部类的 address 【与内部类属性名相同】:" + Outer.address);
System.out.println("外部类的 city 【与内部类属性名不同】:" + province);

System.out.println("内部类的 name :" + name);
System.out.println("内部类的 address 【与外部类属性名相同】:" + address);
}
}

public static void main(String[] args) {
Inner inner = new Inner(); // 静态内部类可以直接创建对象
inner.show();
}

}

常用 API

TIPS: 由于本文过长,常用 API 单独成篇,请阅读: Java 常用 API
到此为止,Java 基础(中篇)就结束了。接下来就是 Java 重点内容,集合与 IO 流以及异常多线程。Java 基础(下篇)

    / 

📚 本站推荐文章
  👉 从 0 开始搭建 Hexo 博客
  👉 计算机网络入门教程
  👉 数据结构入门
  👉 算法入门
  👉 IDEA 入门教程

一言句子获取中...

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×