JavaSE 进阶 (4) API

API-基本使用

什么是 API?

  • API (Application Programming interface)应用程序接口
  • 简单来说:就是 java 帮我们已经写好的一些方法,我们直接拿过来用就可以了。

API-Math

没有构造方法,但是方法都是 static 修饰,所以可以用 类名.方法名 调用

概述: 该类包含了数学运算有关的功能

功能:

  1. public static int abs(int a) //求绝对值
  2. public static double ceil(double a) //向上取整,天花板函数,求比a大的"最小整数"的double形式.
  3. public static double floor(double a) //向下取整,地板函数,求比a小的"最大整数"的double形式.
  4. public static int round(float a) //四舍五入
  5. public static int max(int a,int b) //求较大值
  6. public static int min(int a,int b) //求较小值
  7. public static double pow(double a,double b) //求a的b次幂
  8. public static double random() //返回值为double的正值,[0.0,1.0)

API-Random

概述:Random 是用来产生随机数的类。

1
2
3
4
5
6
7
8
//1.导包->放在类的上边
import java.util.Random;

//2.创建对象->放在main方法中
Random r = new Random();

//3.产生随机数->放在创建对象的后边(随机范围是"0-数字"之间,包含0,不包含"数字")
int num = r.nextInt(数字)
1
2
3
4
5
6
7
//假设 x < y

//产生x->y之间的随机数,包含x,不包含y
int num = r.nextInt(y-x)+x;  //[0, y-x) -> [0,y)

//产生x->y之间的随机数,包含x,包含y
int num = r.nextInt(y-x+1)+x;  //[0, y-x+1) -> [0, y+1)

API-System

不能被实例化,因为没有构造方法

概述: 该类包含了一些和JDK系统有关的方法.

功能:

  1. public static void exit(int status) //终止当前运行的Java虚拟机,参数0表示正常停止,非零表示异常终止
  2. public static long currentTimeMillis() //返回当前系统时间(以毫秒为单位)
  3. public static void arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数) //数组copy
  4. public static getProperty(String str) //获取系统属性

Object

Object 类的概述:每个类都可以将 Object 作为父类。所有类都直接或者间接的继承自该类,也都具备 Object 类的所有特性。因此,我们非常有必要掌握 Object 类的用法。

构造方法:public Object()

回想面向对象中,为什么说子类的构造方法默认访问的是父类的无参构造方法?

  • 因为它们的顶级父类只有无参构造方法

方法名 说明
public String toString() 返回对象的字符串表示形式。建议所有子类重写该方法,自动生成
public boolean equals(另一个对象) 比较对象是否相等。默认比较地址,重写该方法,可以比较内容,自动生成

toString

1
2
3
4
5
6
public class Demo {
  public static void main(String[] args) {
    student s = new Student("张三"23);
    System.out.println(s);  //com.itheima.demo1.Student@3f3afe78
  }
}
/images/java/JavaSE 进阶 (4) API/1.png
(图1)
  1. Object 类是所有类的直接或间接父类
  2. 在打印对象的过程中,我们通过流程图发现有一句是 obj.toString(),这个 toString() 方法调用的是该对象继承自 ObjecttoString() 方法,并将得到的返回值打印
  3. Object 类的 toString 方法打印的就是对象的地址值
  4. 我们一般会 toString 方法进行重写 (也就是对继承的 toString() 方法进行重写)

因为 Student 类默认继承 Object,所以流程图中的 obj.toString() 调用的是 Object 中的方法

Object 中的 toString() 方法打印的是 类名 + @ + 16进制的hashCode

/images/java/JavaSE 进阶 (4) API/2.png
(图2)

Object 中的 toString() 方法不满足我们的要求,所以我们需要重写 toString() 方法

1
2
3
4
5
6
7
@Override
public string tostring() {
	return "Student{" +
			"name='" + name + '\'' +
			", age=" + age +
			'}';
}

综上所述,我们可以得出以下结论

  1. Sout(Arraylist) 中重写了 toString() 方法,所以打印出来的不是地址值,而是数组中的值
  2. Sout(String) 中里面重写了 toString() 方法,所以打印出来的不是地址值,而是字符串的值

对于内存地址的分析

1
2
3
4
5
[I@10f87f48:
	[:当前的空间是一个数组类型
	@: 分隔符
	I: 当前数组容器中所存储的数据类型
	18f87f48:片进制内存地址

equals

1
2
3
4
5
6
7
8
public static void main(String[] args) {
	Student s1 = new Student("zhangsan", 23);
	Student s2 = new Student("zhangsan", 23);
	
	System.out.println(s1 == s2);//false
	//object类中的equals方法,底层也是用==号比较地址值。
	System.out.println(s1.equals(s2));//false
}

==:如果是基本类型,则表示值相等,如果是引用类型,则表示地址相等即是同一个对象。

equals() 方法是 s1 调用的,所以我们去 s1 中找 equals() 方法,没找到,说明 equals() 方法是继承自 Object 的,我们发现 Object 中的 equals() 方法用的是 == 比较的,所以比较的也是地址值

/images/java/JavaSE 进阶 (4) API/3.png
(图3)

父类中的 equals() 方法不能满足我们的需求了,所以我们需要重写 equals() 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@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;
}

一般重写 equals() 方法的时候,也要重写 hashCode() 方法

Object问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class InterviewTest {
	public static void main(String[] args) {
		String s1 = "abc";
		StringBuilder sb = new StringBuilder("abc");
		//1.此时调用的是string类中的equals方法。
		//保证参数也是字符串,否则不会比较属性值而直接返回false,sb不是字符串,直接返回false
		//system.out.println(s1.equals(sb));

		//StringBuilder类中是没有重写equals方法,用的就是object类中的equals方法
		//object类中的equals方法:基本数据类型比较数据值,引用数据类型比较地址值,地址值不同,返回false
		system.out.println(sb.equals(s1)); //false
	}
}

我们看 String 类中 equals() 方法的源码,他会先判断传入的参数是否是字符串,不是直接返回 false

/images/java/JavaSE 进阶 (4) API/4.png
(图4)

我们继续看 StringBuilder 中的 equals() 方法,我们发现 StringBuilder 中是没有 equals() 方法的,说明他是继承自 Objectequals() 方法,而 Object 中的 equals() 就是用 == 比较的,而 StringstringBuilder 不是基本数据类型,比较的就是地址值

API-Objects

Objects:是一个操作对象的工具类,没有构造方法

方法名 说明
public static String toString(对象) 返回参数中对象的字符串表示形式
public static String toString(对象, 默认字符串) 对象有值,返回对象的字符串表示形式
对象为null,返回默认字符串
public static Boolean isNull(对象) 判断对象是否为空
public static Boolean nonNull(对象) 判断对象是否不为空
public static T requireNonNull(T obj) 检查指定的对象引用不是null

objects 中的 toString() 方法实际上调用的就是对象中的 toString() 方法,和 System.out.println(s) 没有区别,因为 System.out.println(s) 调用的也是对象中的 toString() 方法,具体原理看上面 toString 章节的原理图

 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 Student {
	......
	
	@Override
	public string tostring(){
		System.out.println("看看我执行了吗?")
		return "Student{"+
			"name='" + name +'\''+
			", age=" + age +
			'}';
	}
}
--------------------------------------------------
public class MyObjectsDemo {
	public static void main(String[] args) {
		Student s = new Student("小罗同学"50);
		String result = Objects.toString(s);
		System.out.println(result);
		/**
		 * 看看我执行了吗?
		 * Student{name='小罗同学',age=50}
		 */
		
		System.out.println(s);
		/**
		 * 看看我执行了吗?
		 * Student{name='小罗同学',age=50}
		 */
	}
}

问: 那么 objects.toString() 有什么用呢? 我直接调用 System.out.println(s) 不就行了吗?

答: 他真正厉害的方法是 toString(对象,默认字符串)


toString(对象,默认字符串)

如果对象为空,那么返回第二个参数

我们查看它的源码

/images/java/JavaSE 进阶 (4) API/5.png
(图5)

Objects.requireNonNull

用于参数有效性检查

1
2
3
4
5
6
7
8
9
public class Foo {
	private List<Bar> bars;
	
  public Foo(List<Bar> bars) {
		// 在此对入参进行校验
    	Objects.requireNonNull(bars, "bars must not be null");
	    this.bars = bars;
	}
}

在构造方法里面使用 Objects.requireNonNull 能让我们尽早知道错误,从而达到 fail-fast 的设计目的。

Student s = Objects.requireNonNull(student, "不能为空");

Objects.requireNonNull() 判断对象是否为空,对象为 null 抛出异常,异常信息(message)为: 不能为空,不为 null 返回本身

1
2
3
Exception in thread "main" java.lang.NullPointerException: 不能为空
    at java.base/java.util.Objects.requireNonNull(Objects.java:228)
    at com.example.Main.main(Main.java:5)

API-Collections

Collections 工具类中也有排序的方法,collections.sort(),如果不想使用 collections.sort() 的默认排序规则,可以使用自己写的比较器排序

/images/java/JavaSE 进阶 (4) API/6.png
(图6)
补充

  Collections.emptyList() 方法说明:返回一个空的集合,调用者不需要再做非空判断,返回的是一个不可变并且长度为 0 的静态内部类,可以减少内存开销,但是返回的空集合,不可以执行增删操作。

  EmptyList 继承 AbstartList,而在 AbstartList 中并没有具体实现 add() 以及 remove() 方法,所以无法进行增删操作。

BigDecimal-构造

BigDecimal类 (用于下小数的高精度计算)

1
2
3
4
5
public class MyBigDecimalDemo {
	public static void main(string[] args) {
		System.out.println(10.0 / 3.0);  //3.3333333333333335
	}
}

/images/java/JavaSE 进阶 (4) API/7.png
(图7)

为什么 10.0 / 3.0 = 3.33333335? 不应该是 3.333… 吗,这是因为他是先从十进制转成二进制,才进行计算,计算完后又转成十进制展示

整数没有问题,小数在转为二进制的过程中由于计算机的存储机制会出现丢失数据的情况,这个时候会使用一个无限接近于这个数的二进制来表示这个小数,虽然误差很小,但是在金融、证券等领域,这是不行的,此时,我们就要用 BigDecimal 类的构造方法


BigDecimal类的构造方法

方法名 说明
BigDecimal(double val) 参数为double
BigDecimal(String val) 参数为String
1
2
3
4
5
6
7
8
public class MyBigDecimalDemo2 {
	public static void main(String[] args) {
		BigDecimal bd1 = new BigDecimal(10.0);
		BigDecimal bd2 = new BigDecimal("0.3");
		System.out.println(bd1);  //10
		System.out.println(bd2);  //0.3
	}
}

BigDecimal-四则运算

BigDecimal类的常用方法

作用:可以用来精确计算

方法名 说明
public BigDecimal add(另一个BigDecimal对象) 加法
public BigDecimal subtract(另一个Big Decimaly对像) 减法
public BigDecimal multiply(另一个BigDecimaly对像) 乘法
public BigDecimal divide(另一个BigDecimaly对象) 除法
public BigDecimal divide(另一个BigDecimal对象,精确几位,舍入模式) 除法
public BigDecimal setScale(精确几位,舍入模式) 保留几位精度
BigDecimal.ROUND_UP 舍入模式 -> 进一法
BigDecimal.ROUND_FLOOR 舍入模式 -> 去尾法
BigDecimal.ROUND_HALF_UP 舍入模式 -> 四舍五入

如果想要进行精确运算,那么请使用字符串的构造

/images/java/JavaSE 进阶 (4) API/8.png
(图8)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public static void main(String[] args) {
	BigDecimal bd1 = new BigDecimal("0.1");
	BigDecimal bd2 = new BigDecimal("0.2");
	BigDecimal add = bd1.add(bd2);

	System.out.println(add);  //0.3
	System.out.println(0.1 + 0.2);  //0.30000000000000004
	
	//divide
	double v = BigDecimal.valueOf(100).divide(BigDecimal.valueOf(3), 1, BigDecimal.ROUND_HALF_UP).doubleValue();
	
	//setScale
	BigDecimal bigDecimal = BigDecimal.valueOf(100).setScale(1, BigDecimal.ROUND_HALF_UP);
}

结论:

  1. BigDecimal 类是用来进行精确计算的
  2. 创建 BigDecimal 的对象,构造方法使用参数类型为字符串的
  3. 四则运算中的除法,如果除不尽请使用 divide 的三个参数的方法
提示
  1. BigDecimal 定义一个数,如果要对自己这个数本身进行保留小数操作,用 setScale(),当然也可以用 divide() 这个方法除以 1,来决定保留几位小数。
  2. BigInterger:只是范围更大,比 long 大, 用法与 BigDecimal 一样

public BigDecimal setScale(精确几位)

  • 一个参数,位数少的话补全,等于的话直接使用,多的话报错
  • 例如: 0.657,如果用 setscale(),且 setcalse(2),保留两位小数,此时,0.657 是三位数大于2报错

基本数据类型包装类

包装类引入

  int 的范围,我们有时候会记不住,如果我们能定义一个类,在这个类里能记录他们的值,每次用的时候,我们直接拿过来用就好了,不需要记,不需要背,java 给我们提供了一个类 Integer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class MyIntegerDemo1 {
	public static void main(String[] args){
		//需求:我要判断一个整数是否在int范围内?
		//Integer
		System.out.println(Integer.MAX_VALUE);  //2147483647
		System.out.println(Integer.MIN_VALUE);  //-2147483648
		
		//Byte、Short、Integer、Long、Float、Double 都有 MAX_VALUE 和 MIN_VALUE 方法,
	}
}
/images/java/JavaSE 进阶 (4) API/9.png
(图9)

Number 类是抽象类,因此它的抽象方法,所有子类都需要提供实现。Number 类提供了抽象方法:intValue()longValue()floatValue()doubleValue(),意味着所有的 数字型 包装类都可以互相转型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class TestWraperclass {
	public static void main(String[] args) {
		//基本数据类型转为对象
		Integer i = new Integer(300);
		Integer i2 = Integer.valueof(20);
		
		//包装类对象转为基本数据类型
		double d = i2.doubleValue();
		
		//将字符串数字转成包装类对象
		Integer i3 = Integer.valueof("234");
		Integer i4 = Integer.parseInt("334");
		
		//将包装类对象转成字符串
		String str = i3.toString();
	}
}
1
2
3
4
5
ArrayList<Integer> List = new ArrayList();
List.add(10);
List.add(10);
List.add(10);
List.add(10);

基本类型包装类概述

将基本数据类型封装成对象的好处在于可以在对象中定义更多的功能方法操作该数据

常用的操作之一: 用于基本数据类型与字符串之间的转换

“123” → 123

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

Integer获得对象

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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyIntegerDemo2 {
	public static void main(String[] args) {

		//public Integer(int value) 根据int创建Integer对象(过时)
		//public Integer(string s)  根据string值创建Integer对象(过时)
		Integer i1 = new Integer(100);
		Integer i2 = new Integer("100");
		System.out.println(i1);
		System.out.println(i2);
		
		

		//public static Integer valueof (int i)    返回表示指定的int值的Integer实例

		//public static Integer valueof(String s)  返回一个保存指定值的Integer对象String
		Integer i3 = Integer.valueof(200);
		Integer i4 = Integer.valueof("200");
		System.out.println(i3);

		system.out.println(i4);
	}
}

Integer-自动装箱和自动拆箱

  • 装箱:把 基本数据类型 变为 `与之对应的包装类``
  • 自动装箱:指的就是自动发生的装箱操作 -> JDK5 ,Integer i = 5; 编译器帮你改成:Integer i = Integer.value0f(5);
  • 拆箱:把 包装类 变为 与之对应的基本数据类型
  • 自动拆箱:指的就是自动发生的拆箱现象 -> JDK5,int b = a; 编译器帮你改成:int b = a.intValue();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class MyIntegerDemo3 {
	public static void main(String[] args) {
		Integer i1 = 100;
		//对象 = 默认是一个基本数据类型
		//jdk1.5的特性 -- 自动装箱

		//装箱:把一个基本数据类型变量对应的包装类,
		//自动:Java底层会帮我们自动的调用valueof方法,
		System.out.println(i1);

		//jdk1.5的特性 -- 自动拆箱
		//拆箱:把一个包装类型变成对应的基本数据类型
		int i2 = i1;
		System.out.println(i2);
	}
}

/images/java/JavaSE 进阶 (4) API/10.png
(图10)
/images/java/JavaSE 进阶 (4) API/11.png
(图11)
1
2
3
4
5
6
Integer c = null;

//编译器帮你改成:c.intValue();
int c2 = c;

//java.1ang.NullPointerException: 对象为null我们调用了他的属性或方法c是空的.c.intValue()调不了方法

注意:在使用包装类类型的时候,如果做操作,最好先判断是否为 null

推荐:只要是对象,在使用前就必须进行不为 null 的判断

Integer-类型转换

Integer的成员方法

方法名 说明
static int parselnt(String s) 将字符串类型的整数变成int类型的整数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class MyIntegerDemo4 {
	public static void main(String[] args) {
		String s1 = "100";
		int i1 = 200;
		System.out.println(s1 + i1); //100200 -- 字符串+任意的数据类型结果都是一个字符串

		int i2 = Integer.parseInt(s1); //可以将字符串类型的整数变成int类型的整数
		System.out.println(i2 + i1);  //300

		//int --> string
		//方式一:+ ""
		int i3 = 100;
		String s2 = i3 + "";
		System.out.println(s2 + 100);  //100100

		//方式二:可以调用string类中valueof方法
		String s3 = String.valueof(i3);
		System.out.println(s3 + 100);  //100100
	}
}

Int和String的相互转换

基本类型包装类的最常见操作就是:用于 基本类型字符串 之间的相互转换

  1. int 转换为 String
      方式一:加双引号即可
      方式二:public static String valueOf(int i):返回 int 参数的字符串表示形式。该方法是 String 类中的方法,String.valueOf(int i)

  2. String 转换为 int
      public static int parseInt(String s):将字符串解析为 int 类型。该方法是 Integer 类中的方法,Integer.parseInt(String s)

包装类的缓存问题

/images/java/JavaSE 进阶 (4) API/12.png
(图12)

当数字在 [-128, 127] 之间的时候,返回缓存数组中的某个元素,d3d4 都是从缓存数组中取得元素,所以取得是同一个对象

我们知道自动装箱实际上是调用的 Integer.valueOf(); 我们看 Integer.valueOf(); 的源码

/images/java/JavaSE 进阶 (4) API/13.png
(图13)

我们可以知道,4000 不在 [-128,127] 的范围内,所以最后执行的是 new Integer(i),所以 d1d2 是两个不同的对象


Integer.valueOf()和Integer.parseInt()的区别

在日常开发工作中,我们经常需要将一个字符串值转换成整型值,这个时候我们可以选择 Integer.valueOfInteger.parseInt 两个选择


接下来我们分析下这两个方法的区别

Integer.valueOf 返回值是 Integer 类型,Integer.parseInt 返回值是 int 类型

而且,在 Integer 类中还有一个内部的缓存类 IntegerCache,它默认缓存了 [-128, 127]Integer

Integer.parseInt 很简单,它是将一个字符串转成 10 进制的 int 数值.

Integer.valueOf 流程比较复杂,下面我们看一张流转图

/images/java/JavaSE 进阶 (4) API/14.png
(图14)

步骤:

  1. 使用 Integer.parseInt 方法将 Integer.valueOf`` 的字符串类型参数转成 int` 类型
  2. 判断参数值是否在 [-128, 127] 范围之间,如果在范围内则直接从 IntegerCache 类中取值
  3. 如果不在范围内则直接创建一个新的对象

Integer练习

案例:字符串中数据的处理

需求:有一个字符串:91 27 46 38 50,把其中的每一个数存到 int 类型的数组中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MyIntegerDemo5 {
	//需求:有一个字符串:“91 27 46 38 50”,把其中的每一个数存到int类型的数组中
	//步骤:
	//定义一个字符串
	//把字符串中的数字数据存储到一个int类型的数组中
	//遍历数组输出结果
	public static void main(String[] args) {
		String s = "91 27 46 38 50";
		//获取字符串中的每一个数字,
		String[] strArr = s.split(" ");
		
		//创建一个int类型的数组。
		int[] numberArr = new int[strArr.length];

		//把strArr中的数据进行类型转换并存入到int数组中
		for (int i = 0; i < strArr.length; i++){
			int number = Integer.parseInt(strArr[i]);
			numberArr[i]=number;
		}
	
		//遍历int类型的数组
		for (int i = 0; i < numberArr.length; i++){
			System.out.println(numberArr[i]);
		}
	}
}

递归-概念

递归的基本思想是 自己调用自己,一个使用递归技术的方法将会直接或间接的调用自己,通过 a() 方法可以调用 b(),那么是不是可以通过 a() 方法调用自己本身

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class MyFactorialDemo2 {
	public static void main(String[] args) {
		int sum = getsum(100);
		System.out.println(sum);
	}


	private static int getsum(int i) {
		//方法的作用:求1-100之间和
			// 100 + (1-99之间和)
				//...
					//1
		if(i == 1){
			return 1;
		}else{
			return i + getsum(i-1);
		}
	}
}

递归解决问题的思路:
把一个复杂的问题层层转化为一个 与原问题相似的规模较划小 的问题来求解
递归策略只需 少量的程序 就可描述出解题过程所需要的多次重复计算


递归解决问题要找到两个内容

  • 递归出口:否则会出现内存溢出
  • 递归规则:与原问题相似的规模较小的问题

自己调自己会无限循环把栈占满,使栈溢出,所以要给自己调自己的时候,设置一个条件,这样就是一个完整的递归了

/images/java/JavaSE 进阶 (4) API/15.png
(图15)

利用递归可以解决一些复杂的问题,例如,斐波那契数列,汉诺塔,快排等问题。

递归的缺陷:简单的程序是递归的优点之一。但是递归调用会占用大量的系统堆栈,内存耗用多,在递归调用层次多时速度要比循环慢的多,所以在使用递归时要谨慎。

递归会打开多个方法,打开方法本身是耗资源的。

递归-求阶乘代码实现

案例:递归求阶乘

需求:用递归求 5 的阶乘,并把结果在控制台输出

1
2
3
4
5
6
7
public static long factorial(int num) {
        if(num == 1){//递归头
            return 1;
        }else{//递归体
            return num * factorial(num - 1);  //n * (n - 1)
        }
}
/images/java/JavaSE 进阶 (4) API/16.png
(图16)

循环求阶乘

1
2
3
4
5
6
7
int x = 10;
int sum = 1;
while(x > 1){
	sum *= x * (x - 1);
	x -=2;
}
System.out.println(sum);

System.out.println() 方法中,%d%s 是格式化字符串中的占位符,用于指定输出的数据类型和格式。

  • %d:用于输出整数(int)类型的值。
  • %s:用于输出字符串(String)类型的值。
  • %n:换行
1
2
3
4
5
6
7
8
public class Example {  
    public static void main(String[] args) {  
        int age = 25;  
        String name = "John";  
          
        System.out.println("年龄是%d岁,姓名是%s。", age, name);  //年龄是25岁,姓名是John。
    }  
}

递归-求斐波那契数列

斐波那契数列指的是这样的一个数列:112358132134……,这个数列从第 3 项开始,每一项都等于前面两项之和。在数学上,斐波那契数列可以被递推的方法定义如下:

F(1)=1,F(2)=1,F(n)=F(n - 1)+F(n - 2)(n ≥ 3,n ∈ N*)

我们很容易发现当 n=1 或者 n=2 时,是斐波那契数列的递归终止条件,基于斐波那契数列的递推定义,当斐波那契数列达到终止条件 n=1 或者 n=2 时,我们也很容易发现对应 F(1)=1F(2)=1

按照斐波那契数列的数学定义,F(n)=F(n - 1)+F(n - 2)(n ≥ 3,n ∈ N*),即当 n ≥ 3 时,斐波那契数列中这一项的值等于前面两项的值之和,这样便可以将求解一个比较大的斐波那契数列转化为求解较小数值的斐波那契数列值,这里面有重复逻辑可以递归复用。

例如,当我们求解斐波那契数列中的 F(5) 时,按照定义,我们有:

F(5) = F(4) + F(3) // 递归分解
= ( F(3) + F(2) ) + ( F(2)+F(1) ) // 递归求解
= [ ( F(2)+F(1) ) + 1 ] + ( 1+1 ) // 递归求解,遇到终止条件就求解
= [(1+1) +1 ]+(1+1) // 归并
= 3 + 2 // 归并
= 5 // 归并
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static long fs(long num) {
    if(num == 0 || num==1){
        return num;
    }else{
        return (fs(num - 1) + fs(num - 2));
    }
 }

public static void main(String[] args) {
    for(int count = 0;count <= 10;count ++){
		System.out.printf("斐波那契数列第%d个数为%d%n", count, fs(count));
	}
}

异常的体系结构和异常的分类

思考:当代码出现了问题,程序是如何体现的?

异常 就是用来描述代码中出现的问题


异常:就是程序出现了不正常的情况,程序在执行的过程中,出现的非正常的情况,最终会导致 JVM 的非正常终止

注意:语法错误不算在异常体系中


异常体系

/images/java/JavaSE 进阶 (4) API/17.png
(图17)

编译时异常和运行时异常

/images/java/JavaSE 进阶 (4) API/18.png
(图18)

虚拟机默认处理异常的方式

JVM的默认处理方案

如果程序出现了问题,我们没有做任何处理,最终 JVM 会做默认的处理。

  • 把异常的名称,异常原因及异常出现的位置等信息输出在了控制台
  • 程序停止执行
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static void main(string[] args) {
	//思考:控制台为什么会有这样的红色字体呢?是谁打印的?
	int[] arr = {1, 2, 3, 4, 5};
	System.out.printIn(arr[10]); //当代码出现了异常,那么就在这里创建了一个异常对象。
								//new ArrayIndexoutofBoundsException();
								//首先会看,程序中有没有自己处理异常的代码。
								//如果没有,交给本方法的调用者处理,
								//最终这个异常会交给虚拟机默认处理,
								//JVM默认处理异常做了哪几件事情:
								//1,将异常信息以红色字体展示在控制台上。
								//2,停止程序运行,--哪里出现了异常,那么程序就在哪里停止,下面的代码不执行
	System.out.println("嘿嘿嘿,我最帅")
}

throws声明异常

异常处理的方式

出现异常就要处理,但是 异常处理 并不是把异常干掉,而是 让异常发生后,其他程序能继续运行


异常处理方式-throws-声明异常(编译时异常)

格式:public 返回值类型 方法名(参数) throws 异常类型{ }

解析:告诉调用者,该方法有可能有 危险,常用来处理 编译时异常

注意:这个格式是写在方法的定义处,表示声明一个异常

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class ExceptionDemo6 {
	public static void main(string[] args) {
		method1();  //此时调用者也没有处理。还是会交给虚拟机处理。虚拟机会采取默认处理异常的方法
	}

	//告诉调用者,你调用我,有可能会出现这样的异常哦,
	//如果方法中没有出现异常,那么正常执行
	//如果方法中真的出现了异常,其实也是将这个异常交给了调用者处理,调用者是method1()
	private static void method1() throws NullPointerException{
		int[] arr = null;
		for(int i=0;i<arr.length;i++) {
			System.out.println(arr[i]);
		}
	}
}

声明异常的注意事项

  1. 如果声明的异常是一个运行时异常,那么声明的代码可以省略
1
2
3
4
5
6
private static void method1() /*throws NullPointerException*/ {
	int[] arr = null;
	for (int i = 0; i < arr.length; i++) {
		System.out.println(arr[i]);
	}
}

  1. 如果声明的异常是一个编译时期异常,那么声明的代码必须要手动写出
1
2
3
4
private static void method2() throws ParseException {
	SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
	sdf.parse("2048-10月10日");
}

编译时异常因为在编译时就会检查,所以必须要写在方法后面进行显示声明

运行时异常因为在运行时才会发生,所以在方法后面可以不写

throw抛出异常

抛出异常对象-throw-用于 “制造异常”

思考:以前出现了异常,虚拟机帮我们创建一个异常对象,抛给调用者。但是如果我们需要自己手动创建一个异常对象该如何写?

格式:throw new 异常();

注意:这个格式是在方法内的,表示当前代码手动抛出一个异常,下面的代码不用再执行了

1
2
3
4
5
6
7
8
9
public void setAge(int age) {
	if (age > 0 & age < 120) {
		this.age =age;
	}else{
		//System.out.println("年龄错误!!!");
		RuntimeException re = new RuntimeException("年龄异常");
		throw re;
	}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class ExceptionDemo7 {
	public static void main(String[] args) {
		System.out.println("家里有一个貌美如花的老婆");
		System.out.println("还有一个当官的兄弟");
		System..out.println("自己还有一个买卖");
		System,out.println("这样的生活你要不要?");
		throw new RuntimeException();//当代码执行到这里,就创建一个异常对象
									//该异常创建之后,暂时没有手动处理。抛给了调用者处理
									//下面的代码不会再执行了
		System.out.println("武太郎的标准生活");
	}
}

throws和throw的区别

throws

  • 用在方法声明后面,跟的是异常类名
  • 表示声明异常,调用该方法有可能会出现这样的异常

throw

  • 用在方法体内,跟的是异常对象名
  • 表示手动抛出异常对象,由方法体内的语句处理

抛出处理异常的意义

  1. 在方法中,当传递的参数有误,没有继续运行下去的意义了,则采取抛出处理,表示让该方法结束运行。
  2. 告诉调用者方法中出现了问题。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class ExceptionDemo8 {
	public static void main(String[] args) {
		int[] arr = null;
		printArr(arr);  //这里就会接收到一个异常,这里没有处理,就会继续抛给调用者虚拟机,虚拟机采用默认的处理方法
						//我们还需要自己处理一下异常。该怎么处理呢? try...catch
	}
	
	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]);
			}
		}
	}
	
}

try…catch自己处理异常

异常处理方式-try…catch…(运行时处理异常)

1
2
3
4
5
try {
	//可能出现异常的代码;
} catch (异常类名 变量名) {
	// 异常的处理代码;
}
格式:
	try {
		//有可能出现异常的代码
		//如果发生异常,则try中代码不再执行,会立即跳转到catch中
	} catch(异常类型 e) {
		//出现异常后,会自动执行这里
		//执行完毕后,继续执行catch后的代码
	}
意义:
	被"try-catch"捕获异常后,后续代码继续运行。

好处:可以让程序继续往下执行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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("嘿嘿嘿,我最帅!I!");
		}
	}
}

try…catch的常见问题

  1. 如果 try 中没有遇到问题,怎么执行?
    会把 try 中的代码全部执行完毕,不会执行 catch 里面的代码

  2. 如果 try 中遇到了问题,那么 try 下面的代码还会执行吗?
    那么直接跳转到对应的 catch 的语句中,try 下面的代码就不会再执行了,当 catch 里面的语句全部执行完毕,表示整个体系全部执行完毕,继续执行下面的代码

  3. 如果出现的问题没有被捕获,那么程序如何运行?
    那么 try…catch… 就相当于没有写,那么也就是自己没有处理,默认交给虚拟机处理

  4. 同时有可能出现多个异常怎么处理?
    出现多个异常,那么就写多个 catch 就行了


注意:如果多个异常之间存在子父类关系,那么父类一定要写在下面,否则前面的父类异常直接就把异常信息接收了,后面的子类异常就不会执行

以后,我们针对于每种不同的异常,有可能还会有不同的处理结果,如果异常都用 Exception 接收,那么就是一样的处理结果

throwable成员方法

异常的常见方法

方法名 说明
public String getMessage() 返回此throwable的详细消息字符串
public String toString() 返回此可抛出的简短描述
public void printStackTrace() 把异常的错误信息输出在控制台

GetMessagetoStringprintStackTrace 三种方法都是 Throwable 里的,Throwable 是所有异常的父类(见异常体系结构图),所以,所有的异常对象都可以调用这三个方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class ExceptionDemo11 {
	public static void main(String[] args) {
		try {
			int[] arr = {1, 2, 3, 4, 5};
			System.out.println(arr[10]);
			//在这里,虚拟机帮我们创建了一个异常对象 new ArrayIndexoutofBoundsException,然后将他赋值给了e
		} catch (ArrayIndexoutofBoundsException e) {
			//e.getMessage(): Index 10 out bounds for length 5
			System.out.println(e.getMessage());
			
			//e.toString(): java.lang.ArrayIndexOutOfBoundsException: Index 10 out bounds for length 5
			System.out.println(e.toString());
			e.printstackTrace();
		}
	}
}

当然,我们也可以 catch(Exception e),直接捕获最大的异常,Exception e = new ArrayIndexOutOfBoundsException( ); 父类引用指向子类对象,然后通过 e 调用异常方法

e.printstackTrace(); 打印的东西如下

/images/java/JavaSE 进阶 (4) API/19.png
(图19)

两种处理异常方式总结

抛出throw throws

  1. 在方法中,当传递的参数有误,没有继续运行下去的意义了,则采取抛出处理。表示让该方法结束运行。
  2. 告诉调用者出现了问题。

捕获try..catch

捕获:能让代码继续往下运行。

 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
public class Student {
	private Integer age;

	......
	
	public void setAge(int age) {
		if (age>=18 && age<=25) {
			this.age = age;
		} else {
			throw new RuntimeException("年龄超出了范围");
		}
	}
}

public class TestDemo {
	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 (RuntimeException e) {
				System.out.println("请输入一个符合范围的年龄");
				continue;
		}
		System.out.println(s);
	}
}

自定义异常

年龄超出范围,我们抛的是 RuntimeException,它下面还有空指针异常、索引越界异常,都是关于 RuntimeException 的子类,所以,我们直接抛一个 RuntimeException 给调用者,调用者会有点懵逼,现在到底出了什么异常,你给我这么大的一个异常,我哪知道是什么异常? 所以,这个时候我们就要学习自定义异常

自定义异常的目的:为了让异常信息更加的见名知意


自定义异常的步骤

/images/java/JavaSE 进阶 (4) API/20.png
(图20)
  1. 定义异常类 public class AgeoutofBoundsException
  2. 写继承关系 extends RuntimeException
  3. 空参构造
  4. 带参构造
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Student {
	private Integer age;
	
	......
	public void setAge(int age) {
		if (age>=18 && age<=25) {
			this.age = age;
		} else {
			//自定义异常的目的:为了让异常信息更加的见名知意
			//throw new runtimeException("年龄超出了范围"):
			hrow new AgeoutofBoundsException("年龄超出了范围")
		}
	}
	
}

public class TestDemo {
	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("请输入一个符合范围的年龄");
				continue;
		}
		System.out.println(s);
	}
}
/images/java/JavaSE 进阶 (4) API/21.png
(图21)

0%