目录
  1. 第一章 面向对象思想
    1. 1.1 面向对象思想概述
    2. 1.2 类和对象
    3. 1.3 类的定义
    4. 1.4 对象的使用
    5. 1.5 对象内存图
    6. 1.6 成员变量和局部变量区别
  2. 第二章 封装
    1. 2.1 封装概述
    2. 2.2 封装的步骤
    3. 2.3 封装的操作——private关键字
    4. 2.4 封装优化1——this关键字
    5. 2.5 封装优化2——构造方法
    6. 2.6 标准代码——JavaBean
  3. 第三章 Scanner类
    1. 3.1 Scanner使用步骤
    2. 3.2 Scanner练习
  4. 第四章 匿名对象【了解】
  5. 第四章 Random类
    1. 4.1 什么是Random类
    2. 4.2 Random使用步骤
    3. 4.3 练习
  6. 第五章 ArrayList类
    1. 5.1 引入——对象数组
    2. 5.2 什么是ArrayList类
    3. 5.3 ArrayList使用步骤
    4. 5.4 常用方法和遍历
    5. 5.5 如何存储基本数据类型
  7. 第六章 String类
    1. 6.1 String类概述
    2. 6.2 使用步骤
    3. 6.3 常用方法
    4. 6.4 String类的练习
  8. 第七章 static关键字
    1. 7.1 概述
    2. 7.2 定义和使用格式
    3. 7.3 静态原理图解
    4. 7.4 静态代码块
  9. 第八章 Arrays类
    1. 8.1 概述
    2. 8.2 操作数组的方法
    3. 8.3 练习
  10. 第九章 Math类
    1. 9.1 概述
    2. 9.2 基本运算的方法
    3. 9.3 练习
  11. 第十章 继承
    1. 10.1 概述
    2. 10.2 继承的格式
    3. 10.3 继承后的特点——成员变量
    4. 10.4 继承后的特点——成员方法
    5. 10.5 继承后的特点——构造方法
    6. 10.6 super和this
    7. 10.7 继承的特点
  12. 第十一章 抽象类
    1. 11.1 概述
    2. 11.2 abstract使用格式
    3. 11.3 注意事项
  13. 第十二章 接口
    1. 12.1 概述
    2. 12.2 定义格式
    3. 12.3 基本的实现
    4. 12.4 接口的多实现
    5. 12.5 接口的多继承【了解】
    6. 12.6 其他成员特点
  14. 第十三章 多态
    1. 13.1 概述
    2. 13.2 多态的体现
    3. 13.3 多态的好处
    4. 13.4 引用类型转换
  15. 第十四章 final关键字
    1. 14.1 使用方式
  16. 第十五章 权限修饰符
    1. 15.1 概述
    2. 15.2 不同权限的访问能力
  17. 第十六章 内部类
    1. 16.1 概述
    2. 16.2 匿名内部类【重点】
  18. 第十七章 引用类型用法总结
    1. 17.1 class作为成员变量
    2. 17.2 interface作为成员变量
    3. 17.3 interface作为方法参数和返回值类型
Java面向对象与常见类

第一章 面向对象思想

1.1 面向对象思想概述

概述
Java语言是一种面向对象的程序设计语言,而面向对象思想是一种程序设计思想,我们在面向对象思想的指引下,使用Java语言去设计、开发计算机程序。 这里的对象泛指现实中一切事物,每种事物都具备自己的属性行为。面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,描述成计算机事件的设计思想。 它区别于面向过程思想,强调的是通过调用对象的行为来实现功能,而不是自己一步一步的去操作实现。

举例
洗衣服:

  • 面向过程:把衣服脱下来–>找一个盆–>放点洗衣粉–>加点水–>浸泡10分钟–>揉一揉–>清洗衣服–>拧干–>晾起来
  • 面向对象:把衣服脱下来–>打开全自动洗衣机–>扔衣服–>按钮–>晾起来

区别:

  • 面向过程:强调步骤。
  • 面向对象:强调对象,这里的对象就是洗衣机。

特点
面向对象思想是一种更符合我们思考习惯的思想,它可以将复杂的事情简单化,并将我们从执行者变成了指挥者。面向对象的语言中,包含了三大基本特征,即封装、继承和多态。

1.2 类和对象

环顾周围,你会发现很多对象,比如桌子,椅子,同学,老师等。桌椅属于办公用品,师生都是人类。那么什么是类呢?什么是对象呢?

什么是类

  • :是一组相关属性行为的集合。可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该类事物。
    现实中,描述一类事物:
  • 属性:就是该事物的状态信息。
  • 行为:就是该事物能够做什么

什么是对象

  • 对象:是一类事物的具体体现。对象是类的一个实例(对象并不是找个女朋友),必然具备该类事物的属性和行为。

现实中,一类事物的一个实例:一只小猫。
举例:一只小猫。
属性:tom、5kg、2 years、yellow。 行为:溜墙根走、蹦跶的跑、喵喵叫。

类与对象的关系

  • 类是对一类事物的描述,是抽象的
  • 对象是一类事物的实例,是具体的
  • 类是对象的模板,对象是类的实体

1.3 类的定义

事物与类的对比
现实世界的一类事物:
属性:事物的状态信息。 行为:事物能够做什么。
Java中用class描述事物也是如此:
成员变量:对应事物的属性 成员方法:对应事物的行为
类的定义格式

1
2
3
4
public class ClassName {
//成员变量
//成员方法
}
  • 定义类:就是定义类的成员,包括成员变量成员方法
  • 成员变量:和以前定义变量几乎是一样的。只不过位置发生了改变。在类中,方法外
  • 成员方法:和以前定义方法几乎是一样的。只不过把static去掉,static的作用在面向对象后面课程中再详细讲解。

类的定义格式举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Student {
//成员变量
String name;//姓名
int age;//年龄
//成员方法
//学习的方法
publicvoid study() {
System.out.println("好好学习,天天向上");
}
//吃饭的方法
publicvoid eat() {
System.out.println("学习饿了要吃饭");
}
}

1.4 对象的使用

对象的使用格式
创建对象:
类名 对象名 = new 类名();
使用对象访问类中的成员:
对象名.成员变量;
对象名.成员方法();

成员变量的默认值

命令 数据类型 默认值
基本类型 整数(byte,short,int,long) 0
浮点数(float,double) 0.0
字符(char) ‘\u0000’
布尔(boolean) false
引用类型 数组,类,接口 null

1.5 对象内存图

一个对象,调用一个方法内存图

通过上图,我们可以理解,在栈内存中运行的方法,遵循”先进后出,后进先出”的原则。变量p指向堆内存中的空间,寻找方法信息,去执行该方法。
但是,这里依然有问题存在。创建多个对象时,如果每个对象内部都保存一份方法信息,这就非常浪费内存了,因为所有对象的方法信息都是一样的。那么如何解决这个问题呢?请看如下图解。

两个对象,调用同一方法内存图

对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。这样哪怕是多个对象,方法信息只保存一份,节约内存空间。

一个引用,作为参数传递到方法中内存图

引用类型作为参数,传递的是地址值

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

变量根据定义位置的不同,我们给变量起了不同的名字。如下图所示:

  • 在类中的位置不同 重点
    • 成员变量:类中,方法外
    • 局部变量:方法中或者方法声明上(形式参数)
  • 作用范围不一样 重点
    • 成员变量:类中
    • 局部变量:方法中
  • 初始化值的不同 重点
    • 成员变量:有默认值
    • 局部变量:没有默认值。必须先定义,赋值,最后使用
  • 在内存中的位置不同 了解
    • 成员变量:堆内存
    • 局部变量:栈内存
  • 生命周期不同 了解
    • 成员变量:随着对象的创建而存在,随着对象的消失而消失
    • 局部变量:随着方法的调用而存在,随着方法的调用完毕而消失

第二章 封装

2.1 封装概述

概述
面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的方式。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。
原则
属性隐藏起来,若需要访问某个属性,提供公共方法对其访问。

2.2 封装的步骤

  1. 使用 private 关键字来修饰成员变量。
  2. 对需要访问的成员变量,提供对应的一对 getXxx 方法 、 setXxx 方法。

2.3 封装的操作——private关键字

private的含义

  1. private是一个权限修饰符,代表最小权限。
  2. 可以修饰成员变量和成员方法。
  3. 被private修饰后的成员变量和成员方法,只在本类中才能访问。

private的使用格式

private 数据类型 变量名

  1. 使用 private 修饰成员变量,代码如下:
    1
    2
    3
    4
    public class Student {
    private String name;
    private int age;
    }
  2. 提供 getXxx 方法 / setXxx 方法,可以访问成员变量,代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Student {
    private String name;
    private int age;
    public void setName(String n) {
    name = n;
    }
    public String getName() {
    return name;
    }
    public void setAge(int a) {
    age = a;
    }
    public int getAge() {
    return age;
    }
    }

2.4 封装优化1——this关键字

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

1
2
3
4
5
6
7
8
9
10
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使用格式

1
this.成员变量名;

使用 this 修饰方法中的变量,解决成员变量被隐藏的问题,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Student {
private String name;
private int age;
public void setName(String name) {
//name = name;
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
//age = age;
this.age = age;
}
public int getAge() {
return age;
}
}

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

2.5 封装优化2——构造方法

当一个对象被创建时候,构造方法用来初始化该对象,给对象的成员变量赋初始值。

小贴士:无论你与否自定义构造方法,所有的类都有构造方法,因为Java自动提供了一个无参数构造方法,一旦自己定义了构造方法,Java自动提供的默认无参数构造方法就会失效。

构造方法的定义格式

1
2
3
修饰符 构造方法名(参数列表){
// 方法体
}

构造方法的写法上,方法名与它所在的类名相同。它没有返回值,所以不需要返回值类型,甚至不需要void。使用构造方法后,代码如下:

1
2
3
4
5
6
7
8
9
10
11
public class Student {
private String name;
private int age;
// 无参数构造方法
public Student() {}
// 有参数构造方法
public Student(String name,int age) {
this.name = name;
this.age = age;
}
}

注意事项

  1. 如果你不提供构造方法,系统会给出无参数构造方法。
  2. 如果你提供了构造方法,系统将不再提供无参数构造方法。
  3. 构造方法是可以重载的,既可以定义参数,也可以不定义参数。

2.6 标准代码——JavaBean

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

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

编写符合 JavaBean 规范的类,以学生类为例,标准代码如下:

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 Student {
//成员变量
private String name;
private int age;
//构造方法
public Student() {}
public Student(String name,int age) {
this.name = name;
this.age = age;
}
//成员方法
publicvoid setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
publicvoid setAge(int age) {
this.age = age;
}
publicint getAge() {
return age;
}
}

测试类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class TestStudent {
public static void main(String[] args) {
//无参构造使用
Student s= new Student();
s.setName("柳岩");
s.setAge(18);
System.out.println(s.getName()+"‐‐‐"+s.getAge());
//带参构造使用
Student s2= new Student("赵丽颖",18);
System.out.println(s2.getName()+"‐‐‐"+s2.getAge());
}
}

第三章 Scanner类

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

1
2
Scanner sc = new Scanner(System.in);
int i = sc.nextInt();

备注:System.in 系统输入指的是通过键盘录入数据。

3.1 Scanner使用步骤

查看类
java.util.Scanner :该类需要import导入后使用。
查看构造方法
public Scanner(InputStream source) : 构造一个新的 Scanner ,它生成的值是从指定的输入流扫描的。
查看成员方法
public int nextInt() :将输入信息的下一个标记扫描为一个 int 值。
使用Scanner类,完成接收键盘录入数据的操作,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
//1. 导包
import java.util.Scanner;
public class Demo01_Scanner {
public static void main(String[] args) {
//2. 创建键盘录入数据的对象
Scanner sc = new Scanner(System.in);
//3. 接收数据
System.out.println("请录入一个整数:");
int i = sc.nextInt();
//4. 输出数据
System.out.println("i:"+i);
}
}

3.2 Scanner练习

求和
键盘录入两个数据并求和,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.Scanner;
public class Test01Scanner {
public static void main(String[] args) {
// 创建对象
Scanner sc = new Scanner(System.in);
// 接收数据
System.out.println("请输入第一个数据:");
int a = sc.nextInt();
System.out.println("请输入第二个数据:");
int b = sc.nextInt();
// 对数据进行求和
int sum = a + b;
System.out.println("sum:" + sum);
}
}

取最值
键盘录入三个数据并获取最大值,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Scanner;
public class Test02Scanner {
public static void main(String[] args) {
// 创建对象
Scanner sc = new Scanner(System.in);
// 接收数据
System.out.println("请输入第一个数据:");
int a = sc.nextInt();
System.out.println("请输入第二个数据:");
int b = sc.nextInt();
System.out.println("请输入第三个数据:");
int c = sc.nextInt();
// 如何获取三个数据的最大值
int temp = (a > b ? a : b);
int max = (temp > c ? temp : c);
System.out.println("max:" + max);
}
}

第四章 匿名对象【了解】

概念
创建对象时,只有创建对象的语句,却没有把对象地址值赋值给某个变量。虽然是创建对象的简化写法,但是应用场景非常有限。
匿名对象 :没有变量名的对象。
格式:
new 类名(参数列表)
举例:
new Scanner(System.in);
应用场景

  1. 创建匿名对象直接调用方法,没有变量名。
    1
    new Scanner(System.in).nextInt();
  2. 一旦调用两次方法,就是创建了两个对象,造成浪费,请看如下代码。
    1
    2
    new Scanner(System.in).nextInt();
    new Scanner(System.in).nextInt();

    小贴士:一个匿名对象,只能使用一次。

  3. 匿名对象可以作为方法的参数和返回值
  • 作为参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Test {
    public static void main(String[] args) {
    // 普通方式
    Scanner sc = new Scanner(System.in);
    input(sc);
    //匿名对象作为方法接收的参数
    input(new Scanner(System.in));
    }
    public static void input(Scanner sc){
    System.out.println(sc);
    }
    }
  • 作为返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Test2 {
    public static void main(String[] args) {
    // 普通方式
    Scanner sc = getScanner();
    }
    public static Scanner getScanner(){
    //普通方式
    //Scanner sc = new Scanner(System.in);
    //return sc;
    //匿名对象作为方法返回值
    return new Scanner(System.in);
    }
    }

第四章 Random类

4.1 什么是Random类

此类的实例用于生成伪随机数。
例如,以下代码使用户能够得到一个随机数:

1
2
Random r = new Random();
int i = r.nextInt();

4.2 Random使用步骤

查看类
java.util.Random :该类需要 import导入使后使用。
查看构造方法
public Random() :创建一个新的随机数生成器。
查看成员方法
public int nextInt(int n) :返回一个伪随机数,范围在 0 (包括)和 指定值 n (不包括)之间的int 值。
使用Random类,完成生成3个10以内的随机整数的操作,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1. 导包
import java.util.Random;
public class Demo01_Random {
public static void main(String[] args) {
//2. 创建键盘录入数据的对象
Random r = new Random();
for(int i = 0; i < 3; i++){
//3. 随机生成一个数据
int number = r.nextInt(10);
//4. 输出数据
System.out.println("number:"+ number);
}
}
}

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

4.3 练习

获取随机数
获取1-n之间的随机数,包含n,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
// 导包
import java.util.Random;
public class Test01Random {
public static void main(String[] args) {
int n = 50;
// 创建对象
Random r = new Random();
// 获取随机数
int number = r.nextInt(n) + 1;
// 输出随机数
System.out.println("number:" + number);
}
}

猜数字小游戏
游戏开始时,会随机生成一个1-100之间的整数 number 。玩家猜测一个数字 guessNumber ,会与 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
// 导包
import java.util.Random;
public class Test02Random {
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类

5.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
39
40
41
42
43
44
45
46
47
48
public 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;
}

publicvoid setName(String name) {
this.name = name;
}

publicint getAge() {
return age;
}

publicvoid setAge(int age) {
this.age = age;
}
}

public class Test01StudentArray {
public static void main(String[] args) {
//创建学生数组
Student[] students = new Student[3];
//创建学生对象
Student s1 = new Student("曹操", 40);
Student s2 = new Student("刘备", 35);
Student s3 = new Student("孙权", 30);
//把学生对象作为元素赋值给学生数组
students[0] = s1;
students[1] = s2;
students[2] = s3;
//遍历学生数组
for (int x = 0; x < students.length; x++) {
Student s = students[x];
System.out.println(s.getName() + "‐‐‐" + s.getAge());
}
}
}

到目前为止,我们想存储对象数据,选择的容器,只有对象数组。而数组的长度是固定的,无法适应数据变化的需求。为了解决这个问题,Java提供了另一个容器 java.util.ArrayList 集合类,让我们可以更便捷的存储和操作对象数据

5.2 什么是ArrayList类

java.util.ArrayList 是大小可变的数组的实现,存储在内的数据称为元素。此类提供一些方法来操作内部存储的元素。 ArrayList 中可不断添加元素,其大小也自动增长。

5.3 ArrayList使用步骤

查看类
java.util.ArrayList <E> :该类需要 import导入使后使用。
<E> ,表示一种指定的数据类型,叫做泛型。 E ,取自Element(元素)的首字母。在出现 E 的地方,我们使用一种引用数据类型将其替换即可,表示我们将存储哪种引用类型的元素。代码如下:
ArrayList<String>,ArrayList<Student>
查看构造方法
public ArrayList() :构造一个内容为空的集合。
基本格式:

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

JDK 7后,右侧泛型的尖括号之内可以留空,但是<>仍然要写。简化格式:

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

查看成员方法
public boolean add(E e) : 将指定的元素添加到此集合的尾部。参数 E e ,在构造ArrayList对象时, <E> 指定了什么数据类型,那么 add(E e) 方法中,只能添加什么数据类型的对象。
使用ArrayList类,存储三个字符串元素,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test02StudentArrayList {
public static void main(String[] args) {
//创建学生数组
ArrayList<String> list = new ArrayList<>();
//创建学生对象
String s1 = "曹操";
String s2 = "刘备";
String s3 = "孙权";
//打印学生ArrayList集合
System.out.println(list);
//把学生对象作为元素添加到集合
list.add(s1);
list.add(s2);
list.add(s3);
//打印学生ArrayList集合
System.out.println(list);
}
}

5.4 常用方法和遍历

对于元素的操作,基本体现在——增、删、查。常用的方法有:

  • public boolean add(E e) :将指定的元素添加到此集合的尾部。
  • public E remove(int index) :移除此集合中指定位置上的元素。返回被删除的元素。
  • public E get(int index) :返回此集合中指定位置上的元素。返回获取的元素。
  • public int size() :返回此集合中的元素数。遍历集合时,可以控制索引范围,防止越界。

这些都是最基本的方法,操作非常简单,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Demo01ArrayListMethod {
public static void main(String[] args) {
//创建集合对象
ArrayList<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
//public E get(int index):返回指定索引处的元素
System.out.println("get:"+list.get(0));
System.out.println("get:"+list.get(1));
System.out.println("get:"+list.get(2));
//public int size():返回集合中的元素的个数
System.out.println("size:"+list.size());
//public E remove(int index):删除指定索引处的元素,返回被删除的元素
System.out.println("remove:"+list.remove(0));
//遍历输出
for(int i = 0; i < list.size(); i++){
System.out.println(list.get(i));
}
}
}

5.5 如何存储基本数据类型

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

基本类型 基本类型包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

我们发现,只有 IntegerCharacter 需要特殊记忆,其他基本类型只是首字母大写即可。那么存储基本类型数据,代码如下:

1
2
3
4
5
6
7
8
9
10
public class Demo02ArrayListMethod {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
System.out.println(list);
}
}

第六章 String类

6.1 String类概述

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

  1. 字符串不变:字符串的值在创建后不能被更改。

    1
    2
    3
    4
    String s1 = "abc";
    s1 += "d";
    System.out.println(s1); // "abcd"
    // 内存中有"abc","abcd"两个对象,s1从指向"abc",改变指向,指向了"abcd"。
  2. 因为String对象是不可变的,所以它们可以被共享。

    1
    2
    3
    String s1 = "abc";
    String s2 = "abc";
    // 内存中只有一个"abc"对象被创建,同时被s1和s2共享。
  3. "abc" 等效于 char[] data={ 'a' , 'b' , 'c' }

    1
    2
    3
    4
    5
    6
    例如:
    String str = "abc";
    相当于:
    char data[] = {'a', 'b', 'c'};
    String str = new String(data);
    // String底层是靠字符数组实现的。

6.2 使用步骤

  • 查看类
    • java.lang.String :此类不需要导入。
  • 查看构造方法
    • public String() :初始化新创建的 String对象,以使其表示空字符序列。
    • public String(char[] value) :通过当前参数中的字符数组来构造新的String。
    • public String(byte[] bytes) :通过使用平台的默认字符集解码当前参数中的字节数组来构造新的String。
      构造举例,代码如下:
      1
      2
      3
      4
      5
      6
      7
      8
      // 无参构造
      String str = new String();
      // 通过字符数组构造
      char chars[] = {'a', 'b', 'c'};
      String str2 = new String(chars);
      // 通过字节数组构造
      byte bytes[] = { 97, 98, 99 };
      String str3 = new String(bytes);

6.3 常用方法

判断功能的方法

  • public boolean equals (Object anObject) :将此字符串与指定对象进行比较。
  • public boolean equalsIgnoreCase (String anotherString) :将此字符串与指定对象进行比较,忽略大小写。
    方法演示,代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class String_Demo01 {
    public static void main(String[] args) {
    // 创建字符串对象
    String s1 = "hello";
    String s2 = "hello";
    String s3 = "HELLO";
    // boolean equals(Object obj):比较字符串的内容是否相同
    System.out.println(s1.equals(s2)); // true
    System.out.println(s1.equals(s3)); // false
    System.out.println("‐‐‐‐‐‐‐‐‐‐‐");
    //boolean equalsIgnoreCase(String str):比较字符串的内容是否相同,忽略大小写
    System.out.println(s1.equalsIgnoreCase(s2)); // true
    System.out.println(s1.equalsIgnoreCase(s3)); // true
    System.out.println("‐‐‐‐‐‐‐‐‐‐‐");
    }
    }

    Object 是” 对象”的意思,也是一种引用类型。作为参数类型,表示任意对象都可以传递到方法中。

获取功能的方法

  • public int length () :返回此字符串的长度。
  • public String concat (String str) :将指定的字符串连接到该字符串的末尾。
  • public char charAt (int index) :返回指定索引处的 char值。
  • public int indexOf (String str) :返回指定子字符串第一次出现在该字符串内的索引。
  • public String substring (int beginIndex) :返回一个子字符串,从beginIndex开始截取字符串到字符串结尾。
  • public String substring (int beginIndex, int endIndex) :返回一个子字符串,从beginIndex到endIndex截取字符串。含beginIndex,不含endIndex。

方法演示,代码如下:

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 String_Demo02 {
public static void main(String[] args) {
//创建字符串对象
String s = "helloworld";
// int length():获取字符串的长度,其实也就是字符个数
System.out.println(s.length());
System.out.println("‐‐‐‐‐‐‐‐");
// String concat (String str):将将指定的字符串连接到该字符串的末尾.
String s = "helloworld";
String s2 = s.concat("**hello itheima");
System.out.println(s2);// helloworld**hello itheima
// char charAt(int index):获取指定索引处的字符
System.out.println(s.charAt(0));
System.out.println(s.charAt(1));
System.out.println("‐‐‐‐‐‐‐‐");
// int indexOf(String str):获取str在字符串对象中第一次出现的索引,没有返回‐1
System.out.println(s.indexOf("l"));
System.out.println(s.indexOf("owo"));
System.out.println(s.indexOf("ak"));
System.out.println("‐‐‐‐‐‐‐‐");
// String substring(int start):从start开始截取字符串到字符串结尾
System.out.println(s.substring(0));
System.out.println(s.substring(5));
System.out.println("‐‐‐‐‐‐‐‐");
// String substring(int start,int end):从start到end截取字符串。含start,不含end。
System.out.println(s.substring(0, s.length()));
System.out.println(s.substring(3,8));
}
}

转换功能的方法

  • public char[] toCharArray ():将此字符串转换为新的字符数组。
  • public byte[] getBytes () :使用平台的默认字符集将该 String编码转换为新的字节数组。
  • public String replace (CharSequence target, CharSequence replacement) :将与target匹配的字符串使用replacement字符串替换。
    方法演示,代码如下:
    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 String_Demo03 {
    public static void main(String[] args) {
    //创建字符串对象
    String s = "abcde";
    // char[] toCharArray():把字符串转换为字符数组
    char[] chs = s.toCharArray();
    for(int x = 0; x < chs.length; x++) {
    System.out.println(chs[x]);
    }
    System.out.println("‐‐‐‐‐‐‐‐‐‐‐");
    // byte[] getBytes ():把字符串转换为字节数组
    byte[] bytes = s.getBytes();
    for(int x = 0; x < bytes.length; x++) {
    System.out.println(bytes[x]);
    }
    System.out.println("‐‐‐‐‐‐‐‐‐‐‐");
    // 替换字母it为大写IT
    String str = "itchina itworld";
    String replace = str.replace("it", "IT");
    System.out.println(replace); // ITchina ITworld
    System.out.println("‐‐‐‐‐‐‐‐‐‐‐");
    }
    }

    CharSequence 是一个接口,也是一种引用类型。作为参数类型,可以把String对象传递到方法中。

分割功能的方法

  • public String[] split(String regex) :将此字符串按照给定的regex(规则)拆分为字符串数组。
    方法演示,代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class String_Demo03 {
    public static void main(String[] args) {
    //创建字符串对象
    String s = "aa|bb|cc";
    String[] strArray = s.split("|"); // ["aa","bb","cc"]
    for(int x = 0; x < strArray.length; x++) {
    System.out.println(strArray[x]); // aa bb cc
    }
    }
    }

6.4 String类的练习

拼接字符串
定义一个方法,把数组{1,2,3}按照指定个格式拼接成一个字符串。格式参照如下:[word1#word2#word3]。

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 StringTest1 {
public static void main(String[] args) {
//定义一个int类型的数组
int[] arr = {1, 2, 3};
//调用方法
String s = arrayToString(arr);
//输出结果
System.out.println("s:" + s);
}

/*
* 写方法实现把数组中的元素按照指定的格式拼接成一个字符串
* 两个明确:
* 返回值类型:String
* 参数列表:int[] arr
*/
public static String arrayToString(int[] arr) {
// 创建字符串s
String s = new String("[");
// 遍历数组,并拼接字符串
for (int x = 0; x < arr.length; x++) {
if (x == arr.length ‐ 1) {
s = s.concat(arr[x] + "]");
} else {
s = s.concat(arr[x] + "#");
}
}
return s;
}
}

统计字符个数
键盘录入一个字符,统计字符串中大小写字母及数字字符个数

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 StringTest2 {
public static void main(String[] args) {
//键盘录入一个字符串数据
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串数据:");
String s = sc.nextLine();
//定义三个统计变量,初始化值都是0
int bigCount = 0;
int smallCount = 0;
int numberCount = 0;
//遍历字符串,得到每一个字符
for (int x = 0; x < s.length(); x++) {
char ch = s.charAt(x);
//拿字符进行判断
if (ch >= 'A' && ch <= 'Z') {
bigCount++;
} else if (ch >= 'a' && ch <= 'z') {
smallCount++;
} else if (ch >= '0' && ch <= '9') {
numberCount++;
} else {
System.out.println("该字符" + ch + "非法");
}
}
//输出结果
System.out.println("大写字符:" + bigCount + "个");
System.out.println("小写字符:" + smallCount + "个");
System.out.println("数字字符:" + numberCount + "个");
}
}

第七章 static关键字

7.1 概述

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

7.2 定义和使用格式

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

  • 类变量:使用 static关键字修饰的成员变量。
    定义格式:
    1
    static 数据类型 变量名;
    举例:
    1
    static int numberID;

比如说,学校开学了,学生报到。现在想为每一位新来报到的同学编学号(sid),从第一名同学开始,sid为1,以此类推。学号必须是唯一的,连续的,并且与班级的人数相符,这样以便知道,要分配给下一名新同学的学号是多少。这样我们就需要一个变量,与单独的每一个学生对象无关,而是与整个班级同学数量有关。
所以,我们可以这样定义一个静态变量numberOfStudent,代码如下:

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 Student {
private String name;
private int age;
// 学生的id
private int sid;
// 类变量,记录学生数量,分配学号
public static int numberOfStudent = 0;

public Student(String name, int age) {
this.name = name;
this.age = age;
// 通过 numberOfStudent 给学生分配学号
this.sid = ++numberOfStudent;
}

// 打印属性值
public void show() {
System.out.println("Student : name=" + name + ", age=" + age + ", sid=" + sid);
}
}
public class StuDemo {
public static void main(String[] args) {
Student s1 = new Student("张三", 23);
Student s2 = new Student("李四", 24);
Student s3 = new Student("王五", 25);
Student s4 = new Student("赵六", 26);
s1.show(); // Student : name=张三, age=23, sid=1
s2.show(); // Student : name=李四, age=24, sid=2
s3.show(); // Student : name=王五, age=25, sid=3
s4.show(); // Student : name=赵六, age=26, sid=4
}
}

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

  • 类方法:使用 static关键字修饰的成员方法,习惯称为静态方法。
    定义格式:

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

    举例:在Student类中定义静态方法

    1
    2
    3
    public static void showNum() {
    System.out.println("num:" + numberOfStudent);
    }
  • 静态方法调用的注意事项

    • 静态方法可以直接访问类变量和静态方法。
    • 静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类变量或静态方法。
    • 静态方法中,不能使用this关键字。

      小贴士:静态方法只能访问静态成员。

调用格式
被static修饰的成员可以并且建议通过类名直接访问。虽然也可以通过对象名访问静态成员,原因即多个对象均属于一个类,共享使用同一个静态成员,但是不建议,会出现警告信息。

格式:

1
2
3
4
// 访问类变量
类名.类变量名;
// 调用静态方法
类名.静态方法名(参数);

调用演示,代码如下:

1
2
3
4
5
6
7
8
public class StuDemo2 {
public static void main(String[] args) {
// 访问类变量
System.out.println(Student.numberOfStudent);
// 调用静态方法
Student.showNum();
}
}

7.3 静态原理图解

static 修饰的内容:

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

7.4 静态代码块

  • 静态代码块:定义在成员位置,使用static修饰的代码块{ }。
    • 位置:类中方法外。
    • 执行:随着类的加载而执行且执行一次,优先于main方法和构造方法的执行。
      格式:
      1
      2
      3
      4
      5
      public class ClassName{
      static {
      // 执行语句
      }
      }
      作用:给类变量进行初始化赋值。用法演示,代码如下:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public class Game {
      public static int number;
      public static ArrayList<String> list;
      static {
      // 给类变量赋值
      number = 2;
      list = new ArrayList<String>();
      // 添加元素到集合中
      list.add("张三");
      list.add("李四");
      }
      }

      小贴士:
      static 关键字,可以修饰变量、方法和代码块。在使用的过程中,其主要目的还是想在不创建对象的情况下,去调用方法。下面将介绍两个工具类,来体现static 方法的便利。

第八章 Arrays类

8.1 概述

java.util.Arrays 此类包含用来操作数组的各种方法,比如排序和搜索等。其所有方法均为静态方法,调用起来非常简单。

8.2 操作数组的方法

public static String toString(int[] a) :返回指定数组内容的字符串表示形式。

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
// 定义int 数组
int[] arr = {2,34,35,4,657,8,69,9};
// 打印数组,输出地址值
System.out.println(arr); // [[email protected]
// 数组内容转为字符串
String s = Arrays.toString(arr);
// 打印字符串,输出内容
System.out.println(s); // [2, 34, 35, 4, 657, 8, 69, 9]
}

public static void sort(int[] a) :对指定的 int 型数组按数字升序进行排序。

1
2
3
4
5
6
7
8
public static void main(String[] args) {
// 定义int 数组
int[] arr = {24, 7, 5, 48, 4, 46, 35, 11, 6, 2};
System.out.println("排序前:"+ Arrays.toString(arr)); // 排序前:[24, 7, 5, 48, 4, 46, 35, 11, 6,2]
// 升序排序
Arrays.sort(arr);
System.out.println("排序后:"+ Arrays.toString(arr));// 排序后:[2, 4, 5, 6, 7, 11, 24, 35, 46,48]
}

8.3 练习

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ArraysTest {
public static void main(String[] args) {
// 定义随机的字符串
String line = "ysKUreaytWTRHsgFdSAoidq";
// 转换为字符数组
char[] chars = line.toCharArray();
// 升序排序
Arrays.sort(chars);
// 反向遍历打印
for (int i = chars.length‐1; i >= 0 ; i‐‐) {
System.out.print(chars[i]+" "); // y y t s s r q o i g e d d a W U T S R K H F A
}
}
}

第九章 Math类

9.1 概述

java.lang.Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。类似这样的工具类,其所有方法均为静态方法,并且不会创建对象,调用起来非常简单。

9.2 基本运算的方法

  • public static double abs(double a) :返回 double 值的绝对值。
    1
    2
    double d1 = Math.abs(‐5); //d1的值为5
    double d2 = Math.abs(5); //d2的值为5
  • public static double ceil(double a) :返回大于等于参数的最小的整数
    1
    2
    3
    double d1 = Math.ceil(3.3); //d1的值为 4.0
    double d2 = Math.ceil(‐3.3); //d2的值为 ‐3.0
    double d3 = Math.ceil(5.1); //d3的值为 6.0
  • public static double floor(double a) :返回小于等于参数最大的整数。
    1
    2
    3
    double d1 = Math.floor(3.3); //d1的值为3.0
    double d2 = Math.floor(‐3.3); //d2的值为‐4.0
    double d3 = Math.floor(5.1); //d3的值为 5.0
  • public static long round(double a) :返回最接近参数的 long。(相当于四舍五入方法)
    1
    2
    long d1 = Math.round(5.5); //d1的值为6.0
    long d2 = Math.round(5.4); //d2的值为5.0

9.3 练习

请使用 Math 相关的API,计算在 -10.85.9 之间,绝对值大于 6 或者小于 2.1 的整数有多少个?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MathTest {
public static void main(String[] args) {
// 定义最小值
double min = ‐10.8;
// 定义最大值
double max = 5.9;
// 定义变量计数
int count = 0;
// 范围内循环
for (double i = Math.ceil(min); i <= max; i++) {
// 获取绝对值并判断
if (Math.abs(i) > 6 || Math.abs(i) < 2.1) {
// 计数
count++;
}
}
System.out.println("个数为: " + count + " 个");
}
}

第十章 继承

10.1 概述

由来
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。如图所示:

其中,多个类可以称为子类,单独那一个类称为父类超类(superclass)或者基类
继承描述的是事物之间的所属关系,这种关系是: is-a 的关系。例如,图中兔子属于食草动物,食草动物属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。

定义

  • 继承:就是子类继承父类的属性行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
  • 好处*
  1. 提高代码的复用性
  2. 类与类之间产生了关系,是多态的前提

10.2 继承的格式

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

1
2
3
4
5
6
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
31
32
33
34
35
36
/*
* 定义员工类Employee,做为父类
*/
class Employee {
String name; // 定义name属性
// 定义员工的工作方法
public void work() {
System.out.println("尽心尽力地工作");
}
}
/*
* 定义讲师类Teacher 继承 员工类Employee
*/
class Teacher extends Employee {
// 定义一个打印name的方法
public void printName() {
System.out.println("name=" + name);
}
}


/*
* 定义测试类
*/
public class ExtendDemo01 {
public static void main(String[] args) {
// 创建一个讲师类对象
Teacher t = new Teacher();
// 为该员工类的name属性进行赋值
t.name = "小明";
// 调用该员工的printName()方法
t.printName(); // name = 小明
// 调用Teacher类继承来的work()方法
t.work(); // 尽心尽力地工作
}
}

10.3 继承后的特点——成员变量

当类之间产生了关系后,其中各类中的成员变量,又产生了哪些影响呢?
成员变量不重名
如果子类父类中出现不重名的成员变量,这时的访问是没有影响的。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Fu {
// Fu中的成员变量。
int num = 5;
}

class Zi extends Fu {
// Zi中的成员变量
int num2 = 6;
// Zi中的成员方法
public void show() {
// 访问父类中的num,
System.out.println("Fu num="+num); // 继承而来,所以直接访问。
// 访问子类中的num2
System.out.println("Zi num2="+num2);
}
}

class ExtendDemo02 {
public static void main(String[] args) {
// 创建子类对象
Zi z = new Zi();
// 调用子类中的show方法
z.show();
}
}
演示结果:
Fu num = 5
Zi num2 = 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
class Fu {
// Fu中的成员变量。
int num = 5;
}
class Zi extends Fu {
// Zi中的成员变量
int num = 6;
public void show() {
// 访问父类中的num
System.out.println("Fu num=" + num);
// 访问子类中的num
System.out.println("Zi num=" + num);
}
}
class ExtendsDemo03 {
public static void main(String[] args) {
// 创建子类对象
Zi z = new Zi();
// 调用子类中的show方法
z.show();
}
}
演示结果:
Fu num = 6
Zi num = 6

子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用 super 关键字,修饰父类成员变量,类似于之前学过的 this

使用格式:

super.父类成员变量名
子类方法需要修改,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Zi extends Fu {
// Zi中的成员变量
int num = 6;
public void show() {
//访问父类中的num
System.out.println("Fu num=" + super.num);
//访问子类中的num
System.out.println("Zi num=" + this.num);
}
}
演示结果:
Fu num = 5
Zi num = 6

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

10.4 继承后的特点——成员方法

当类之间产生了关系,其中各类中的成员方法,又产生了哪些影响呢?
成员方法不重名
如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Fu{
public void show(){
System.out.println("Fu类中的show方法执行");
}
}
class Zi extends Fu{
public void show2(){
System.out.println("Zi类中的show2方法执行");
}
}
public class ExtendsDemo04{
public static void main(String[] args) {
Zi z = new Zi();
//子类中没有show方法,但是可以找到父类方法去执行
z.show();
z.show2();
}
}

成员方法重名——重写(Override)
如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)。

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

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Fu {
public void show() {
System.out.println("Fu show");
}
}
class Zi extends Fu {
//子类重写了父类的show方法
public void show() {
System.out.println("Zi show");
}
}
public class ExtendsDemo05{
public static void main(String[] args) {
Zi z = new Zi();
// 子类中有show方法,只执行重写后的show方法
z.show(); // 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
class Phone {
public void sendMessage(){
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
public void showNum(){
System.out.println("来电显示号码");
}
}


//智能手机类
class NewPhone extends Phone {
//重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能
public void showNum(){
//调用父类已经存在的功能使用super
super.showNum();
//增加自己特有显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
}
}


public class ExtendsDemo06 {
public static void main(String[] args) {
// 创建子类对象
NewPhone np = new NewPhone();
// 调用父类继承而来的方法
np.call();
// 调用子类重写的方法
np.showNum();
}
}

小贴士:这里重写时,用到super.父类成员方法,表示调用父类的成员方法

注意事项

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

10.5 继承后的特点——构造方法

当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
首先我们要回忆两个事情,构造方法的定义格式和作用。

  1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
  2. 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个 super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Fu {
private int n;
Fu(){
System.out.println("Fu()");
}
}
class Zi extends Fu {
Zi(){
// super(),调用父类构造方法
super();
System.out.println("Zi()");
}
}
public class ExtendsDemo07{
public static void main (String args[]){
Zi zi = new Zi();
}
}
输出结果:
Fu()
Zi()

10.6 super和this

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

super和this的含义

  • super :代表父类的存储空间标识(可以理解为父亲的引用)。
  • this :代表当前对象的引用(谁调用就代表谁)。

super和this的用法

  1. 访问成员

    1
    2
    3
    4
    this.成员变量 ‐‐ 本类的
    super.成员变量 ‐‐ 父类的
    this.成员方法名() ‐‐ 本类的
    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
    class Animal {
    public void eat() {
    System.out.println("animal : eat");
    }
    }
    class Cat extends Animal {
    public void eat() {
    System.out.println("cat : eat");
    }
    public void eatTest() {
    this.eat(); // this 调用本类的方法
    super.eat(); // super 调用父类的方法
    }
    }
    public class ExtendsDemo08 {
    public static void main(String[] args) {
    Animal a = new Animal();
    a.eat();
    Cat c = new Cat();
    c.eatTest();
    }
    }
    输出结果为:
    animal : eat
    cat : eat
    animal : eat
  2. 访问构造方法

    1
    2
    this(...) ‐‐ 本类的构造方法
    super(...) ‐‐ 父类的构造方法

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

10.7 继承的特点

  1. Java只支持单继承,不支持多继承。

    1
    2
    3
    //一个类只能有一个父类,不可以有多个父类。
    class C extends A{} //ok
    class C extends AB... //error
  2. Java支持多层继承(继承体系)。

    1
    2
    3
    class A{}
    class B extends A{}
    class C extends B{}

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

  3. 子类和父类是一种相对的概念。

第十一章 抽象类

11.1 概述

由来
父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类

定义

  • 抽象方法 : 没有方法体的方法。
  • 抽象类:包含抽象方法的类。

11.2 abstract使用格式

抽象方法
使用 abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。
定义格式:

1
修饰符 abstract 返回值类型 方法名 (参数列表);

代码举例:

1
public abstract void run()

抽象类
如果一个类包含抽象方法,那么该类必须是抽象类。
定义格式:

1
2
abstract class 类名字 {
}

代码举例:

1
2
3
public abstract class Animal {
public abstract void run()
}

抽象的使用
继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。
代码举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Cat extends Animal {
public void run (){
System.out.println("小猫在墙头走~~~");
}
}
public class CatTest {
public static void main(String[] args) {
// 创建子类对象
Cat c = new Cat();
// 调用run方法
c.run();
}
}
输出结果:
小猫在墙头走~~~

此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法

11.3 注意事项

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

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

    理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

    理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

  3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

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

  4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。

    理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义

第十二章 接口

12.1 概述

接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法(JDK 9)。
接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。

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

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

12.2 定义格式

1
2
3
4
5
6
public interface 接口名称 {
// 抽象方法
// 默认方法
// 静态方法
// 私有方法
}

含有抽象方法
抽象方法:使用 abstract 关键字修饰,可以省略,没有方法体。该方法供子类实现使用。
代码如下:

1
2
3
public interface InterFaceName {
public abstract void method();
}

含有默认方法和静态方法
默认方法:使用 default 修饰,不可省略,供子类调用或者子类重写。
静态方法:使用 static 修饰,供接口直接调用。
代码如下:

1
2
3
4
5
6
7
8
public interface InterFaceName {
public default void method() {
// 执行语句
}
public static void method2() {
// 执行语句
}
}

含有私有方法和私有静态方法
私有方法:使用 private 修饰,供接口中的默认方法或者静态方法调用。
代码如下:

1
2
3
4
5
public interface InterFaceName {
private void method() {
// 执行语句
}
}

12.3 基本的实现

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

  1. 必须重写接口中所有抽象方法。
  2. 继承了接口的默认方法,即可以直接调用,也可以重写。
    实现格式:
    1
    2
    3
    4
    class 类名 implements 接口名 {
    // 重写接口中抽象方法【必须】
    // 重写接口中默认方法【可选】
    }

抽象方法的使用
必须全部实现,代码如下:
定义接口:

1
2
3
4
5
public interface LiveAble {
// 定义抽象方法
public abstract void eat();
public abstract void sleep();
}

定义实现类:

1
2
3
4
5
6
7
8
9
10
11
public class Animal implements LiveAble {
@Override
public void eat() {
System.out.println("吃东西");
}
@Override
public void sleep() {
System.out.println("晚上睡");
}
}
`

定义测试类:

1
2
3
4
5
6
7
8
9
10
11
12
public class InterfaceDemo {
public static void main(String[] args) {
// 创建子类对象
Animal a = new Animal();
// 调用实现后的方法
a.eat();
a.sleep();
}
}
输出结果:
吃东西
晚上睡

默认方法的使用
可以继承,可以重写,二选一,但是只能通过实现类的对象来调用。

  1. 继承默认方法,代码如下:
    定义接口:
    1
    2
    3
    4
    5
    public interface LiveAble {
    public default void fly(){
    System.out.println("天上飞");
    }
    }
    定义实现类:
    1
    2
    3
    public class Animal implements LiveAble {
    // 继承,什么都不用写,直接调用
    }
    定义测试类:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class InterfaceDemo {
    public static void main(String[] args) {
    // 创建子类对象
    Animal a = new Animal();
    // 调用默认方法
    a.fly();
    }
    }
    输出结果:
    天上飞
  2. 重写默认方法,代码如下:
    定义接口:
    1
    2
    3
    4
    5
    public interface LiveAble {
    public default void fly(){
    System.out.println("天上飞");
    }
    }
    定义实现类:
    1
    2
    3
    4
    5
    6
    public class Animal implements LiveAble {
    @Override
    public void fly() {
    System.out.println("自由自在的飞");
    }
    }
    定义测试类:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class InterfaceDemo {
    public static void main(String[] args) {
    // 创建子类对象
    Animal a = new Animal();
    // 调用重写方法
    a.fly();
    }
    }
    输出结果:
    自由自在的飞

静态方法的使用
静态与.class 文件相关,只能使用接口名调用,不可以通过实现类的类名或者实现类的对象调用,代码如下:
定义接口:

1
2
3
4
5
public interface LiveAble {
public static void run(){
System.out.println("跑起来~~~");
}
}

定义实现类:

1
2
3
public class Animal implements LiveAble {
// 无法重写静态方法
}

定义测试类:

1
2
3
4
5
6
7
8
public class InterfaceDemo {
public static void main(String[] args) {
// Animal.run(); // 【错误】无法继承方法,也无法调用
LiveAble.run(); //
}
}
输出结果:
跑起来~~~

私有方法的使用

  • 私有方法:只有默认方法可以调用。
  • 私有静态方法:默认方法和静态方法可以调用。
    如果一个接口中有多个默认方法,并且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法去调用。从设计的角度讲,私有的方法是对默认方法和静态方法的辅助。
    定义接口:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public interface LiveAble {
    default void func(){
    func1();
    func2();
    }
    private void func1(){
    System.out.println("跑起来~~~");
    }
    private void func2(){
    System.out.println("跑起来~~~");
    }
    }

12.4 接口的多实现

之前学过,在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。
实现格式:

1
2
3
4
class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3... {
// 重写接口中抽象方法【必须】
// 重写接口中默认方法【不重名时可选】
}

[ ]: 表示可选操作。

抽象方法
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。代码如下:
定义多个接口:

1
2
3
4
5
6
7
8
interface A {
public abstract void showA();
public abstract void show();
}
interface B {
public abstract void showB();
public abstract void show();
}

定义实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class C implements A,B{
@Override
public void showA() {
System.out.println("showA");
}
@Override
public void showB() {
System.out.println("showB");
}
@Override
public void show() {
System.out.println("show");
}
}

默认方法
接口中,有多个默认方法时,实现类都可继承使用。如果默认方法有重名的,必须重写一次。代码如下:
定义多个接口:

1
2
3
4
5
6
7
8
interface A {
public default void methodA(){}
public default void method(){}
}
interface B {
public default void methodB(){}
public default void method(){}
}

定义实现类:

1
2
3
4
5
6
public class C implements A,B{
@Override
public void method() {
System.out.println("method");
}
}

静态方法
接口中,存在同名的静态方法并不会冲突,原因是只能通过各自接口名访问静态方法。

优先级的问题
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的默认方法重名,子类就近选择执行父类的成员方法。代码如下:
定义接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
interface A {
public default void methodA(){
System.out.println("AAAAAAAAAAAA");
}
}

定义父类:
```java
class D {
public void methodA(){
System.out.println("DDDDDDDDDDDD");
}
}

定义子类:

1
2
3
class C extends D implements A {
// 未重写methodA方法
}

定义测试类:

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
C c = new C();
c.methodA();
}
}
输出结果:
DDDDDDDDDDDD

12.5 接口的多继承【了解】

一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承使用 extends 关键字,子接口继承父接口的方法。如果父接口中的默认方法有重名的,那么子接口需要重写一次。代码如下:
定义父接口:

1
2
3
4
5
6
7
8
9
10
interface A {
public default void method(){
System.out.println("AAAAAAAAAAAAAAAAAAA");
}
}
interface B {
public default void method(){
System.out.println("BBBBBBBBBBBBBBBBBBB");
}
}

定义子接口:

1
2
3
4
5
interface D extends A,B{
@Override
public default void method() {
System.out.println("DDDDDDDDDDDDDD");
}

小贴士:
子接口重写默认方法时,default关键字可以保留。
子类重写默认方法时,default关键字不可以保留。

12.6 其他成员特点

  • 接口中,无法定义成员变量,但是可以定义常量,其值不可以改变,默认使用public static final修饰。
  • 接口中,没有构造方法,不能创建对象。
  • 接口中,没有静态代码块。

第十三章 多态

13.1 概述

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

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

13.2 多态的体现

多态体现的格式:

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

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

1
2
Fu f = new Zi();
f.method();

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后方法。

代码如下:
定义父类:

1
2
3
public abstract class Animal {
public abstract void eat();
}

定义子类:

1
2
3
4
5
6
7
8
9
10
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}

定义测试类:

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) {
// 多态形式,创建对象
Animal a1 = new Cat();
// 调用的是 Cat 的 eat
a1.eat();
// 多态形式,创建对象
Animal a2 = new Dog();
// 调用的是 Dog 的 eat
a2.eat();
}
}

13.3 多态的好处

实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。代码如下:
定义父类:

1
2
3
public abstract class Animal {
public abstract void eat();
}

定义子类:

1
2
3
4
5
6
7
8
9
10
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
}
class Dog extends Animal {
public void eat() {
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
public class Test {
public static void main(String[] args) {
// 多态形式,创建对象
Cat c = new Cat();
Dog d = new Dog();
// 调用showCatEat
showCatEat(c);
// 调用showDogEat
showDogEat(d);
/*
以上两个方法, 均可以被showAnimalEat(Animal a)方法所替代
而执行效果一致
*/
showAnimalEat(c);
showAnimalEat(d);
}
public static void showCatEat (Cat c){
c.eat();
}
public static void showDogEat (Dog d){
d.eat();
}
public static void showAnimalEat (Animal a){
a.eat();
}
}

由于多态特性的支持,showAnimalEat方法的Animal类型,是Cat和Dog的父类类型,父类类型接收子类对象,当然可以把Cat对象和Dog对象,传递给方法。
当eat方法执行时,多态规定,执行的是子类重写的方法,那么效果自然与showCatEat、showDogEat方法一致,所以showAnimalEat完全可以替代以上两方法。
不仅仅是替代,在扩展性方面,无论之后再多的子类出现,我们都不需要编写showXxxEat方法了,直接使用showAnimalEat都可以完成。
所以,多态的好处,体现在,可以使程序编写的更简单,并有良好的扩展。

13.4 引用类型转换

多态的转型分为向上转型与向下转型两种:
向上转型

  • 向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。
    当父类引用指向一个子类对象时,便是向上转型。
    使用格式:
    1
    2
    父类类型 变量名 = new 子类类型();
    如:Animal a = new Cat();

向下转型

  • 向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。
    一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
    使用格式:
    1
    2
    子类类型 变量名 = (子类类型) 父类变量名;
    如:Cat c =(Cat) a;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void watchHouse() {
System.out.println("看家");
}
}

定义测试类:

1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
}
}

转型的异常
转型的过程中,一不小心就会遇到这样的问题,请看如下代码:

1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}

这段代码可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:

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

所以,转换前,我们最好先做一个判断,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
}

第十四章 final关键字

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

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

14.1 使用方式

修饰类
格式如下:

1
2
final class 类名 {
}

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

修饰方法
格式如下:

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

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

修饰变量

  1. 局部变量——基本类型

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class FinalDemo1 {
public static void main(String[] args) {
// 声明变量,使用final修饰
final int a;
// 第一次赋值
a = 10;
// 第二次赋值
a = 20; // 报错,不可重新赋值
// 声明变量,直接赋值,使用final修饰
final int b = 10;
// 第二次赋值
b = 20; // 报错,不可重新赋值
}
}
思考,如下两种写法,哪种可以通过编译?
写法1
```java
final int c = 0;
for (int i = 0; i < 10; i++) {
c = i;
System.out.println(c);
}

写法2:

1
2
3
4
for (int i = 0; i < 10; i++) {
final int c = i;
System.out.println(c);
}

根据 final 的定义,写法1报错!写法2,为什么通过编译呢?因为每次循环,都是一次新的变量c。这也是大家需要注意的地方。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class FinalDemo2 {
    public static void main(String[] args) {
    // 创建 User 对象
    final User u = new User();
    // 创建 另一个 User对象
    u = new User(); // 报错,指向了新的对象,地址值改变。
    // 调用setName方法
    u.setName("张三"); // 可以修改
    }
    }
  2. 成员变量
    成员变量涉及到初始化的问题,初始化方式有两种,只能二选一:

  • 显示初始化;

    1
    2
    3
    4
    public class User {
    final String USERNAME = "张三";
    private int age;
    }
  • 构造方法初始化

    1
    2
    3
    4
    5
    6
    7
    8
    public class User {
    final String USERNAME ;
    private int age;
    public User(String username, int age) {
    this.USERNAME = username;
    this.age = age;
    }
    }

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

第十五章 权限修饰符

15.1 概述

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

  • public:公共的。
  • protected:受保护的
  • default:默认的
  • private:私有的

15.2 不同权限的访问能力

public protected default(空的) private
同一类中
同一包中(子类与无关类)
不同包的子类
不同包中的无关类

可见,public具有最大权限。private则是最小权限。
编写代码时,如果没有特殊的考虑,建议这样使用权限:

  • 成员变量使用 private ,隐藏细节。
  • 构造方法使用 public ,方便创建对象。
  • 成员方法使用 public ,方便调用方法。

小贴士:不加权限修饰符,其访问能力与default修饰符相同

第十六章 内部类

16.1 概述

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

  • 成员内部类 :定义在类中方法外的类。
    定义格式:
    1
    2
    3
    4
    class 外部类 {
    class 内部类{
    }
    }
    在描述事物时,若一个事物内部还包含其他事物,就可以使用内部类这种结构。比如,汽车类 Car 中包含发动机类 Engine ,这时, Engine 就可以使用内部类来描述,定义在成员位置。
    代码举例
    1
    2
    3
    4
    class Car { //外部类
    class Engine { //内部类
    }
    }

访问特点

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

创建内部类对象格式:

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

访问演示,代码如下:
定义类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Person {
private boolean live = true;
class Heart {
public void jump() {
// 直接访问外部类成员
if (live) {
System.out.println("心脏在跳动");
} else {
System.out.println("心脏不跳了");
}
}
}
public boolean isLive() {
return live;
}
public void setLive(boolean live) {
this.live = live;
}
}

定义测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class InnerDemo {
public static void main(String[] args) {
// 创建外部类对象
Person p = new Person();
// 创建内部类对象
Heart heart = p.new Heart();
// 调用内部类方法
heart.jump();
// 调用外部类方法
p.setLive(false);
// 调用内部类方法
heart.jump();
}
}
输出结果:
心脏在跳动
心脏不跳了

内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号 。
比如,Person$Heart.class

16.2 匿名内部类【重点】

匿名内部类 :是内部类的简化写法。它的本质是一个 带具体实现的 父类或者父接口的 匿名的 子类对象
开发中,最常用到的内部类就是匿名内部类了。以接口举例,当你使用一个接口时,似乎得做如下几步操作,

  1. 定义子类
  2. 重写接口中的方法
  3. 创建子类对象
  4. 调用重写后的方法

我们的目的,最终只是为了调用方法,那么能不能简化一下,把以上四步合成一步呢?匿名内部类就是做这样的快捷方式。

前提
匿名内部类必须继承一个父类或者实现一个父接口。

格式

1
2
3
4
5
6
7
new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};

使用方式*
以接口为例,匿名内部类的使用,代码如下:
定义接口:

1
2
3
public abstract class FlyAble{
public abstract void fly();
}

创建匿名内部类,并调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class InnerDemo {
public static void main(String[] args) {
/*
1.等号右边:是匿名内部类,定义并创建该接口的子类对象
2.等号左边:是多态赋值,接口类型引用指向子类对象
*/
FlyAble f = new FlyAble(){
public void fly() {
System.out.println("我飞了~~~");
}
};
//调用 fly方法,执行重写后的方法
f.fly();
}
}

通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class InnerDemo2 {
public static void main(String[] args) {
/*
1.等号右边:定义并创建该接口的子类对象
2.等号左边:是多态,接口类型引用指向子类对象
*/
FlyAble f = new FlyAble(){
public void fly() {
System.out.println("我飞了~~~");
}
};
// 将f传递给showFly方法中
showFly(f);
}
public static void showFly(FlyAble f) {
f.fly();
}
}

以上两步,也可以简化为一步,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class InnerDemo3 {
public static void main(String[] args) {
/*
创建匿名内部类,直接传递给showFly(FlyAble f)
*/
showFly( new FlyAble(){
public void fly() {
System.out.println("我飞了~~~");
}
});
}
public static void showFly(FlyAble f) {
f.fly();
}
}

第十七章 引用类型用法总结

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

17.1 class作为成员变量

在定义一个类Role(游戏角色)时,代码如下:

1
2
3
4
5
class Role {
int id; // 角色id
int blood; // 生命值
String name; // 角色名称
}

使用 int 类型表示 角色id和生命值,使用 String 类型表示姓名。此时, String 本身就是引用类型,由于使用的方式类似常量,所以往往忽略了它是引用类型的存在。如果我们继续丰富这个类的定义,给 Role 增加武器,穿戴装备等属性,我们将如何编写呢?

定义武器类,将增加攻击能力:

1
2
3
4
class Weapon {
String name; // 武器名称
int hurt; // 伤害值
}

定义穿戴盔甲类,将增加防御能力,也就是提升生命值:

1
2
3
4
class Armour {
String name;// 装备名称
int protect;// 防御值
}

再次定义角色类:

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 Role {
int id;
int blood;
String name;
// 添加武器属性
Weapon wp;
// 添加盔甲属性
Armour ar;
// 提供get/set方法
public Weapon getWp() {
return wp;
}
public void setWeapon(Weapon wp) {
this.wp = wp;
}
public Armour getArmour() {
return ar;
}
public void setArmour(Armour ar) {
this.ar = ar;
}
// 攻击方法
public void attack(){
System.out.println("使用"+ wp.getName() +", 造成"+wp.getHurt()+"点伤害");
}
// 穿戴盔甲
public void wear(){
// 增加防御,就是增加blood值
this.blood += ar.getProtect();
System.out.println("穿上"+ar.getName()+", 生命值增加"+ar.getProtect());
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public static void main(String[] args) {
// 创建Weapon 对象
Weapon wp = new Weapon("屠龙刀" , 999999);
// 创建Armour 对象
Armour ar = new Armour("麒麟甲",10000);
// 创建Role 对象
Role r = new Role();
// 设置武器属性
r.setWeapon(wp);
// 设置盔甲属性
r.setArmour(ar);
// 攻击
r.attack();
// 穿戴盔甲
r.wear();
}
}
输出结果:
使用屠龙刀,造成999999点伤害
穿上麒麟甲 ,生命值增加10000

类作为成员变量时,对它进行赋值的操作,实际上,是赋给它该类的一个对象。

17.2 interface作为成员变量

接口是对方法的封装,对应游戏当中,可以看作是扩展游戏角色的技能。所以,如果想扩展更强大技能,我们在Role 中,可以增加接口作为成员变量,来设置不同的技能。
定义接口:

1
2
3
4
// 法术攻击
public interface FaShuSkill {
public abstract void faShuAttack();
}

定义角色类:

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 Role {
FaShuSkill fs;
public void setFaShuSkill(FaShuSkill fs) {
this.fs = fs;
}
// 法术攻击
public void faShuSkillAttack(){
System.out.print("发动法术攻击:");
fs.faShuAttack();
System.out.println("攻击完毕");
}
}
``

定义测试类:
```java
public class Test {
public static void main(String[] args) {
// 创建游戏角色
Role role = new Role();
// 设置角色法术技能
role.setFaShuSkill(new FaShuSkill() {
@Override
public void faShuAttack() {
System.out.println("纵横天下");
}
});
// 发动法术攻击
role.faShuSkillAttack();
// 更换技能
role.setFaShuSkill(new FaShuSkill() {
@Override
public void faShuAttack() {
System.out.println("逆转乾坤");
}
});
// 发动法术攻击
role.faShuSkillAttack();
}
}
输出结果:
发动法术攻击:纵横天下
攻击完毕
发动法术攻击:逆转乾坤
攻击完毕

我们使用一个接口,作为成员变量,以便随时更换技能,这样的设计更为灵活,增强了程序的扩展性。
接口作为成员变量时,对它进行赋值的操作,实际上,是赋给它该接口的一个子类对象。

17.3 interface作为方法参数和返回值类型

当接口作为方法的参数时,需要传递什么呢?当接口作为方法的返回值类型时,需要返回什么呢?对,其实都是它的子类对象。 ArrayList 类我们并不陌生,查看API我们发现,实际上,它是 java.util.List 接口的实现类。所以,当我们看见 List 接口作为参数或者返回值类型时,当然可以将ArrayList 的对象进行传递或返回。
请观察如下方法:获取某集合中所有的偶数。
定义方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static List<Integer> getEvenNum(List<Integer> list) {
// 创建保存偶数的集合
ArrayList<Integer> evenList = new ArrayList<>();
// 遍历集合list,判断元素为偶数,就添加到evenList中
for (int i = 0; i < list.size(); i++) {
Integer integer = list.get(i);
if (integer % 2 == 0) {
evenList.add(integer);
}
}
/*
返回偶数集合
因为getEvenNum方法的返回值类型是List,而ArrayList是List的子类,
所以evenList可以返回
*/
return evenList;
}

调用方法:

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) {
// 创建ArrayList集合,并添加数字
ArrayList<Integer> srcList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
srcList.add(i);
}
/*
获取偶数集合
因为getEvenNum方法的参数是List,而ArrayList是List的子类,
所以srcList可以传递
*/
List list = getEvenNum(srcList);
System.out.println(list);
}
}

接口作为参数时,传递它的子类对象。
接口作为返回值类型时,返回它的子类对象。

文章作者: LiuYu
文章链接: https://www.liuyu.pw/posts/87330513.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 LiuYuBLOG

评论