学为人师、行为世范
一、常用 API
1. API
API (Application Programming Interface) :应用程序编程接口
Java 11 中文 API 参考文档: https://www.apiref.com/java11-zh/index.html
2. Scanner补充
方法 | 描述 |
---|---|
String next() | 从此扫描仪查找并返回下一个完整令牌 |
int nextInt() | 将输入的下一个标记扫描为 int |
String nextLine() | 使此扫描器前进超过当前行并返回跳过的输入 |
public static void main(String[] args) {
// nextLint、nextInt、next
Scanner s = new Scanner(System.in);
// nextInt
System.out.println("输入整数:");
int num = s.nextInt();
System.out.println(num); // => 10
// nextLine
System.out.println("输入字符串:");
String s1 = s.nextLine();
System.out.println(s1);
// 输入整数后,回车 会导致进程结束
// nextLint 遇到 空格/回车 会结束进程
// next
System.out.println("输入字符串(会过滤空格后的数据)");
String s2 = s.next();
System.out.println(s2);
}
3. String 类
- String 类在
java.lang
包下,所以使用的时候不需要导包 - String 类代表字符串,Java 程序中的所有字符串文字(例如“abc”)都被实现为此类的实例也就是说,Java 程序中所有的双引号字符串,都是 String 类的对象
- 字符串不可变,它们的值在创建后不能被更改(引用的字符串可以被覆盖)
⑴. String 类常用 API
方法 | 描述 |
---|---|
public trim() | 返回一个字符串,删除了所有前导和尾随空格 |
public boolean equals(Object anObject) | 比较字符串的内容,严格区分大小写 |
public boolean equalsIgnoreCase(String anotherString) | 比较字符串的内容,忽略大小写 |
public int length() | 返回此字符串的长度 |
public char charAt(int index) | 返回指定索引处的 char 值 |
public char[] toCharArray() | 将字符串拆分为字符数组后返回 |
public String substring(int beginIndex, int endIndex) | 根据开始和结束索引进行截取,得到新的字符串(包含头,不包含尾) |
public String substring(int beginIndex) | 从传入的索引处截取,截取到末尾,得到新的字符串 |
public String replace(CharSequence target, CharSequence replacement) | 使用新值,将字符串中的旧值替换,得到新的字符串 |
public String[] split(String regex) | 根据传入的规则切割字符串,得到字符串数组 |
⑵. trim & equals & equalsIgnoreCase
示例:
public static void main(String[] args) {
String s1 = new String("helloWorld");
// 数组长度
System.out.println(s1.length()); // 10
String s2 = "helloWorld";
String s3 = "HelloWorld";
// 数组比较(区分大小写)
System.out.println(s1.equals(s2)); // true
System.out.println(s1.equals(s3)); // false
// 数组比较(不区分大小写)
System.out.println(s1.equalsIgnoreCase(s2)); // true
System.out.println(s1.equalsIgnoreCase(s3)); // true
// 去掉数组首尾空格
String s4 = " hello world";
System.out.println(s4.trim());
// => "hello world"
}
⑶. charAt & toCharArray
示例:
public static void main(String[] args) {
// 输入字符串,统计大写、小写、数字字符的个数
int bigCount = 0;
int smallCount = 0;
int numCount = 0;
Scanner s = new Scanner(System.in);
System.out.println("请输入需要统计的字符串:");
String s1 = s.nextLine();
char[] chars = s1.toCharArray();
for (int i = 0; i < chars.length; i++) {
int c = chars[i];
if(chars[i] >= 'A' && chars[i] <= 'Z') bigCount++;
if(chars[i] >= 'a' && chars[i] <= 'z') smallCount++;
if(chars[i] >= '0' && chars[i] <= '9') numCount++;
}
System.out.println("大写字符有:"+ bigCount + "个");
System.out.println("小写字符有:"+ smallCount + "个");
System.out.println("数字字符有:"+ numCount + "个");
}
⑷. substring
示例:
public static void main(String[] args) {
// 接收一个手机号,屏蔽部分号码:180****8888
Scanner s = new Scanner(System.in);
System.out.println("输入手机号:");
String s1 = s.nextLine();
String s1Pre = s1.substring(0, 3); // => 180
String s1Next = s1.substring(7); // => 8888
System.out.println(s1Pre + "****" + s1Next);
}
⑸. replace
示例:
public static void main(String[] args) {
// 替换敏感词汇
Scanner s = new Scanner(System.in);
System.out.println("请输入(输入敏感词汇 'TMD' 会被屏蔽):");
String s1 = s.nextLine();
String result = s1.replace("TMD", "***");
System.out.println(result);
}
⑹. split
示例:
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
System.out.println("请输入姓名和年龄,中间用 ',' 隔开:");
String s1 = s.nextLine();
String[] res = s1.split(",");
System.out.println("姓名:" + res[0]+ ",年龄:" + res[1]);
}
4. StringBuilder
StringBuilder 是一个可变的字符串类,我们可以把它看成是一个容器,这里的可变指的是StringBuilder 对象中的内容是可变的
⑴. StringBuilder 类的意义
先通过一个示例测试两者性能:
public class test01 {
public static void main(String[] args) {
forEach1();
forEach2();
}
public static void forEach1() {
// 获取1970 年 1 月 1 日 0 时 0 分 0 秒 到当前时间的毫秒数
long start = System.currentTimeMillis();
String s1 = "";
for (int i = 0; i < 50000; i++) {
s1 += i;
}
long end = System.currentTimeMillis();
System.out.println(end - start); // => 1533
}
public static void forEach2() {
long start = System.currentTimeMillis();
StringBuilder s2 = new StringBuilder();
for (int i = 0; i < 50000; i++) {
s2.append(i);
}
long end = System.currentTimeMillis();
System.out.println(end - start); // => 4
}
// 极大地提高了字符串的操作效率
}
原因:
- 字符串拼接时,底层会自动调用 StringBuilder 方法,将其进行拼接,拼接后再转换为 String 类型
- 一次拼接,堆内存中就会添加两个对象
- 而 StringBuilder 中,直接进行拼接,不会产生多个 StringBuilder 对象,以及频繁的 String 类型转换
⑵. StringBuilder 类和 String 类 的区别
- String类: 内容是不可变的
- StringBuilder类: 内容是可变的
⑶. 常用方法
类型 | 方法 | 描述 |
---|---|---|
构造方法 | public StringBuilder() | 创建一个空白可变字符串对象,不含有任何内容 |
构造方法 | public StringBuilder(String str) | 根据字符串的内容,来创建可变字符串对象 |
成员方法 | public StringBuilder append(任意类型) | 添加数据,并返回对象本身 |
成员方法 | public StringBuilder reverse() | 返回相反的字符序列 |
成员方法 | public String toString() | StringBuilder 转换为 String |
成员方法 | public StringBuilder(String s): | String 转换为 StringBuilder |
成员方法 | int length() | 返回长度(字符数) |
⑷. 方法示例
①. append
public static void main(String[] args) {
// append 会将传入的数据转换成字符串
StringBuilder sb1 = new StringBuilder();
sb1.append("绿色");
sb1.append(123);
sb1.append(true);
System.out.println(sb1); // => 绿色123true
// append 返回的是对象本身
StringBuilder sb2 = new StringBuilder("sb2");
StringBuilder sb2_1 = sb2.append("绿色");
StringBuilder sb2_2 = sb2.append("红色");
System.out.println(sb2_1); // => sb2绿色红色
System.out.println(sb2_2); // => sb2绿色红色
// 链式调用:方法返回的是对象类型,就可以继续向下调用
StringBuilder sb3 = new StringBuilder("sb3");
sb3.append("绿色").append(123).append(true);
System.out.println(sb3); // => sb3绿色123true
}
②. reverse、toString、length
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("helloWorld");
System.out.println(sb); // => helloWorld
// reverse 翻转字符串
StringBuilder res1 = sb.reverse();
System.out.println(res1); // => dlroWolleh
// length 字符串长度
int res2 = sb.length();
System.out.println(res2); // => 10
// toString 转换成 String
String res3 = sb.toString();
System.out.println(res3); // => dlroWolleh
}
⑸. 实例
需求: 输入一个字符串,判断是否是对称,并提示
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
System.out.println("输入:");
StringBuilder sb = new StringBuilder();
String s1 = s.nextLine();
StringBuilder s2 = new StringBuilder(s1);
s2.reverse();
String s3 = s2.toString();
if(s1.equals(s3)) {
System.out.println("是对称字符串");
} else {
System.out.println("不是对称字符串");
}
}
二、集合(ArrayList)基础
集合和数组的区别 :
- 共同点: 都是存储数据的容器
- 不同点: 数组的容量是固定的,集合的容量是可变的
1. 常用方法:
方法 | 描述 |
---|---|
public ArrayList() | 创建一个空的集合对象 |
public boolean add(E e) | 将指定的元素追加到此集合的末尾 |
public void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
public boolean remove(Object o) | 删除指定的元素,返回删除是否成功 |
public E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
public E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
public E get(int index) | 返回指定索引处的元素 |
public int size() | 返回集合中的元素的个数 |
2. 方法示例:
public static void main(String[] args) {
// ArrayList() 构造函数: 构造一个初始容量为 10 的列表
// ArrayList arr = new ArrayList();
// <> 枚举:对集合容器储存的数据类型进行限定
ArrayList<String> arr = new ArrayList<>();
// boolean add(E a): 将指定元素添加至此列表尾部
arr.add("aaa");
arr.add("bbb");
arr.add("ccc");
arr.add("ddd");
// void add(int index, E element):将指定元素插入此列表指定索引位置
arr.add(1, "sss");
System.out.println(arr);
// => [aaa, sss, bbb, ccc, ddd]
// public boolean remove(Object o):删除指定的元素,返回删除是否成功
System.out.println(arr.remove("aaa")); // => true
System.out.println(arr); // => [sss, bbb, ccc, ddd]
//public E remove(int index):删除指定索引处的元素,返回被删除的元素
System.out.println(arr.remove(1)); // => bbb
System.out.println(arr); // => [sss, ccc, ddd]
// public E set(int index,E element):修改指定索引处的元素,返回被修改的元素
System.out.println(arr.set(1, "xxx")); // => ccc
System.out.println(arr); // => [sss, xxx, ddd]
// public E get(int index):返回指定索引处的元素
System.out.println(arr.get(1)); // => xxx
// public int size():返回集合中的元素的个数
System.out.println(arr.size()); // => 3
}
3. 方法示例:
需求: 创建一个存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合
// domain/Student
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;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
调用方法:
public static void main(String[] args) {
ArrayList<Student> arr = new ArrayList<>();
Student stu1 = new Student("zoe", 18);
Student stu2 = new Student("ami", 34);
Student stu3 = new Student("lucy", 43);
arr.add(stu1);
arr.add(stu2);
arr.add(stu3);
for (int i = 0; i < arr.size(); i++) {
Student temp = arr.get(i);
System.out.println("姓名:" + temp.getName() + ",年龄:" + temp.getAge());
// => 姓名:zoe,年龄:18 ...
}
}
4. 方法示例:
需求: 删除集合中 “test” 的项
// 对象 Student 同上例
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("test");
list.add("bbb");
list.add("ddd");
list.add("test");
list.add("test");
list.remove("test");
System.out.println(list); // => [bbb, ddd, test, test]
// 只能删除第一项 "test"
for (int i = 0; i < list.size(); i++) {
String temp = list.get(i);
// 变量 和 常量 之间的比较,尽量使用常量的方法,这样不会出现空指针异常的问题
if("test".equals(temp)) {
list.remove(i);
// 删除之后,集合中的数据会整体向前移动,需要修改索引
i--;
}
}
System.out.println(list); // => [bbb, ddd]
}
5. 学生管理系统:
需求: 创建一个学生信息的管理系统,并能够对数据 增删改查。
// Student 对象
public class Student {
private String sid; // 学号
private String name; // 姓名
private int age; // 年龄
private String birthday; // 生日
public Student() {}
public Student(String sid, String name, int age, String birthday) {
this.sid = sid;
this.name = name;
this.age = age;
this.birthday = birthday;
}
public String getSid() {
return sid;
}
public void setSid(String sid) {
this.sid = sid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
}
调用方法:
// 调用方法 TestIndex
public class TestIndex {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 创建容器对象
ArrayList<Student> list = new ArrayList<>();
lo:
while (true) {
System.out.println("--------欢迎来到学生管理系统--------");
System.out.println("1 添加学生");
System.out.println("2 删除学生");
System.out.println("3 修改学生");
System.out.println("4 查看学生");
System.out.println("5 退出");
System.out.println("请输入您的选择:");
String choose = sc.next();
switch (choose) {
case "1":
addStudent(list);
break;
case "2":
removeStudent(list);
break;
case "3":
editStudent(list);
break;
case "4":
queryStudent(list);
break;
case "5":
System.out.println("感谢您的使用");
break lo;
default:
System.out.println("您的输入有误");
break;
}
}
}
private static void editStudent(ArrayList<Student> list) { // 修改
Scanner sc = new Scanner(System.in);
System.out.println("请输入学号:");
String deleteSid = sc.next();
int index = getIndex(list, deleteSid);
if (index != -1) {
System.out.println("请输入新的学生学号:");
String sid = sc.next();
System.out.println("请输入新的学生姓名:");
String name = sc.next();
System.out.println("请输入新的学生年龄:");
int age = sc.nextInt();
System.out.println("请输入新的学生生日:");
String bir = sc.next();
Student stu = new Student(sid, name, age, bir);
list.set(index, stu);
System.out.println("修改成功!");
}
}
private static void removeStudent(ArrayList<Student> list) { // 删除
Scanner sc = new Scanner(System.in);
System.out.println("请输入学号:");
String deleteSid = sc.next();
int index = getIndex(list, deleteSid);
if (index != -1) {
list.remove(index);
System.out.println("删除成功!");
}
}
public static void queryStudent(ArrayList<Student> list) { // 查看
if (list.size() == 0) {
System.out.println("无信息, 请添加后重新查询");
return;
}
System.out.println("学号\t\t姓名\t年龄\t生日");
for (int i = 0; i < list.size(); i++) {
Student temp = list.get(i);
System.out.println(temp.getSid() + "\t" + temp.getName() + "\t" + temp.getAge() + "\t" + temp.getBirthday());
}
}
public static void addStudent(ArrayList<Student> list) { // 添加
Scanner sc = new Scanner(System.in);
String sid;
while (true) {
System.out.println("请输入学号:");
sid = sc.next();
int index = getIndex(list, sid);
if(index == -1) {
break;
} else {
System.out.println("该学号已存在");
}
}
System.out.println("请输入姓名:");
String name = sc.next();
System.out.println("请输入年龄:");
int age = sc.nextInt();
System.out.println("请输入生日:");
String bir = sc.next();
Student stu = new Student(sid, name, age, bir);
list.add(stu);
System.out.println("添加成功!");
}
public static int getIndex(ArrayList<Student> list, String sid) { // 判断学号是否存在
int index = -1;
for (int i = 0; i < list.size(); i++) {
Student temp = list.get(i);
String id = temp.getSid();
if (id.equals(sid)) {
index = i;
}
}
if(index == -1) System.out.println("未查到");
return index;
}
}
三、项目:教务信息管理系统
1. 分类思想
分类思想:分工协作,专人干专事
类名 | 描述 |
---|---|
Student 类 | 标准学生类,封装键盘录入的学生信息(id , name , age , birthday) |
StudentDao 类 | Dao : (Data Access Object 缩写) 用于访问存储数据的数组或集合 |
StudentService 类 | 用来进行业务逻辑的处理(例如:判断录入的id是否存在) |
StudentController 类 | 和用户打交道(接收用户需求,采集用户信息,打印数据到控制台) |
2. 分包思想
如果将所有的类文件都放在同一个包下,不利于管理和后期维护 所以,对于不同功能的类文件,可以放在不同的包下进行管理
- 同一个包下的访问: 不需要导包,直接使用即可
- 不同包下的访问: import 导包后访问 通过全类名(包名 + 类名)访问
- 注意: import 、package 、class 三个关键字的摆放位置存在顺序关系
- package 必须是程序的第一条可执行的代码
- import 需要写在 package 下面
- class 需要在 import 下面
3. static 关键字
static 关键字是静态的意思,是Java中的一个修饰符,可以修饰成员方法,成员变量
- 被static修饰的成员变量,一般叫做静态变量
- 被static修饰的成员方法,一般叫做静态方法
static 修饰的特点:
- 被类的所有对象共享:
- 是我们判断是否使用静态关键字的条件
- 随着类的加载而加载,优先于对象存在
- 对象需要类被加载后,才能创建
- 可以通过类名调用:
- 也可以通过对象名调用
- 推荐使用类名调用
注意事项:
- 静态方法只能访问静态的成员
- 非静态方法可以访问静态的成员,也可以访问非静态的成员
- 静态方法中是没有this关键字
4. 案例:信息管理系统
⑴. 需求说明
- 添加学生: 键盘录入学生信息(id,name,age,birthday) 使用数组存储学生信息,要求学生的id不能重复
- 删除学生: 键盘录入要删除学生的id值,将该学生从数组中移除,如果录入的id在数组中不存在,需要重新录入
- 修改学生: 键盘录入要修改学生的id值和修改后的学生信息 将数组中该学生的信息修改,如果录入的id在数组中不存在,需要重新录入
- 查询学生: 将数组中存储的所有学生的信息输出到控制台。
⑵. 预览
⑶. Gitee
Gitee 仓库地址: https://gitee.com/yuan0_0/info-manage
⑷. 目录结构
包 | 存储的类 | 作用 |
---|---|---|
infoManager.domain | Student.java、Teacher.java | 封装学生信息、封装老师信息 |
infoManager.dao | StudentDao.java、TeacherDao.java | 访问存储数据的数组,进行赠删改查(库管) |
infoManager.service | StudentService.java、TeacherService.java | 业务的逻辑处理(业务员) |
infoManager.controller | StudentController.java、TeacherController.java | 和用户打交道(客服接待) |
infoManager.entry | InfoManagerEntry.java | 程序的入口类,提供一个main方法 |
四、继承
1. 继承
让类与类之间产生关系(子父类关系),子类可以直接使用父类中非私有的成员
⑴. 概述
继承的格式:
- 格式: public class 子类名
extends
父类名 { } - 范例: public class Zi
extends
Fu { } - Fu: 是父类,也被称为基类、超类
- Zi: 是子类,也被称为派生类
继承的好处和弊端:
- 好处:
- 提高了代码的复用性
- 提高了代码的维护性
- 让类与类之间产生了关系,是多态的前提
- 弊端:
- 继承是侵入性的: 降低了代码的灵活性 继承关系,导致子类必须拥有父类非私有属性和方法,让子类自由的世界中多了些约束
- 增强了代码的耦合性: 代码与代码之间存在关联都可以将其称之为"耦合"
⑵. 特点
Java只支持单继承,不支持多继承,但支持多层继承。 (多层继承:子类 A 继承父类 B ,父类B 可以 继承父类 C )
成员变量的访问特点:
- 在子类方法中访问一个变量:
- 子类局部范围找、子类成员范围找、父类成员范围找
- 注意:
- 如果子父类中,出现了重名的成员变量,通过就近原则,会优先使用子类的
- 如果一定要使用父类的,可以通过super关键字,进行区分。
关键字 | 访问成员变量 | 访问成员方法 | 访问构造方法 |
---|---|---|---|
this | this.成员变量、访问本类成员变量 | this.成员方法(…)、访问本类成员方法 | this(…)、访问本类构造方法 |
super | super.成员变量、访问父类成员变量 | super.成员方法(…)、访问父类成员方法 | super(…)、访问父类构造方法 |
- super 关键字的用法和 this 关键字的用法相似
- this:代表本类对象的引用
- super:代表父类存储空间的标识(可以理解为父类对象引用)
⑶. 示例
父类:
public class Father {
int a = 10;
}
子类:
public class Son extends Father{
int a = 20; // 创建重名变量
public void method() {
System.out.println(a); // => 20
int a = 30;
System.out.println(a); // => 30 局部变量(就近原则)
System.out.println(this.a); // => 20(this 指向)
System.out.println(super.a); // => 10(super 方法获取父类)
}
}
入口文件:
public class test {
public static void main(String[] args) {
Son son = new Son();
son.method();
}
}
⑷. 方法重写
①. 概述
在继承体系中,子类出现了和父类中一模一样的方法声明
应用场景: 当子类需要父类的功能,而功能主体子类有自己特有内容,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容
注意事项:
- 父类中私有方法不能被重写
- 父类静态方法,子类必须通过静态方法进行重写,父类非静态方法,子类也必须通过非静态方法进行重写
- 子类重写父类方法时,访问权限必须大于等于父类
- 注意: 静态方法不能被重写!如果子类中,也存在一个方法声明一模一样的方法 可以理解为,子类将父类中同名的方法,隐藏了起来,并非是方法重写!
②. 权限修饰符
关键字 | 访问成员变量 | 访问成员方法 | 访问构造方法 | 访问构造方法 |
---|---|---|---|---|
private | ✅ | - | - | - |
默认 | ✅ | ✅ | - | - |
protected | ✅ | ✅ | ✅ | - |
public | ✅ | ✅ | ✅ | ✅ |
③. 示例
父类:
public class Father {
public static void method1() {
System.out.println("father_method1");
}
public void method2() {
System.out.println("father_method2");
}
}
子类:
public class Son extends Father {
public static void method1() {
System.out.println("son_method1");
}
@Override // 检查当前方法是否是一个正确的重写方法
public void method2() {
super.method2();
}
}
入口文件:
public class test {
public static void main(String[] args) {
Son son = new Son();
son.method1(); // => son_method1
son.method2(); // => father_method2
}
}
⑸. 构造方法
①. 构造方法的访问特点
- 子类中所有的构造方法默认都会访问父类中无参的构造方法
- 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。
- 子类初始化之前,一定要先完成父类初始化
- 构造方法的第一条语句默认都是:
super()
- 注意: 如果编写的类,没有手动指定父类,系统也会自动继承Object (Java继承体系中的最顶层父类)
①. 空参构造方法
如果父类中没有空参构造方法,只有带参构造方法,会出现什么现象呢?
- 子类通过 super,手动调用父类的带参的构造方法(推荐)
- 子类通过 this 去调用本类的其他构造方法,本类其他构造方法再通过 super 去手动调用父类的带参的构造方法
- 注意:
this(…)
和super(…)
必须放在构造方法的第一行有效语句,并且二者不能共存。
③. 示例
父类:
public class Person {
private String name;
private int age;
public Person() {
System.out.println("Person_无参构造方法....");
}
public Person(String name, int age) {
System.out.println("Person_带参构造方法!!!!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
子类:
public class Student {
public Student() {
System.out.println("Student_无参构造方法....");
}
public Student(int score) {
System.out.println("Student_带参构造方法!!!!");
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
private int score;
}
入口文件:
public class test {
public static void main(String[] args) {
Person p1 = new Person();
// => Person_无参构造方法....
Person p2 = new Person("zoe",18);
// => Person_带参构造方法!!!!
Student s1 = new Student();
// => Student_无参构造方法....
Student s2 = new Student(100);
// => Student_带参构造方法!!!!
}
}
2. 抽象类
⑴. 概述
- 抽象方法: 将共性的行为(方法)抽取到父类之后,发现该方法的实现逻辑,无法在父类中给出具体明确,该方法就可以定义为抽象方法。
- 抽象类: 如果一个类中存在抽象方法,那么该类就必须声明为抽象类
定义格式:
# public abstract 返回值类型 方法名(参数列表);
public abstract class 类名{}
注意事项:
- 抽象类不能实例化
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 可以有构造方法
- 抽象类的子类
- 要么重写抽象类中的所有抽象方法
- 要么是抽象类
⑵. 示例
需求:
- 定义猫类(Cat)和狗类(Dog):
- 猫类成员方法:eat (猫吃鱼) drink(喝水…)
- 狗类成员方法:eat (狗吃肉) drink(喝水…)
抽象类:
// 抽象类
public abstract class Animal {
public void drink() {
System.out.println("喝水");
}
// 抽象方法
public abstract void eat();
}
猫:
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
狗:
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
入口文件:
public class test {
public static void main(String[] args) {
System.out.println("猫");
Cat cat = new Cat();
cat.drink(); // => 喝水
cat.eat(); // => 猫吃鱼
System.out.println("狗");
Dog dog = new Dog();
dog.drink(); // => 喝水
dog.eat(); // => 狗吃肉
}
}
⑶. 模板设计模式
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
模板设计模式: 把抽象类整体就可以看做成一个模板,模板中不能决定的东西定义成抽象方法 让使用模板的类(继承抽象类的类)去重写抽象方法实现需求
小结: 模板设计模式的优势,模板已经定义了通用结构,使用者只需要关心自己需要实现的功能即可
示例:
需求: 定义写作类模板,模板的标题、尾注固定,正文根据不同文体自己定制
模板:
public abstract class Template {
public void write() {
System.out.println("文字标题");
body();
System.out.println("文章尾注");
}
public abstract void body();
}
散文:
public class Novel extends Template {
@Override
public void body() {
System.out.println("小说正文");
}
}
入口文件:
public class test {
public static void main(String[] args) {
Novel n = new Novel();
n.write();
// => 文字标题
// => 小说正文
// => 文章尾注
}
}
⑷. final 关键字
final 关键字是最终的意思,可以修饰(方法,变量,类)
final 修饰的特点:
- 修饰方法: 表明该方法是最终方法,不能被重写
- 修饰变量: 表明该变量是常量,不能再次被赋值
- 修饰类: 表明该类是最终类,不能被继承
变量是基本类型: final 修饰指的是基本类型的数据值不能发生改变
变量是引用类型: final 修饰指的是引用类型的地址值不能发生改变,但是地址里面的内容是可以发生改变的
3. 代码块
在Java中,使用 { } 括起来的代码被称为代码块
⑴. 分类
- 局部代码块:
- 位置: 方法中定义
- 作用: 限定变量的生命周期,及早释放,提高内存利用率
- 构造代码块:
- 位置: 类中方法外定义
- 特点: 每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行
- 作用: 将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性
- 静态代码块:
- 位置: 类中方法外定义
- 特点: 需要通过static关键字修饰,随着类的加载而加载,并且只执行一次
- 作用: 在类加载的时候做一些数据初始化的操作
⑵. 示例
局部代码块:
public class Test {
/*
局部代码块
位置:方法中定义
作用:限定变量的生命周期,及早释放,提高内存利用率
*/
public static void main(String[] args) {
{
int a = 10;
System.out.println(a);
}
// System.out.println(a);
}
}
构造代码块:
public class Test {
/*
构造代码块:
位置:类中方法外定义
特点:每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行
作用:将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性
*/
public static void main(String[] args) {
Student stu1 = new Student();
Student stu2 = new Student(10);
}
}
class Student {
{
System.out.println("好好学习");
}
public Student(){
System.out.println("空参数构造方法");
}
public Student(int a){
System.out.println("带参数构造方法...........");
}
}
静态代码块:
public class Test {
/*
静态代码块:
位置:类中方法外定义
特点:需要通过static关键字修饰,随着类的加载而加载,并且只执行一次
作用:在类加载的时候做一些数据初始化的操作
*/
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person(10);
}
}
class Person {
static {
System.out.println("我是静态代码块, 我执行了");
}
public Person(){
System.out.println("我是Person类的空参数构造方法");
}
public Person(int a){
System.out.println("我是Person类的带...........参数构造方法");
}
}
五、接口
当一个类中的所有方法都是抽象方法的时候,我们就可以将其定义为接口 接口也是一种引用数据类型,它比抽象类还要抽象
1. 接口的定义和特点
定义:
# 接口用关键字interface来定义
public interface 接口名 {}
特点:
- 接口不能实例化
- 接口和类之间是实现关系,通过
implements
关键字表示:- 格式: public class 类名 implements 接口名 {}
- 接口的子类(实现类)
- 要么重写接口中的所有抽象方法
- 要么是抽象类
注意:
- 接口和类的实现关系,可以单实现,也可以多实现
- 格式: public class 类名 implements 接口名1 , 接口名2 {}
2. 接口中成员的特点
- 成员变量:
- 只能是常量
- 默认修饰符: public static final
- 构造方法: 没有
- 成员方法:
- 只能是抽象方法
- 默认修饰符: public abstract
- 关于接口中的方法,JDK8和JDK9中有一些新特性
3. JDK8版中接口成员的特点
JDK8版本后:
- 允许在接口中定义非抽象方法,但是需要使用关键字 default 修饰,这些方法就是默认方法
- 作用: 解决接口升级的问题 接口中允许定义static静态方法
定义:
# 接口中静态方法的定义格式
public static 返回值类型 方法名(参数列表) { }
public static void show() { }
注意事项:
- 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
- public可以省略,static不能省略
私有方法:
# private 返回值类型 方法名(参数列表) { }
private void show() { }
# private static 返回值类型 方法名(参数列表) { }
private static void method() { }
4. 接口的使用思路
- 如果发现一个类中所有的方法都是抽象方法,那么就可以将该类,改进为一个接口
- 涉及到了接口大面积更新方法,而不想去修改每一个实现类,就可以将更新的方法,定义为带有方法体的默认方法
- 希望默认方法调用的更加简洁,可以考虑设计为
static
静态方法。(需要去掉default关键字) - 默认方法中出现了重复的代码,可以考虑抽取出一个私有方法。(需要去掉default关键字)
5. 类和接口的关系
- 类和类的关系: 继承关系,只能单继承,但是可以多层继承
- 类和接口的关系: 实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
- 接口和接口的关系: 继承关系,可以单继承,也可以多继承
6. 示例
接口:
public interface interA {
// 默认方法
public default void show() {
System.out.println("interA的 show 方法");
}
// 静态方法
public static void method() {
System.out.println("interA的 method 方法");
}
}
测试文件:
public class testInterface {
public static void main(String[] args) {
InterImpl interImpl = new InterImpl();
// interImpl.show(); // => interA的 show 方法
interImpl.show(); // => 重写后的 show 方法
// 静态方法的使用
interA.method();
}
}
// 生效类
//class InterImpl implements interA {
//}
// 重新默认方法
class InterImpl implements interA {
@Override
public void show() {
System.out.println("重新后的 show 方法");
}
}
六、多态
1. 概述
同一个对象,在不同时刻表现出来的不同形态
多态的前提和体现:
- 有继承/实现关系
- 有方法重写
- 有父类引用指向子类对象
示例:
public class test01 {
public static void main(String[] args) {
// 多态的前提3:要有父类引用, 指向子类对象
Animal a = new Cat();
a.eat(); // => 猫吃鱼
}
}
class Animal {
public void eat() {
System.out.println("动物吃饭");
}
}
// 多态的前提1:要有(继承 \ 实现)关系
class Cat extends Animal {
// 多态的前提2:要有方法重写
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
2. 成员访问特点
- 构造方法: 同继承一样,子类会通过 super 访问父类构造方法
- 成员变量: 编译看左边(父类),执行看左边(父类)
- 成员方法: 编译看左边(父类),执行看右边(子类)
- 为什么成员变量和成员方法的访问不一样呢?
- 因为成员方法有重写,而成员变量没有
示例:
public class test02 {
public static void main(String[] args) {
Father f = new Son();
// 成员变量:编译看左边(父类),执行看左边(父类)
System.out.println(f.val);
// => 父类
// 成员方法:编译看左边(父类),执行看右边(子类)
f.show();
// => 子类的 show 方法
}
}
class Father {
String val = "父类";
public void show() {
System.out.println("父类的 show 方法");
}
}
class Son extends Father {
String val = "子类";
@Override
public void show() {
System.out.println("子类的 show 方法");
}
}
3. 好处和弊端
- 多态的好处: 提高了程序的扩展性
- 具体体现: 定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的任意子类对象
- 多态的弊端: 不能使用子类的特有功能
4. 转型
- 向上转型:
- 从子到父
- 父类引用指向子类对象
- 向下转型:
- 从父到子
- 父类引用转为子类对象
- 存在的风险:
- 如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现
ClassCastException
- 如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现
- 关键字
instanceof
:- 格式: 变量名 instanceof 类型
- 判断关键字左边的变量,是否是右边的类型,返回boolean类型结果
示例:
public class test {
public static void main(String[] args) {
animalEat(new Dog());
// => 狗吃肉
// => 看家
animalEat(new Cat());
// => 猫吃鱼
}
public static void animalEat(Animal a) {
a.eat();
// instanceof
// dog.watchHome(); // ClassCastException 类型转换报错
if(a instanceof Dog) {
Dog dog = (Dog) a;
dog.watchHome();
}
}
}
abstract class Animal {
public abstract void eat();
}
class Dog extends Animal {
public void eat() {
System.out.println("狗吃肉");
}
public void watchHome() {
System.out.println("看家");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
七、内部类&Lambda
1. 内部类
⑴. 概述
定义:
// 在一个A类的内部定义一个B类, B类就被称为内部类
public class Outer {
public class Inner { }
}
内部类的访问特点:
- 内部类可以直接访问外部类的成员,包括私有
- 外部类要访问内部类的成员,必须创建对象
⑵. 成员内部类
成员内部类是在类中定义的
修饰符:
- private:
- 私有成员内部类访问:在自己所在的外部类中创建对象访问。
- static:
- 静态成员内部类访问格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
- 静态成员内部类中的静态方法:外部类名.内部类名.方法名();
示例:
public class test {
public static void main(String[] args) {
// 直接访问私有内部类,是访问不到的
// Outer.Inter inter = new Outer() . new Inter();
Outer outer = new Outer();
outer.method();
// => Inter...show...
// 静态成员内部类的引用方法
Outer.Inter2 inter2 = new Outer.Inter2();
inter2.show2();
// => Inter2...show2...
// 静态类中的静态方法调用
Outer.Inter2.show3();
}
}
class Outer {
// 私有内部类
private class Inter {
public void show() {
System.out.println("Inter...show...");
}
}
// 静态成员内部类
static class Inter2 {
public void show2() {
System.out.println("Inter2...show2...");
}
static public void show3() {
System.out.println("Inter2...show3...");
}
}
public void method() {
Inter inter = new Inter();
inter.show();
}
}
⑶. 局部内部类
局部内部类是在方法中定义的
- 外界是无法直接使用
- 需要在方法内部创建对象并使用
- 该类可以直接访问外部类的成员,也可以访问(创建该局部类)方法内的局部变量
示例:
public class test {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
// => Inter...show...
}
}
class Outer {
public void method() {
// 局部内部类(方法中)
class Inter {
public void show() {
System.out.println("Inter...show...");
}
}
Inter inter = new Inter();
inter.show();
}
}
⑷. 匿名内部类
①. 概述
- 匿名内部类本质上是一个特殊的局部内部类(定义在方法内部)
- 前提: 需要存在一个接口或类
示例:
public class test {
public static void main(String[] args) {
// 创建实现类对象
InterImpl interImpl = new InterImpl();
// 调用重写后的方法
interImpl.show();
// => InterImpl...show...
}
}
// 创建接口
interface Inter {
void show();
}
// 实现接口
class InterImpl implements Inter {
// 重新方法
@Override
public void show() {
System.out.println("InterImpl...show...");
}
}
②. 格式定义
public class test {
public static void main(String[] args) {
// 匿名内部类
// 将继承/实现、方法重写、创建对象 合为一步
new Inter() {
@Override
public void show() {
System.out.println("匿名...show...");
}
}.show();
// => 匿名...show...
}
}
// 创建接口
interface Inter {
void show();
}
③. 接口在中存在多个方法
public class test {
public static void main(String[] args) {
// 接口中存在多个方法
Inter i = new Inter () {
@Override
public void show1() {
System.out.println("show1");
}
@Override
public void show2() {
System.out.println("show2");
}
};
i.show1();
// => show1
i.show2();
// => show2
}
}
interface Inter {
void show1();
void show2();
}
④. 匿名函数在开发中的使用
当方法的形式参数是接口或者抽象类时,可以将匿名内部类作为实际参数进行传递
public class test07 {
public static void main(String[] args) {
goSwimming(new Swimming() {
@Override
public void swim() {
System.out.println("我们去游泳吧~");
}
});
}
// 使用接口的方法
public static void goSwimming(Swimming swimming) {
swimming.swim();
}
}
// 接口
interface Swimming {
void swim();
}
2. Lambda
⑴. 对比匿名内部类
public class test01 {
public static void main(String[] args) {
// 匿名内部类
goSwimming(new Swimming() {
@Override
public void swim() {
System.out.println("我们去游泳吧~");
}
});
// => 我们去游泳吧~
// lambda
goSwimming(() -> {
System.out.println("我们去游泳吧~");
});
// => 我们去游泳吧~
}
// 使用接口的方法
public static void goSwimming(Swimming swimming) {
swimming.swim();
}
}
// 接口
interface Swimming {
void swim();
}
⑵. 代码分析
匿名内部类中重写 swim() 方法
的代码分析:
- 方法形式参数为空,说明调用方法时不需要传递参数
- 方法返回值类型为void,说明方法执行没有结果返回
- 方法体中的内容,是我们具体要做的事情
Lambda表达式的代码分析:
- (): 里面没有内容,可以看成是方法形式参数为空
- ->: 用箭头指向后面要做的事情
- { }: 包含一段代码,我们称之为代码块,可以看成是方法体中的内容
Lambda表达式的格式:
- 格式: ( 形式参数 ) -> { 代码块 }
- 形式参数: 如果有多个参数,参数之间用逗号隔开
- ->: 固定写法,代表指向动作
- 代码块: 是我们具体要做的事情,也就是以前我们写的方法体内容
⑶. 练习 1
- 编写一个接口(ShowHandler)
- 在该接口中存在一个抽象方法(show),该方法是无参数无返回值
- 在测试类(ShowHandlerDemo)中存在一个方法(useShowHandler),方法的的参数是ShowHandler类型的,在方法内部调用了ShowHandler的show方法
public class test02 {
public static void main(String[] args) {
// 匿名内部类
useShowHandler(new ShowHandler() {
@Override
public void show() {
System.out.println("这是匿名类");
}
});
// Lambda
useShowHandler(() -> {
System.out.println("这是lambda");
});
}
public static void useShowHandler(ShowHandler showHandler) {
showHandler.show();
}
}
interface ShowHandler {
void show();
}
⑷. 练习 2
- 编写一个接口(StringHandler)
- 在该接口中存在一个抽象方法(printMessage),该方法是有参数无返回值
- 在测试类(StringHandlerDemo)中存在一个方法(useStringHandler),方法的的参数是StringHandler类型的,在方法内部调用了StringHandler的printMessage方法
public class test03 {
public static void main(String[] args) {
useStringHandler(new StringHandler() {
@Override
public void printMessage(String msg) {
System.out.println(msg + "World");
}
// => helloWorld
});
useStringHandler((String msg) -> {
System.out.println(msg + "World");
});
// => helloWorld
}
public static void useStringHandler(StringHandler stringHandler) {
stringHandler.printMessage("hello");
}
}
interface StringHandler {
void printMessage(String msg);
}
⑷. 练习 3
- 编写一个接口(RandomNumHandler)
- 在该接口中存在一个抽象方法(getNumber),该方法是无参数但是有返回值
- 在测试类(RandomNumHandlerDemo)中存在一个方法(useRandomNumHandler),方法的的参数是RandomNumHandler类型的,在方法内部调用了RandomNumHandler的getNumber方法
public class test04 {
public static void main(String[] args) {
useRandomNumHandler(() -> {
Random r = new Random();
int num = r.nextInt(10) + 1;
return num;
});
// => 生成 1 ~ 10 之间的随机数
}
public static void useRandomNumHandler(RandomNumHandler randomNumHandler) {
int num = randomNumHandler.getNumber();
System.out.println(num);
}
}
interface RandomNumHandler {
int getNumber();
}
⑷. 练习 4
- 编写一个接口(Calculator)
- 在该接口中存在一个抽象方法(calc),该方法是有参数也有返回值
- 在测试类(CalculatorDemo)中存在一个方法(useCalculator),方法的的参数是Calculator类型的,在方法内部调用了Calculator的calc方法
public class test05 {
public static void main(String[] args) {
// CalculatorDemo((int a, int b) -> {
// return a + b;
// });
// 简化
CalculatorDemo((a, b) -> a + b);
}
public static void CalculatorDemo(Calculator calculator) {
int result = calculator.calc(10, 20);
System.out.println(result);
}
}
interface Calculator {
int calc(int a, int b);
}
⑸. 省略模式
- 参数类型可以省略,但是有多个参数的情况下,不能只省略一个
- 如果参数有且仅有一个,那么小括号可以省略
- 如果代码块的语句只有一条,可以省略大括号和分号,甚至是return
⑹. Lambda表达式和匿名内部类的区别
- 所需类型不同:
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式: 只能是接口
- 使用限制不同:
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
- 实现原理不同:
- 匿名内部类:编译之后,产生一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
八、常用API
API(Application Programming interface) 应用程序接口
简单来说:就是Java帮我们已经写好的一些方法,我们直接拿过来用就可以了
1. Math
方法名 | 描述 |
---|---|
public static int abs(int a) | 返回参数的绝对值 |
public static double ceil (double a) | 向上取整 |
public static double floor (double a) | 向下取整 |
public static int round (float a) | 四舍五入 |
public static int max (int a,int b) | 返回两个int值中的较大值 |
public static int min (int a,int b) | 返回两个int值中的较小值 |
public static double pow (double a,double b) | 返回a的b次幂的值 |
public static double random () | 返回值为double的正值,[0.0,1.0) |
示例:
public class test {
public static void main(String[] args) {
// 返回参数的绝对值
int abs = Math.abs(-10);
System.out.println(abs);
// => 10
// 向上取整
double ceil = Math.ceil(10.1);
System.out.println(ceil);
// => 11.0
// 向下取整
double floor = Math.floor(10.9);
System.out.println(floor);
// => 10.0
// 四舍五入
long round = Math.round(10.4);
long round1 = Math.round(10.5);
System.out.println(round);
// => 10
System.out.println(round1);
// => 11
// 返回两个int值中的较大值
int max = Math.max(3, 2);
System.out.println(max);
// => 3
// 返回两个int值中的较小值
int min = Math.min(3, 2);
System.out.println(min);
// => 2
// 返回a的b次幂的值
double pow = Math.pow(2, 3);
System.out.println(pow);
// => 8
// 返回值为double的随机数
double random = Math.random();
System.out.println(random);
// => [0.0,1.0) (随机数)
}
}
2. System
方法名 | 描述 |
---|---|
arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数) | 数组copy |
public static long currentTimeMillis () | 返回当前时间(以毫秒为单位) |
public static void exit (int status) | 终止当前运行的 Java 虚拟机,非零表示异常终止 |
示例:
public class test {
public static void main(String[] args) {
// 数组copy: arraycopy(被拷贝数组, 被拷贝数组的起始索引, 拷贝数组, 拷贝数组的起始索引, 拷贝个数)
int[] arr1 = {1, 2, 3, 4, 5};
int[] arr2 = new int[9];
System.arraycopy(arr1, 2, arr2, 4, 3);
for (int i = 0; i < arr2.length; i++) {
System.out.print(arr2[i] + ",");
}
// => 0,0,0,0,3,4,5,0,0,
// 返回当前时间(以毫秒为单位)
System.out.println(System.currentTimeMillis());
// => 1657116321587
// 终止当前运行的 Java 虚拟机,非零表示异常终止
System.out.println("start");
System.exit(0);
System.out.println("end");
// => start
}
}
3. Object
⑴. 概述
每个类都可以将 Object 作为父类。所有类都直接或者间接的继承自该类
- Object类是所有类的直接或者间接父类
- 直接打印一个对象就是打印这个对象的toString方法的返回值
- Object类的toString方法得到的是对象的地址值
- 一般会对toString方法进行重写
⑵. 方法
方法名 | 描述 |
---|---|
public String toString() | 返回对象的字符串表示形式;建议所有子类重写该方法,自动生成 |
public boolean equals(另一个对象) | 比较对象是否相等;默认比较地址,重写可以比较内容,自动生成 |
⑶. 示例
public class test {
public static void main(String[] args) {
Student student = new Student("zoe", 18);
System.out.println(student);
// => api1.object.Student@2f92e0f4 (包名 + 类名 + @ + 对象地址值)
// toString 方法重写后
Student student2 = new Student("tony", 65);
System.out.println(student2);
// => Student{name='tony', age=65}
}
}
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
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;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
// return name != null ? name.equals(student.name) : student.name == null;
return Objects.equals(name, student.name);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
⑷. 面试题
public class demo {
public static void main(String[] args) {
String s1 = "abc";
StringBuilder sb = new StringBuilder("abc");
System.out.println(s1.equals(sb));
// => false
// String 的 equals 源码中调用了 instanceof 来进行比较,如果两者数据类型不同,直接返回false,如果类型相同,才会转换类型进行比较
//StringBuilder类中是没有重写equals方法,用的就是Object类中的.
System.out.println(sb.equals(s1));
// => false
// 调用的 Object 的 equals 方法,会通过 == 进行比较两者的储存地址
}
}
4. Objects
方法名 | 描述 |
---|---|
public static String toString(对象) | 返回参数中对象的字符串表示形式 |
public static String toString(对象, 默认字符串) | 返回对象的字符串表示形式 |
public static Boolean isNull(对象) | 判断对象是否为空 |
public static Boolean nonNull(对象) | 判断对象是否不为空 |
示例:
public class demo {
@Override
public String toString() {
return super.toString();
}
public static void main(String[] args) {
// 返回参数中对象的字符串表示形式
Student s1 = new Student("zoe", 18);
String result1 = Objects.toString(s1);
System.out.println(result1);
// => Student{name='zoe',age='18'}
// 返回对象的字符串表示形式。如果对象为空,那么返回第二个参数.
Student s2 = null;
String res2 = Objects.toString(s2, "默认内容");
System.out.println(res2);
// => 默认内容
// 判断对象是否为空
Student s3 = new Student("tony", 56);
boolean res3 = Objects.isNull(s3);
System.out.println(res3);
// => false
// 判断对象是否不为空
Student s4 = null;
boolean res4 = Objects.nonNull(s4);
System.out.println(res4);
// => false
}
}
class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
@Override
public String toString() {
return "Student{" +
"name='" +
name + '\'' + ",age='"
+ age + "'}";
}
}
5. BigDecimal
方法名 | 描述 |
---|---|
public BigDecimal add(另一个BigDecimal对象) | 加法 |
public BigDecimal subtract (另一个BigDecimal对象) | 减法 |
public BigDecimal multiply (另一个BigDecimal对象) | 乘法 |
public BigDecimal divide (另一个BigDecimal对象) | 除法 |
public BigDecimal divide (另一个BigDecimal对象,精确几位,舍入模式) | 除法(精准计算) |
示例:
public class demo2 {
public static void main(String[] args) {
BigDecimal bd1 = new BigDecimal(0.1);
BigDecimal bd2 = new BigDecimal(0.2);
BigDecimal bd3 = new BigDecimal("0.1");
BigDecimal bd4 = new BigDecimal("0.2");
// add 加法
// 注意:如果需要精确计算,需要传入 String 类型数据
BigDecimal res1 = bd1.add(bd2);
System.out.println(res1);
// => 0.300000000000000016653345...
BigDecimal res2 = bd3.add(bd4);
System.out.println(res2);
// => 0.3
// subtract 减法
BigDecimal res3 = bd3.subtract(bd4);
System.out.println(res3);
// => -0.1
// multiply 乘法
BigDecimal res4 = bd3.multiply(bd4);
System.out.println(res4);
// => 0.02
// divide 除法
BigDecimal res5 = bd3.divide(bd4);
System.out.println(res5);
// => 0.5
// divide( 运算对象,小数点精确位数,舍入模式 )
// 舍入模式:进一法 ROUND_UP、去尾法 ROUND_FLOOR、四舍五入ROUND_HALF_UP
BigDecimal bd6 = new BigDecimal("10.00");
BigDecimal bd7 = new BigDecimal("3.00");
// BigDecimal res6 = bd6.divide(bd7);
// System.out.println(res6);
// => Non-terminating decimal expansion(报错:非终止十进制扩展)
BigDecimal res7 = bd6.divide(bd7, 3, BigDecimal.ROUND_UP);
System.out.println(res7);
// => 3.334
BigDecimal res8 = bd6.divide(bd7, 3, BigDecimal.ROUND_FLOOR);
System.out.println(res8);
// =>3.333
BigDecimal res9 = bd6.divide(bd7, 3, BigDecimal.ROUND_HALF_UP);
System.out.println(res9);
// => 3.333
}
}
6. 基本类型包装类
⑴. 概述
将基本数据类型封装成对象的目的是:可以在对象中定义更多的功能方法操作该数据;
例如: 基础数据类型和字符串之间转换
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
⑵. Integer的使用
Integer:该对象中包装了一个基本数据类型int的值
方法名 | 描述 |
---|---|
public Integer(int value) | 根据 int 值创建 Integer 对象(过时) |
public Integer(String s) | 根据 String 值创建 Integer 对象(过时) |
public static Integer valueOf (int i) | 返回表示指定的 int 值的 Integer 实例 |
public static Integer valueOf (String s) | 返回一个保存指定值的 Integer 对象 String |
示例:
public class demo1 {
public static void main(String[] args) {
// 过时的构造方法
Integer integer1 = new Integer(100);
Integer integer2 = new Integer("200");
// 工厂函数
Integer integer3 = Integer.valueOf(100);
Integer integer4 = Integer.valueOf("200");
// 简化
Integer integer5 = 100;
// JDK1.5 的特性 --- 自动装箱
// 自动: Java 底层会自动调用 valueOf()方法
// 装箱: 将基本数据类型 变成对应的包装类
// 自动拆箱
Integer i1 = Integer.valueOf(100);
int i2 = i1;
// 拆箱: 将包装类 变成对应的基本数据类型
// 隐患:null 无法转换成基础数据类型
Integer i3 = null;
i3 += 100;
System.out.println(i3);
// => NullPointerException (空指针异常)
// 使用包装类类型的时候,使用前需进行不为 null 判断
}
}
⑶. Integer的成员方法
方法名 | 描述 |
---|---|
static int parseInt(String s) | 将字符串类型的整数变成int类型的整数 |
示例:
public class demo2 {
public static void main(String[] args) {
// String + int ---> String
String str1 = "100";
int num1 = 200;
System.out.println(str1 + num1);
// => 100200
// String ---> int
String str2 = "100";
int num2 = 200;
int num3 = Integer.parseInt(str2);
System.out.println(num2 + num3);
// => 300
// int ---> String
// A: +
int num4 = 100;
String str3 = num4 + "";
System.out.println(str3 + 100);
// => 100100
// B: valueOf
int num5 = 100;
String str4 = String.valueOf(num5);
System.out.println(str4 + 100);
// => 100100
}
}
⑷. Integer案例
需求: 有一个字符串"90 26 73 59 40",把其中每一个数存到 int 类型的数组中,并输出
public class demo3 {
public static void main(String[] args) {
// 需求: 有一个字符串"90 26 73 59 40",把其中每一个数存到 int 类型的数组中
String str = "90 26 73 59 40";
// 切割成字符串数组
String[] strArr = str.split(" ");
int[] intArr = new int[strArr.length];
for (int i = 0; i < strArr.length; i++) {
// 转换数据类型
int temp = Integer.parseInt(strArr[i]);
intArr[i] = temp;
}
for (int i = 0; i < intArr.length; i++) {
System.out.print(intArr[i] + ",");
}
// => 90,26,73,59,40,
}
}
7. 数组的高级操作
⑴. 二分法
在计算机科学中,二分查找算法,也称折半搜索算法,是一种在有序数组中查找某一特定元素的搜索算法
public class search01 {
public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int index = searchHalf(arr1, 5);
System.out.println(index);
}
// 二分法
public static int searchHalf(int[] arr, int num) {
int max = arr.length - 1;
int min = 0;
// 查找的元素存在
while (min <= max) {
// 双目移位运算符:其功能是把“>> ”左边的运算数的各二进位全部右移若干位,“>>”右边的数指定移动的位数
// 等价 => (max + min) / 2
int mid = (max + min) >> 1;
if (num < arr[mid]) {
max = mid - 1;
} else if (num > arr[mid]) {
min = mid + 1;
} else {
return mid;
}
}
return -1;
}
}
⑵. 冒泡排序
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法
冒泡排序的思想就是在每次遍历一遍未排序的数列之后,将一个数据元素浮上去(也就是排好了一个数据)
public class sort01 {
public static void main(String[] args) {
// 冒泡排序:
// 1. 相邻元素,两两比较,大的放到右边,依次向后执行
// 2. 执行到最右边,那最右边的数字就是最大值,不用进行下一次比较
// 3. 排除最右边的值,进行下一次循环比较
// 4. 直至只有一个值(最小值),结束循环
int[] arr1 = {2, 5, 1, 3, 4};
int[] newArr = sortDemo(arr1);
for (int i = 0; i < newArr.length; i++) {
System.out.println(newArr[i]);
}
}
public static int[] sortDemo(int[] arr) {
// 总循环次数:数组长度 - 1 次(只剩一个值时停止循环)
for(int i = 0; i < arr.length - 1; i++) {
// 遍历数组值
for (int j = 0; j < arr.length - 1 - i; j++) {
// 相邻元素,两两比较,如果左边值大,则交换位置
if(arr[j] > arr[j + 1]) {
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
}
⑶. 递归
以编程的角度来看,递归指的是方法定义中调用方法本身的现象
需求: 计算从 1 … + 100 的值
public class recursion {
public static void main(String[] args) {
int result = 0;
for (int i = 0; i <= 100; i++) {
result += i;
}
System.out.println(result);
// => 5050
// 递归
int result2 = recursionDemo(100);
System.out.println(result2);
// => 5050
}
public static int recursionDemo(int num) {
if(num == 1) {
return 1;
} else {
return num + recursionDemo(num - 1);
}
}
}
⑷. 快速排序
快速排序(Quick Sort)算法是在冒泡排序的基础上进行改进的一种算法,该排序算法的特点是快、效率高,是处理大数据最快的排序算法之一
实现的基本思想是:通过一次排序将整个无序表分成相互独立的两部分,其中一部分中的数据都比另一部分中包含的数据的值小;然后继续沿用此方法分别对两部分进行同样的操作,直到每一个小部分不可再分,所得到的整个序列就变成有序序列
8. Arrays
Arrays 类包含用于操作数组的各种方法
方法名 | 描述 |
---|---|
public static String toString (int[] a) | 返回指定数组的内容的字符串表示形式 |
public static void sort (int[] a) | 按照数字顺序排列指定的数组 |
public static int binarySearch (int[] a, int key) | 利用二分查找返回指定元素的索引 |
示例:
public class demo1 {
public static void main(String[] args) {
// toString
int[] intArr = {1, 3, 5, 6, 9};
System.out.println(Arrays.toString(intArr));
// => [1, 3, 5, 6, 9]
// sort
int[] intArr2 = {4, 9, 2, 1, 2};
Arrays.sort(intArr2);
System.out.println(Arrays.toString(intArr2));
// => [1, 2, 2, 4, 9]
// binarySearch
int[] intArr3 = {1, 2, 4, 5, 6, 7, 8};
int index = Arrays.binarySearch(intArr3, 7);
System.out.println(index);
// => 5
}
}
九、异常
1. 概述
⑴. 什么是异常
就是程序出现了不正常的情况。程序在执行过程中,出现的非正常的情况,最终会导致 JVM 的非正常停止
注意: 语法错误不算在异常体系中
示例:
public class demo1 {
public static void main(String[] args) throws ParseException {
// ArrayIndexOutOfBoundsException 非法索引访问数组时抛出的异常
// 如果索引为负或大于等于数组大小,则该索引为非法索引
// int[] arr = {1, 2, 3, 4, 5};
// System.out.println(arr[9]);
// NullPointerException 空指针异常
// 引用本身为空,却调用了方法,这个时候就会出现空指针异常
// String s = null;
// System.out.println(s.equals("string"));
// ParseException 编译异常
// 编译异常,不解决没法运行,必须处理
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
sdf.parse("2022-12月20日");
}
}
⑵. 异常体系
Error
:严重问题,通过代码无法处理。 比如:内存溢出。Exception
:称为异常类,它表示程序本身可以处理的问题RuntimeException
及其子类:运行时异常 (空指针异常,数组索引越界异常)- 除
RuntimeException
之外所有的异常:编译期必须处理的,否则程序不能通过编译 (日期格式化异常)
编译时异常和运行时异常:
- Javac.exe: 编译时异常,是在编译成class文件时必须要处理的异常,也称之为受检异常
- Java.exe: 运行时异常,在编译成class文件不需要处理,在运行字节码文件时可能出现的异常。也称之为非受检异常
⑷. JVM 的默认处理方案
- 如果程序出现了问题,没有设定异常处理的话,最终
JVM
会做默认的处理。 - 把异常的名称,异常原因及异常出现的位置等信息输出在了控制台 并 程序停止执行
2. 异常处理方式
⑴. throw、throws
①. throws
# 格式
throws 异常类名;
# 注意:这个格式是写在方法的定义处,表示声明一个异常
- 编译时异常因为在编译时就会检查,所以必须要写在方法后面进行显示声明
- 运行时异常因为在运行时才会发生,所以在方法后面可以不写
示例:
public class ExceptionDemo6 {
public static void main(String[] args) throws ParseException {
method1(); //此时调用者也没有处理.还是会交给虚拟机处理.
method2(); //还是继续交给调用者处理.而main方法的调用者是虚拟机还是会采取虚拟机默认处理异常的方法.
}
//告诉调用者,你调用我,有可能会出现这样的异常哦.
//如果方法中没有出现异常,那么正常执行
//如果方法中真的出现了异常,其实也是将这个异常交给了调用者处理.
//如果声明的异常是一个运行时异常,那么声明的代码可以省略
private static void method1() /*throws NullPointerException*/ {
int [] arr = null;
for (int i = 0; i < arr.length; i++) {//出现的空指针异常,还是由虚拟机创建出来的.
System.out.println(arr[i]);
}
}
//告诉调用者,你调用我,有可能会出现这样的异常哦.
//如果方法中没有出现异常,那么正常执行
//如果方法中真的出现了异常,其实也是将这个异常交给了调用者处理.
//如果声明的异常是一个编译时异常,那么声明的代码必须要手动写出.
private static void method2() throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
sdf.parse("2048-10月10日");
}
}
②. throw
# 格式
throw new 异常();
# 注意:这个格式是在方法内的,表示当前代码手动抛出一个异常,下面的代码不用再执行了
示例:
public class ExceptionDemo8 {
public static void main(String[] args) {
//int [] arr = {1,2,3,4,5};
int [] arr = null;
printArr(arr);
//就会 接收到一个异常.
//我们还需要自己处理一下异常.
}
private static void printArr(int[] arr) {
if(arr == null){
//调用者知道成功打印了吗?
//System.out.println("参数不能为null");
throw new NullPointerException();
//当参数为null的时候
//手动创建了一个异常对象,抛给了调用者.
}else{
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
}
③. throw 和 throws 的区别
throws | throw |
---|---|
用在方法声明后面,跟的是异常类名 | throw 用在方法体内,跟的是异常对象名 |
表示声明异常,调用该方法有可能会出现这样的异常 | 表示手动抛出异常对象,由方法体内的语句处理 |
⑵. try…catch…
①. 格式
# 格式
try {
可能出现异常的代码;
} catch(异常类名 变量名) {
异常的处理代码;
}
# 程序可以继续向下执行
②. 示例
public class ExceptionDemo9 {
public static void main(String[] args) {
//好处:为了能让代码继续往下运行.
int [] arr = null;
try{
//有肯能发现异常的代码
printArr(arr);
}catch (NullPointerException e){
//如果出现了这样的异常,那么我们进行的操作
System.out.println("参数不能为null");
}
System.out.println("helloWorld");
}
private static void printArr(int[] arr) {
if(arr == null){
throw new NullPointerException();
}else{
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
}
③. 疑难点
1. 如果 try 中没有遇到问题,怎么执行?
- 会把try中所有的代码全部执行完毕,不会执行catch里面的代码
2. 如果 try 中遇到了问题,那么 try 下面的代码还会执行吗?
- 那么直接跳转到对应的catch语句中,try下面的代码就不会再执行了
- 当catch里面的语句全部执行完毕,表示整个体系全部执行完全,继续执行下面的代码
3. 如果出现的问题没有被捕获,那么程序如何运行?
- 那么try…catch就相当于没有写,那么也就是自己没有处理
- 默认交给虚拟机处理
4. 同时有可能出现多个异常怎么处理?
- 出现多个异常,那么就写多个catch就可以了
- 注意点: 如果多个异常之间存在子父类关系,那么父类一定要写在下面
⑶. Throwable 的成员方法
方法名 | 描述 |
---|---|
public String getMessage () | 返回此 throwable 的详细消息字符串 |
public String toString () | 返回此可抛出的简短描述 |
public void printStackTrace () | 把异常的错误信息输出在控制台 |
示例:
public class ExceptionDemo11 {
//public String getMessage() 返回此 throwable 的详细消息字符串
//public String toString() 返回此可抛出的简短描述
//public void printStackTrace() 把异常的错误信息输出在控制台(字体为红色的)
public static void main(String[] args) {
try {
int [] arr = {1,2,3,4,5};
System.out.println(arr[10]);
//虚拟机帮我们创建了一个异常对象 new ArrayIndexOutOfBoundsException();
} catch (ArrayIndexOutOfBoundsException e) {
/*String message = e.getMessage();
System.out.println(message);*/
/* String s = e.toString();
System.out.println(s);*/
e.printStackTrace();
}
System.out.println("helloWorld");
}
}
⑷. 实例
需求: 键盘录入学生的姓名和年龄,其中年龄为 18 - 25岁,超出这个范围是异常数据不能赋值,需要重新录入,一直录到正确为止
public class ExceptionDemo12 {
public static void main(String[] args) {
Student s = new Student();
Scanner sc = new Scanner(System.in);
System.out.println("请输入姓名");
String name = sc.nextLine();
s.setName(name);
while(true){
System.out.println("请输入年龄");
String ageStr = sc.nextLine();
try {
int age = Integer.parseInt(ageStr);
s.setAge(age);
break;
} catch (NumberFormatException e) {
System.out.println("请输入一个整数");
continue;
} catch (AgeOutOfBoundsException e) {
System.out.println(e.toString());
System.out.println("请输入一个符合范围的年龄");
continue;
}
/*if(age >= 18 && age <=25){
s.setAge(age);
break;
}else{
System.out.println("请输入符合要求的年龄");
continue;
}*/
}
System.out.println(s);
}
}
3. 自定义异常
步骤: 定义异常类 -> 写继承关系 -> 空参构造 -> 带参构造
自定义异常存在的意义:就是为了让控制台的报错信息更加的见名之意。
# 格式
public class AgeOutOfBoundsException extends RuntimeException {
public AgeOutOfBoundsException() {
}
public AgeOutOfBoundsException(String message) {
super(message);
}
}