1、命令行操作与 JDK
1.1、命令行基础知识
命令行 MS-DOS(Microsoft Disk Operation System)
命令行中的基本操作
1 | # Win+R输入cmd |
1.2、JDK
JVM、JDK 与 JRE
JVM(Java Virtual Machine)
Java 虚拟机:Java 是一种跨平台的语言,是因为每个操作系统下有不同版本的虚拟机,比如 win 版 JVM、Linux 版 JVM、Mac 版 JVM,虚拟机本身不跨平台 。
JRE(Java Runtime Environment)
Java 程序的运行环境,包含 JVM 和运行时需要的核心类库。
JDK(Java Development Kit)
- Java 程序开发工具包,包含 JRE 和开发人员使用的工具;
- 运行已有的 Java 程序,只需 JRE;
- 开发全新 Java 程序 必须安装 JDK,JDK 包含 JRE,JRE 包含 JVM。
JDK、JRE、JVM 之间的关系

1.3、一个 Java 程序运行的过程
1 | Java 源程序 -> 编译器 -> Java 字节码文件 -> JVM 运行(字节码文件) |
新建 HelloWorld.java
文件
1 | // 类的名称必须和文件名完全一样 |
打开命令行,输入以下内容执行程序。
1
2javac HelloWorld.java # 编译,生成HelloWorld.class
java HelloWorld # 无.class后缀,运行程序,如果程序内容更改了需要用javac重新编译命名规范
类名:首字母大写,后面每个单词首字母大写。(大驼峰式)
变量名/方法名:首字母小写,后面每个单词首字母大写。(小驼峰式)
编译期
compile:通过编译器进行编译,其中有各种校验解析步骤,把源代码编译成了可被虚拟机执行的字节码, Java源码 -> Java字节码
这个编译器是 jdk 里的 javac 编译器, javac HelloWorld.java
编译该源码,javac 编译器位置:jdk\bin\javac.exe
运行期
load and execute:加载 Java 字节码并执行
通过 jdk 里的java命令运行 Java 字节码,java HelloWorld
加载并执行该字节码,当运行 java 命令时,JRE 将与您指定的类一起加载。然后执行该类的主要方法,java 命令位置:jdk\bin\java.exe
Java 提供类加载器把虚拟机外部的字节码资源载入到虚拟机的运行时环境(主要是指虚拟机的方法区)并提供字节码验证器来保证载入的字节码是安全合法的,对程序没有危害的。
加载器 (Class Loader):当字节码还没被类加载器加载之前它目前还处于虚拟机外部存储空间里,要想执行它需要通过类加载器来加载到虚拟机的运行时内存空间里,常见的类加载器:
- Bootstrap ClassLoader(启动类加载器):加载位于
<JAVA_HOME>\lib
目录下的类文件,如rt.jar
; - Extension ClassLoader(扩展类加载器): 加载位于
<JAVA_HOME>\lib\ext
目录下的类文件; - Application ClassLoader(应用程序类加载器):加载位于类路径(ClassPath)下的类文件(这部分可参考注解与反射)
2、变量
2.1、局部变量与成员变量
==局部变量和成员变量的区别==
定义的位置不一样
局部变量:在方法的内部。
成员变量:在方法的外部,直接写在类当中。
作用范围不一样
局部变量:只有方法当中才可以使用,出了方法就不能再用。
成员变量:整个类全都可以通用。
默认值不一样
局部变量:没有默认值,如果要想使用,必须手动进行赋值。
成员变量:如果没有赋值,会有默认值,规则和数组一样。
内存的位置不一样
局部变量:位于栈内存
成员变量:位于堆内存
生命周期不一样
局部变量:随着方法进栈而诞生,随着方法出栈而消失。(生命周期稍微短一点)
成员变量:随着对象创建而诞生,随着对象被垃圾回收而消失。(何时回收不能控制)
1 | public class Demo01VariableDifference { |
2.2、BigDecimal
float,double 等表示会有损失,需要用到BigDecimal
- 不能处理无限循环小数;
- 可以使用
BigDecimal.valueOf(0.580)
,或者new BigDecimal("0.580")
进行精确转换;
3、方法
一个方法(函数)
1 | public class HelloWorld // 类的名称,必须和文件名完全一样 |
==方法的重载(OverLoad)==
多个方法名称一样,参数个数 或 参数类型 或 多类型参数的顺序不一样,叫方法的重载。
==&&
与 ||
具有短路效果==
如果根据左边已经可以判断得到最终结果,那么右边的代码将不再执行,从而节省一定的性能。
==Java 中的 switch/case==
switch 后面小括号中的数据类型只能是基本数据类型:byte/short/char/int,引用数据类型:String/enum;
case 没有 break 向下执行,穿透下一个 case,直到遇到 break 或整体结束;
死循环后有语句会报错。
4、static
==static 关键字修饰成员变量==
如果一个成员变量使用了static关键字,那么这个变量不再属于对象自己,而是属于所在的类。多个对象都可以共享这同一份数据。
以下代码只对 two 中的 room 进行了赋值,但是 one 中也能读取到。
1 | class Student { |
==static 关键字修饰成员方法==
一旦使用 static 修饰成员方法,那么这就成为了静态方法。静态方法不属于对象,而是属于类的。
如果没有 static 关键字,那么必须首先创建对象,然后通过对象才能使用它。
如果有了static关键字,那么不需要创建对象,直接就能通过类名称来使用它。
无论是成员变量,还是成员方法。如果有了 static,都推荐使用类名称进行调用。
- 静态变量:
类名称.静态变量
- 静态方法:
类名称.静态方法()
==注意事项==
静态不能直接访问非静态。
因为在内存当中是先有的静态内容,后有的非静态内容。“先人不知道后人,但是后人知道先人。”
静态方法当中不能用 this。 原因:this 代表当前对象,通过谁调用的方法,谁就是当前对象。
静态和对象没有关系,静态通过类名直接调用,这时候没有对象,this 代表当前对象,通过谁调用的方法,谁就是当前对象,所以不能用 this。
1 | public class MyClass { |
1 | public class Demo02StaticMethod { |
==静态代码块==
1 | public class 类名称 { |
特点:当第一次用到本类时,静态代码块执行唯一的一次。静态内容总是优先于非静态,所以静态代码块比构造方法先执行。
静态代码块的典型用途: 用来一次性地对静态成员变量进行赋值。
1 | class Person{ |
1 | public class Demo04Static { |
5、接口
5.1、接口的使用
接口:接口就是多个类的公共规范。接口是一种引用数据类型,其中最重要的内容就是抽象方法。
在任何版本的 Java 中,接口都能定义抽象方法。接口当中的抽象方法,修饰符必须是两个固定的关键字:public、abstract,这两个关键字修饰符可以选择性地省略。
1 | public interface MyInterfaceAbstract { |
==如何定义一个接口==
关键字由 class 换成了 interface 之后,编译生成的字节码文件仍然是:.java -> .class。
1 | public interface 接口名称 { |
==接口的使用步骤==
接口不能直接使用,必须有一个实现类来实现该接口。(类比于子类继承父类)
1
2
3public class 实现类名称 implements 接口名称 {
// ...
}接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。
接口的实现类命名:接口名+Impl(implements 的缩写)
如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。
创建实现类的对象,进行使用。
1
2MyInterfaceAbstract inter = new MyInterfaceAbstract(); // 不能直接new接口
MyInterfaceAbstractImpl impl = new MyInterfaceAbstractImpl(); // 要创建实现类的对象进行调用
==使用接口的注意事项==
接口没有静态代码块或者构造方法。
一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。
1
2
3public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
// 覆盖重写所有抽象方法
}
5.2、JDK 8/JDK 9 对接口的扩展
在不同的 JDK 版本中,接口中可以有不同的内容。
- JDK 7:常量、抽象方法
- JDK 8:常量、抽象方法、默认方法、静态方法
- JDK 9:常量、抽象方法、默认方法、静态方法、私有方法
==JDK 8:新增默认方法==
从 JDK 8 开始,接口里允许定义默认方法,子类可以选择是否重写默认方法。在原有情况下,如果接口中新增了抽象方法,子类就必须覆盖重写,如果新增默认方法,就可以重写也可以不重写,不必为了添加新方法而将实现类修改为抽象类。
1 | public interface MyInterfaceDefault { |
1 | public class MyInterfaceDefaultA implements MyInterfaceDefault { |
1 | public class MyInterfaceDefaultB implements MyInterfaceDefault { |
测试:当实现类中没有默认方法时,会向上找接口中的实现。
1 | public class Demo02Interface { |
==JDK 8:新增静态方法==
从 JDK 8 开始,接口当中允许定义静态方法。(就是将 abstract 或者 default 换成 static 即可,带上方法体。)
1 | public static 返回值类型 方法名称(参数列表) { |
静态方法可以通过接口直接调用:接口名称.静态方法名(参数);
注意事项:不能通过接口实现类的对象来调用接口当中的静态方法。 要通过接口名称直接调用其中的静态方法。
1 | public interface MyInterfaceStatic { |
1 | public class MyInterfaceStaticImpl implements MyInterfaceStatic {} |
1 | public class Demo03Interface { |
==JDK 9:新增私有方法==
问题描述:我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题。 但是这个公有方法不应该让实现类使用,应该是私有化的。
从 JDK 9 开始,接口当中允许定义私有方法,主要用于解决多个方法之间重复代码问题。
普通私有方法,解决多个默认方法之间重复代码问题。
1
2
3private 返回值类型 方法名称(参数列表) {
// 方法体
}通过抽取方法
mothodCommon
解决重复代码问题,实现类不应该能够使用抽取方法,抽取方法应该是私有的,这里就要用到普通私有方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public interface MyInterfacePrivateA {
public default void methodDefault1() {
System.out.println("默认方法1");
// System.out.println("AAA");
// System.out.println("BBB");
methodCommon();
}
public default void methodDefault2() {
System.out.println("默认方法2");
// System.out.println("AAA");
// System.out.println("BBB");
methodCommon();
}
// public default void methodCommon(){} // ×
private void methodCommon() { // 更改之后实现类就访问不到了
System.out.println("AAA");
System.out.println("BBB");
}
}静态私有方法,解决多个静态方法之间重复代码问题。
1
2
3private static 返回值类型 方法名称(参数列表) {
// 方法体
}静态私有方法解决代码抽取问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public interface MyInterfacePrivateB {
public static void methodStatic1() {
System.out.println("静态方法1");
// System.out.println("AAA");
// System.out.println("BBB");
methodStaticCommon();
}
public static void methodStatic2() {
System.out.println("静态方法2");
// System.out.println("AAA");
// System.out.println("BBB");
methodStaticCommon();
}
// public static void methodStaticCommon(){} // ×
private static void methodStaticCommon() {
System.out.println("AAA");
System.out.println("BBB");
}
}
/*****************************************************************/
public static void main(String[] args) {
MyInterfacePrivateB.methodStatic1();
MyInterfacePrivateB.methodStatic2();
// 错误写法!
// MyInterfacePrivateB.methodStaticCommon();
}
5.3、接口中的成员变量
接口当中也可以定义 “成员变量”,但是必须使用 public static final
三个关键字进行修饰。 从效果上看,这其实就是接口的【常量】,一旦赋值,不可修改。 一旦使用 final 关键字进行修饰,说明不可改变。
1 | // public static final 数据类型 常量名称 = 数据值; |
1 | public class Demo05Interface { |
==注意事项==
- 接口当中的常量,可以省略
public static final
,但是无论不写都默认是public static final
。 - 接口当中的常量,必须进行赋值;不能不赋值。(因为有 final)
- 接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则)
5.4、类的单继承
==对子类实现的接口和父类中同名方法的处理==
如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
如果实现类实现的多个接口当中存在重复的默认方法,那么实现类(无论是否是抽象类)一定要对冲突的默认方法进行覆盖重写。
1
2
3
4
5
6
7public interface MyInterfaceA {
public abstract void methodA();
public abstract void methodAbs(); // 重复的抽象方法
public default void methodDefault() { // 重复的默认方法
System.out.println("默认方法AAA");
}
}1
2
3
4
5
6
7public interface MyInterfaceB {
public abstract void methodB();
public abstract void methodAbs(); // 重复的抽象方法
public default void methodDefault() { // 重复的默认方法
System.out.println("默认方法BBB");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class MyInterfaceImpl /*extends Object*/ implements MyInterfaceA, MyInterfaceB {
public void methodA() {
System.out.println("覆盖重写了A方法");
}
public void methodB() {
System.out.println("覆盖重写了B方法");
}
public void methodAbs() { // 覆盖重写一次即可
System.out.println("覆盖重写了AB接口都有的抽象方法");
}
public void methodDefault() { // 对冲突的默认方法进行覆盖重写
System.out.println("对多个接口当中冲突的默认方法进行了覆盖重写");
}
}一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,会优先用父类当中的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class Fu {
public void method() {
System.out.println("父类方法");
}
}
/**********************************************/
public interface MyInterface {
public default void method() {
System.out.println("接口的默认方法");
}
}
/**********************************************/
public class Zi extends Fu implements MyInterface {
// 此处不用覆盖重写method()方法,继承是优于接口实现的,这就是“亲爹”与“干爹”的区别
}
/*********************************************************/
public static void main(String[] args) {
Zi zi = new Zi();
zi.method(); // 输出:父类方法
}
5.5、接口之间的多继承
- 类与类之间是单继承的,直接父类只有一个;
- 类与接口之间是多实现的,一个类可以实现多个接口;
- 与类与类之间不同,接口与接口之间是多继承的。
==对子接口的父接口中同名方法的处理==
多个父接口当中的抽象方法如果重复,子接口无需重写。
多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,而且要带着default关键字。
1 | public interface MyInterfaceA { |
1 | public interface MyInterfaceB { |
这个子接口当中有几个方法?4 个
methodA()
来源于接口 A;methodB
来源于接口 B;methodCommon()
同时来源于接口 A 和 B;method()
来源于我自己。
1 | public interface MyInterface extends MyInterfaceA, MyInterfaceB { |
6、数组
==数组的特点==
- 数组是一种引用数据类型;
- 数组当中的多个数据,类型必须统一;
- 数组的长度在程序运行期间不可改变。
6.1、Java 数组的初始化
数组的初始化会在内存当中创建一个数组,并且向其中赋予一些默认值。 如果不确定数组当中的具体内容,用动态初始化;否则,已经确定了具体的内容,用静态初始化。
==动态初始化(指定长度)==
1 | // 数据类型[] 数组名称 = new 数据类型[数组长度]; |
使用动态初始化数组的时候,其中的元素将会自动拥有一个默认值,规则如下。
- 整数类型,默认为
0
; - 浮点类型,默认为
0.0
; - 字符类型,默认为
'\u0000'
;(u -> unicode) - 布尔类型,默认为
false
; - 引用类型,默认为
null
。
==静态初始化(指定内容)==
1 | // 数据类型[] 数组名称 = new 数据类型[] {元素1, 元素2, ...}; |
使用静态初始化数组的时候,格式还可以省略一下:
1 | // 数据类型[] 数组名称 = new 数据类型[] {元素1, 元素2, ...}; // 标准格式 |
静态初始化其实也有设置默认值的过程,只不过系统自动马上将默认值替换成为了大括号当中的具体数值。虽然静态初始化没有直接告诉长度,但是根据大括号里面的元素具体内容,也可以自动推算出来长度。
还可以将初始化过程拆分:动态初始化和标准静态初始化可以拆分,省略格式的静态初始化不能拆分。
1 | int[] arrayC; |
==数组是一种引用类型的变量==
所有的引用类型变量,都可以赋值为一个 null 值。但是代表其中什么都没有。数组必须进行 new 初始化才能使用其中的元素。如果只是赋值了一个 null,没有进行 new 创建,那么将会发生:空指针异常 NullPointerException
1 | int[] array = null; |
获得数组的长度:array.length
数组在方法中的应用
数组作为方法的参数,传递进去的其实是数组的地址值。 数组作为方法的返回值,返回的其实也是数组的地址值。
6.2、Java 中的内存划分
Java 的内存需要划分成为 5 个部分:
栈(Stack):存放的都是方法中的局部变量。方法的运行一定要在栈当中运行。
局部变量:方法的参数,或者是方法{}内部的变量
特点:有作用域,一旦超出作用域, 立刻从栈内存当中消失。
堆(Heap):凡是 new 出来的东西,都在堆当中。
堆内存里面的东西都有一个地址值:16进制
堆内存里面的数据,都有默认值。规则:
- 如果是整数,默认为0;
- 如果是浮点数,默认为0.0;
- 如果是字符,默认为
'\u0000'
; - 如果是布尔,默认为false;
- 如果是引用类型,默认为null。
方法区(Method Area):存储 .class 相关信息,包含方法的信息。
本地方法栈(Native Method Stack):与操作系统相关。
寄存器(pc Register):与CPU相关。
7、String
7.1、创建 String 对象
java.lang.String 类代表字符串,Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。 其实就是说:程序当中所有的双引号字符串,都是 String 类的对象。
==String 的特点==
- 字符串的内容永不可变。
- 正是因为字符串不可改变,所以字符串是可以共享使用的。
- 字符串效果上相当于是
char[]
字符数组,但是底层原理是byte[]
字节数组。
字符串是不能改变的,再次赋值还是创建了新的字符串只是变量指向的地址发生了改变,如下面这种写法,字符串中的内容仍然是没有改变的。
1 | // 下面有两个字符串:"Hello","Java",strA当中保存的是地址值。 |
==创建 String 的 3+1 种方式==
public String()
:创建一个空白字符串,不含有任何内容;public String(char[] array)
:根据字符数组的内容,来创建对应的字符串;public String(byte[] array)
:根据字节数组的内容,来创建对应的字符串;String str = "Hello";
:右边直接用双引号引上字符串内容,直接写上双引号,就是字符串对象 ,不需要new。
1 | public class Demo01String { |
7.2、字符串常量池
在程序当中直接写上的双引号字符串,就储存在字符串常量池中。( JDK 7 开始储存在堆中)
关于 ==
对于基本类型来说,
==
是进行数值的比较。对于引用类型来说,
==
是进行地址值的比较。
字符串常量池的一个例子:在字符串常量池中储存的在用 ==
比较时结果是 true。
7.3、String 常用的方法
==字符串的内容比较==
==
是进行对象的地址值比较,如果确实需要字符串的内容比较,可以使用以下两个方法:
public boolean equals(Object obj)
: 参数可以是任何对象,只有参数是一个字符串并且内容相同的才会给true;否则返回false。不过要注意:任何对象都能用Object进行接收。
equals方法具有对称性,也就是
a.equals(b)
和b.equals(a)
效果一样。如果比较的双方一个常量一个变量,推荐把常量字符串写在前面。推荐:
"abc".equals(str)
不推荐:str.equals("abc")
。1
2
3String str5 = null;
System.out.println("abc".equals(str5)); // 推荐:false
// System.out.println(str5.equals("abc")); // 不推荐:报错,空指针异常NullPointerException
public boolean equalsIgnoreCase(String str)
:忽略大小写,进行内容比较, 只有英文字母区分大小写,其他都不区分大小写。1
2
3
4
5
6String strA = "Java";
String strB = "java";
System.out.println(strA.equals(strB)); // false,严格区分大小写
System.out.println(strA.equalsIgnoreCase(strB)); // true,忽略大小写
// 只有英文字母区分大小写,其他都不区分大小写
System.out.println("abc一123".equalsIgnoreCase("abc壹123")); // false
==获取 String 相关属性的常用方法==
public int length()
:获取字符串当中含有的字符个数,拿到字符串长度;1
2
3// 获取字符串的长度
int length = "asdasfeutrvauevbueyvb".length();
System.out.println("字符串的长度是:" + length);public String concat(String str)
:将当前字符串和参数字符串 拼接成新的字符串并返回, 作用相当于+
;1
2
3
4
5
6String str1 = "Hello";
String str2 = "World";
String str3 = str1.concat(str2); // 拼接字符串
System.out.println(str1); // Hello,原封不动
System.out.println(str2); // World,原封不动
System.out.println(str3); // HelloWorld,新的字符串public char charAt(int index)
:获取指定索引位置的单个字符 。(索引从0开始)1
2char ch = "Hello".charAt(1);
System.out.println("在1号索引位置的字符是:" + ch);public int indexOf(String str)
:查找参数字符串在本字符串当中首次出现的索引位置,如果没有返回-1
。1
2
3
4String original = "HelloWorldHelloWorld";
int index = original.indexOf("llo");
System.out.println("第一次索引值是:" + index); // 2
System.out.println("HelloWorld".indexOf("abc")); // -1
==String 的截取==
public String substring(int index)
截取从参数位置一直到字符串末尾,返回新字符串。1
2
3
4String str1 = "HelloWorld";
String str2 = str1.substring(5);
System.out.println(str1); // HelloWorld,原封不动
System.out.println(str2); // World,新字符串public String
substring(int begin, int end)
截取从begin开始,一直到end结束,中间的字符串。**[begin,end)**,区间左闭右开。1
2
3String str1 = "HelloWorld";
String str3 = str1.substring(4, 7);
System.out.println(str3); // oWo
==String 的转换与分割==
public char[] toCharArray()
:将当前字符串拆分成为字符数组作为返回值。(对字符串中的元素逐个单独处理)1
2
3char[] chars = "Hello".toCharArray(); // 转换成为字符数组
System.out.println(chars[0]); // H
System.out.println(chars.length); // 5public byte[] getBytes()
:获得当前字符串底层的字节数组。1
2
3
4byte[] bytes = "abc".getBytes(); // 转换成为字节数组
for (int i = 0; i < bytes.length; i++) {
System.out.println(bytes[i]); // 输出:97 98 99
}public String replace(CharSequence oldString, CharSequence newString)
:将所有出现的老字符串替换成为新的字符串,返回替换之后的结果新字符串。(CharSequence 是一个接口,可以接受字符串类型。)1
2
3
4
5
6
7
8String str1 = "How do you do?";
String str2 = str1.replace("o", "*"); // 字符串的内容替换
System.out.println(str1); // How do you do?
System.out.println(str2); // 输出:H*w d* y*u d*?
String lang1 = "会不会玩儿呀!你大爷的!你大爷的!你大爷的!!!";
String lang2 = lang1.replace("你大爷的", "****");
System.out.println(lang2); // 输出:会不会玩儿呀!****!****!****!!!public String[] split(String regex)
:按照参数的规则,将字符串切分成为若干部分,返回值是 String 数组。split 方法的参数其实是一个 “正则表达式”,比如若要按照英文句点.
进行切分,括号内参数必须写\\.
。1
2
3
4
5
6
7
8
9
10
11
12String str1 = "aaa,bbb,ccc";
String[] array1 = str1.split(",");
for (int i = 0; i < array1.length; i++) {
System.out.println(array1[i]); // 输出:aaa bbb ccc
}
String str3 = "XXX.YYY.ZZZ";
String[] array3 = str3.split("\\.");
System.out.println(array3.length); // 3
for (int i = 0; i < array3.length; i++) {
System.out.println(array3[i]); // 输出:XXX YYY ZZZ
}
8、异常
异常,就是不正常的意思。在生活中医生说,你的身体某个部位有异常,该部位和正常相比有点不同,该部位的功能将受影响。在程序中的意思也类似,异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行。
什么是异常?
异常:指的是程序在执行过程中,出现的非正常的情况,最终会导致 JVM 的非正常停止。在 Java 等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象,Java 处理异常的方式是中断处理。
异常体系
异常机制其实是帮助我们找到程序中的问题,异常的根类是 java.lang.Throwable
,其下有两个子类:java.lang.Error
与 java.lang.Exception
,平常所说的异常指 java.lang.Exception
。

Error:严重问题,不需要处理;
Exception:异常类,程序本身可以处理的问题:
RuntimeException:都是 RuntimeException 类及其子类异常,如 NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
非 RuntimeException:这种异常的特点是 Java 编译器会检查它,也就是说,当程序中可能出现这类异常,要么用 try-catch 捕获它,要么用 throws 抛出它,否则编译不会通过。
Throwable 体系
java.lang.Throwable
类是 Java 语言中所有错误或异常的父类。
Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。好比感冒、阑尾炎。
运行期异常,Java 程序运行过程中出现的问题。在写代码时不会被发现。
1
2
3
4
5
6
7
8
9
10
11public class Demo01Exception {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
try {
// ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException extends RuntimeException extends Exception
System.out.println(arr[3]); // 可能会出现异常的代码
} catch (Exception e) {
System.out.println(e); // 异常的处理逻辑
}
}
}编译期异常,在编写代码时就会发现问题。处理方法:
try-catch
或者throws
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class Demo01Exception {
// ParseException extends Exception
public static void main(String[] args) /*throws ParseException*/ {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // 用来格式化日期
Date date = null;
try {
// 这个方法会报异常
date = sdf.parse("1999-0909"); // 把字符串格式的日期解析为Date格式的日期
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date);
}
}
Error:严重错误 Error,无法通过处理的错误,只能事先避免,好比绝症。
以下这段代码会产生内存溢出的错误,创建的数组太大了,超出了给 JVM 分配的内存,必须修改代码,使创建的数组小一点。
1
2int[] arr = new int[1024*1024*1024]; // OutOfMemoryError: Java heap space
int[] arr = new int[1024*1024]; // ✔
异常的产生过程
8.1、异常的处理
Java 异常处理的五个关键字:try、catch、finally、throw、throws
throw关键字
可以使用 throw 关键字在指定的方法中抛出指定的异常
使用格式:throw new xxxException("异常产生的原因");
注意
throw 关键字必须写在方法的内部;
throw 关键字后边 new 的对象必须是
Exception
或者Exception
的子类对象;throw 关键字抛出指定的异常对象,我们就必须处理这个异常对象;
throw 关键字后边创建的是
RuntimeException
或者是RuntimeException
的子类对象,可以不处理, 默认交给 JVM 处理(打印异常对象,中断程序)throw 关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么
throws
,要么try-catch
举个例子:定义一个方法,获取数组指定索引处的元素。
在编写方法时首先必须对方法传递过来的参数进行合法性校验,如果参数不合法,那么我们就必须使用抛出异常的方式,告知方法的调用者,传递的参数有问题。NullPointerException 和 ArrayIndexOutOfBoundsException 是两个运行期异常,我们不用处理,默认交给 JVM 处理。
1 | public class Demo03Throw { |
Throwable中的常用方法:
public void printStackTrace()
:打印异常的详细信息,包含了异常的类型、异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用 printStackTrace。
public String getMessage()
:获取发生异常的原因。提示给用户的时候,就提示错误原因。
9、常用 API
==引用类型的一般使用步骤==
导包
1
import 包路径.类名称;
如果需要使用的目标类,和当前类位于同一个包下,则可以省略导包语句不写。
只有
java.lang
包下的内容(如 String)不需要导包,其他的包都需要 import 语句。创建对象
1
类名称 对象名 = new 类名称();
使用
1
对象名.成员方法名()
9.1、Scanner
Scanner 类的功能:可以实现键盘输入数据到程序当中,使用步骤如下。
1 | Scanner sc = new Scanner(System.in); // System.in表示从键盘进行输入 |
9.2、Random
Random 类用来生成随机数字,使用步骤如下。
1 | // 1、导包 |
9.3、ArrayList
数组的长度不可以发生改变,但是 ArrayList 集合的长度是可以随意变化的。
==泛型与包装类==
ArrayList 有一个尖括号 <E>
代表泛型,装在集合当中的所有元素,全都是统一的类型,叫做泛型。泛型是引用类型,不是基本类型。
如果希望向集合 ArrayList 当中存储基本类型数据,必须使用基本类型对应的 “包装类”。包装类都是引用类型,位于 java.lang
包下。
1 | #基本类型# #包装类型# |
要注意 Integer 转换为 int 时可能会出现的空指针问题。
1 | Integer a = null; |
==自动拆/装箱==
Java 从 JDK 1.5 开始,支持自动装箱、自动拆箱。
自动装箱:基本类型 -> 包装类型(引用类型)
自动拆箱:包装类型 -> 基本类型
==ArrayList 的使用==
对于 ArrayList 集合来说,直接打印得到的不是地址值,而是内容。 如果内容是空,得到的则是空的中括号[]
。
1 | import java.util.ArrayList; |
==ArrayList 当中的常用方法==
public boolean add(E e)
向集合当中添加元素,参数 e 的类型和泛型一致。返回值代表添加是否成功。对于 ArrayList 集合来说,add 一定是成功的,所以返回值可用可不用。 但是对于其他集合来说,add 不一定成功。
public E get(int index)
从集合当中获取元素,参数是索引编号,返回值就是对应位置的元素。
public E remove(int index)
从集合当中删除元素,参数是索引编号,返回值就是被删除掉的元素。
public int size()
获取集合的尺寸长度,返回值是集合中包含的元素个数。
9.3、Math
java.util.Math
类是数学相关的工具类,里面提供了大量的静态方法,完成与数学运算相关的操作。
1 | public static double abs(double num) // 获取绝对值,有多种重载 |
9.4、Arrays
java.util.Arrays
是一个与数组相关的工具类,里面提供了大量静态方法,用来实现数组常见的操作。
public static String toString(int[] arr)
:将参数数组变成字符串,默认格式:[元素1, 元素2, 元素3...]
1
2
3int[] arr = {10,20,30};
String str = Arrays.toString(arr);
System.out.println(str); // [10, 20, 30]public static void sort(int[] arr)
:按照默认升序(从小到大)对数组的元素进行排序。如果是数值,sort 默认按照升序从小到大;
如果是字符串,sort 默认按照字母升序;
如果是自定义的类型,那么这个自定义的类需要有 Comparable 或者 Comparator 接口的支持。
1
2
3
4
5
6
7int[] arr1 = {2, 5, 10, 4, 1};
Arrays.sort(arr1);
System.out.println(Arrays.toString(arr1)); // [1, 2, 4, 5, 10]
String[] arr2 = {"bbb", "aaa", "ccc"};
Arrays.sort(arr2);
System.out.println(Arrays.toString(arr2)); // [aaa, bbb, ccc]
10、泛型
https://blog.csdn.net/weixin_42071874/article/details/82993262
方法的泛型优先级高于类的泛型
1 | public class TDemo { |
11、String…
String...
是 Java5 新加入的功能,表示一个可变长度的参数列表。其语法就是类型后跟 …
,表示此处接受的参数为 0 到多个 Object 类型的对象,或者是一个 Object 数组。
例如有一个方法叫做 test(String…strings)
,那么还可以增加方法 test()
,但是不能写 test(String[] strings)
(实际上跟前者是一样的),这样会造成编译错误,系统提示出现重复的方法。在使用的时候,对于 test(String… strings)
,可以用以下几种方式 去调用:
1 | test(); // 表示没有参数 |
tips:
如果既有
test(String… strings)
,又有test()
,在调用test()
时会优先使用test()
;只有当没有
test()
函数时调用test()
,程序才会走test(String… strings)
。
-------------Thanks for your time.-------------