final关键字

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y4clTw5g-1649075023636)(https://cdn.nlark.com/yuque/0/2022/png/2699848/1647265729124-ebd1307f-a001-4079-abd9-8b6aec1c0979.png)]

抽象类

接口

接口的基础语法

接口在开发中的作用

类型和类型之间的关系

抽象类和接口的区别

package和import机制

package


import



java.lang下的类不需要,但是java.lang中的子包还是需要导的。

访问控制权限

1、访问控制权限都有哪些?
有4个
private 私有
public 公开
protected 受保护
默认

2、以上的4个访问控制权限,控制的范围是什么?
private 表示私有的,只能在本类中访问。
public 表示公开的,在任何位置都可以访问。
“默认” 表示只能在本类,以及同包下访问。
protected 表示只能在本类、同包、子类中访问。

访问控制修饰符 本类 同包 子类 任意位置
public
protected ×
默认 × ×
private × × ×

范围从大到小排序:public > protected > 默认 > private

3、访问控制权限修饰符可以修饰什么?
属性(4个都能用)
方法(4个都能用)
类(public和默认能用,其他不行)
接口(public和默认能用,其他不行)

JDK类库的根类Object

这个老祖宗类中的方法我们需要研究一下,因为这些方法都是所有子类通用的。
任何一个类默认继承Object。就算没有直接继承,最终也会间接继承。

Object类当中有哪些常用的方法?
我们去哪里找这些方法呢?
第一种方法:去源代码当中。(但是这种方式比较麻烦,源代码也比较难)
第二种方法:去查阅java的类库的帮助文档。

什么是API?
应用程序编程接口。(Application Program Interface)
整个JDK的类库就是一个javase的API。
每一个API都会配置一套API帮助文档。
SUN公司提前写好的这套类库就是API。(一般每一份API都对应一份API帮助文档。)

需要知道几个方法:
protected Object clone() //负责对象克隆的。
int hashcode() //获取一个对象的哈希代码值
boolean equals(Object obj) //判断两个对象是否相等
String toString() //将对象转换成字符串形式
protected void finalize() //垃圾回收器负责调用的方法

toString()方法

1、源代码:
public String toString() {
return getClass().getName() + “@” + Integer.toHexString(hashCode());
}
源代码上toString()方法的默认实现是:
类名@对象的内存地址转换为十六进制的形式

2、SUN公司设计toString()方法的目的是什么?
toString()方法的设计目的是:通过调用这个方法可以将一个“java对象”转换成“字符串表示形式”

3、其实SUN公司开发java语言的时候,建议所有的子类都去重写toString()方法。
toString()方法应该是一个简洁的、详实的、易阅读的。

注:以后所有类的toString()方法是需要重写的。
System.out.println(引用);这里会自动调用“引用”的toString()方法。
String类是SUN写的,toString方法已经重写了。

equals()方法


idea重写:

finalize()方法

hasecode()方法

内部类


例:

class Test01{// 静态变量static String country;// 该类在类的内部,所以称为内部类// 由于前面有static,所以称为“静态内部类”static class Inner1{}// 实例变量int age;// 该类在类的内部,所以称为内部类// 没有static叫做实例内部类。class Inner2{}// 方法public void doSome(){// 局部变量int i = 100;// 该类在类的内部,所以称为内部类// 局部内部类。class Inner3{}}public void doOther(){// doSome()方法中的局部内部类Inner3,在doOther()中不能用。}// main方法,入口public static void main(String[] args){// 调用MyMath中的mySum方法。MyMath mm = new MyMath();/*Compute c = new ComputeImpl();mm.mySum(c, 100, 200);*///合并(这样写代码,表示这个类名是有的。类名是:ComputeImpl)//mm.mySum(new ComputeImpl(), 100, 200);// 使用匿名内部类,表示这个ComputeImpl这个类没名字了。// 这里表面看上去好像是接口可以直接new了,实际上并不是接口可以new了。// 后面的{} 代表了对接口的实现。// 不建议使用匿名内部类,为什么?// 因为一个类没有名字,没有办法重复使用。另外代码太乱,可读性太差。mm.mySum(new Compute(){public int sum(int a, int b){return a + b;}}, 200, 300);}
}// 负责计算的接口
interface Compute{ // 抽象方法int sum(int a, int b);
}// 你自动会在这里编写一个Compute接口的实现类
/*
class ComputeImpl implements Compute{// 对方法的实现public int sum(int a, int b){return a + b;}
}
*/// 数学类
class MyMath{// 数学求和方法public void mySum(Compute c, int x, int y){int retValue = c.sum(x, y);System.out.println(x + "+" + y + "=" + retValue);}
}

实例内部类

  • 创建实例内部类,外部类的实例必须已经创建
  • 实例内部类会持有外部类的引用
  • 实例内部不能定义static成员,只能定义实例成员

静态内部类

  • 静态内部类不会持有外部的类的引用,创建时可以不用创建外部类
  • 静态内部类可以访问外部的静态变量,如果访问外部类的成员变量必须通过外部类的实例访问

局部内部类

局部内部类是在方法中定义的,它只能在当前方法中使用。和局部变量的作用一样。
局部内部类和实例内部类一致,不能包含静态成员。

数组

1、Java语言中的数组是一种**引用数据类型**。不属于基本数据类型。数组的父类是Object。
2、数组实际上是一个容器,可以同时容纳多个元素。(数组是一个数据的集合。)
数组:字面意思是“一组数据”
3、数组当中可以存储“基本数据类型”的数据,也可以存储“引用数据类型”的数据。
4、数组因为是引用类型,所以数组对象是堆内存当中。(数组是存储在堆当中的)
5、数组当中如果存储的是“java对象”的话,实际上存储的是对象的“引用(内存地址)”,数组中不能直接存储java对象。
6、数组一旦创建,在java中规定,**长度不可变**。(数组长度不可变)
7、数组的分类:一维数组、二维数组、三维数组、多维数组...(一维数组较多,二维数组偶尔使用!)
8、所有的数组对象都有length属性(java自带的),用来获取数组中元素的个数。
9、java中的数组要求数组中元素的类型统一。比如int类型数组只能存储int类型,Person类型数组只能存储Person类型。
例如:超市购物,购物袋中只能装苹果,不能同时装苹果和橘子。(数组中存储的元素类型统一)
10、数组在内存方面存储的时候,数组中的元素内存地址(存储的每一个元素都是有规则的挨着排列的)是**连续**的。**内存地址连续**。
这是数组存储元素的特点(特色)。数组实际上是一种简单的数据结构。
11、所有的数组都是拿“第一个小方框的内存地址”作为整个数组对象的内存地址。
(数组中**首元素的内存地址作为整个数组对象的内存地址**。)
12、数组中每一个元素都是有下标的,下标从0开始,以1递增。最后一个元素的下标是:length - 1
下标非常重要,因为我们对数组中元素进行“存取”的时候,都需要通过下标来进行。

数组这种数据结构的优缺点

13、数组这种数据结构的优点和缺点是什么?**优点**:查询/查找/检索某个下标上的元素时效率极高。可以说是**查询效率最高的一个数据结构**。为什么检索效率高?第一:每一个元素的内存地址在空间存储上是连续的。第二:每一个元素类型相同,所以占用空间大小一样。第三:知道第一个元素内存地址,知道每一个元素占用空间的大小,又知道下标,所以通过一个数学表达式就可以计算出某个下标上元素的内存地址。直接通过内存地址定位元素,所以数组的检索效率是最高的。数组中存储100个元素,或者存储100万个元素,在元素查询/检索方面,效率是相同的,因为数组中元素查找的时候不会一个一个找,是通过数学表达式计算出来的。(算出一个内存地址,直接定位的。)**缺点**:第一:由于为了保证数组中每个元素的内存地址连续,所以在数组上随机删除或者增加元素的时候,效率较低,因为随机增删元素会涉及到后面元素统一向前或者向后位移的操作。第二:数组**不能存储大数据量**,为什么?因为很难在内存空间上找到一块特别大的连续的内存空间。注意:对于数组中最后一个元素的增删,是没有效率影响的。

声明及初始化数组

怎么声明/定义一个一维数组?
1.数组元素的类型[] 变量名称
2.数组元素的类型 变量名称[] //这是C++风格,不建议java中使用。
语法格式:
int[] array1;
double[] array2;
boolean[] array3;
String[] array4;
Object[] array5;
在一行中也可以声明多个数组,例如:int[] a,b,c

怎么初始化一个一维数组呢?
包括两种方式:静态初始化一维数组,动态初始化一维数组。
静态初始化语法格式:
int[] array = {100, 2100, 300, 55};
动态初始化语法格式:
int[] array = new int[5]; // 这里的5表示数组的元素个数。
// 初始化一个5个长度的int类型数组,每个元素默认值0
String[] names = new String[6]; // 初始化6个长度的String类型数组,每个元素默认值null。

访问数组中元素及遍历数组

public class ArrayTest01 {public static void main(String[] args) {// 声明一个int类型的数组,使用静态初始化的方式int[] a = {1, 100, 10, 20, 55, 689};// 这是C++风格,不建议java中使用。//int a[] = {1, 100, 10, 20, 55, 689};// 所有的数组对象都有length属性System.out.println("数组中元素的个数" + a.length);// 数组中每一个元素都有下标// 通过下标对数组中的元素进行存和取。// 取(读)System.out.println("第一个元素 = " + a[0]);System.out.println("最后一个元素 = " + a[5]);System.out.println("最后一个元素 = " + a[a.length - 1]);// 存(改)// 把第一个元素修改为111a[0] = 111;// 把最后一个元素修改为0a[a.length - 1] = 0;System.out.println("第一个元素 = " + a[0]);System.out.println("最后一个元素 = " + a[5]);// 一维数组怎么遍历呢?for(int i = 0; i < a.length; i++){System.out.println(a[i]); // i是从0到5,是下标}// 下标为6表示第7个元素,第7个元素没有,下标越界了。会出现什么异常呢?//System.out.println(a[6]); //ArrayIndexOutOfBoundsException(比较著名的异常。)// 从最后一个元素遍历到第1个元素for (int i = a.length - 1; i >= 0; i--) {System.out.println("颠倒顺序输出-->" + a[i]);}}
}

动态初始化一维数组


什么时候采用静态初始化方式,什么时候使用动态初始化方式呢?
当你创建数组的时候,确定数组中存储哪些具体的元素时,采用静态初始化方式。
当你创建数组的时候,不确定将来数组中存储哪些数据,可以采用动态初始化的方式,预先分配内存空间。

public class ArrayTest02 {public static void main(String[] args) {// 声明/定义一个数组,采用动态初始化的方式创建int[] a = new int[4]; // 创建长度为4的int数组,数组中每个元素的默认值是0// 遍历数组for (int i = 0; i < a.length; i++) {System.out.println("数组中下标为" + i + "的元素是:" + a[i]);}// 后期赋值a[0] = 1;a[1] = 100;a[2] = 111;a[3] = 222; // 注意下标别越界。for (int i = 0; i < a.length; i++) {System.out.println("数组中下标为" + i + "的元素是:" + a[i]);}// 初始化一个Object类型的数组,采用动态初始化方式Object[] objs = new Object[3]; // 3个长度,动态初始化,所以每个元素默认值是nullfor (int i = 0; i < objs.length; i++) {System.out.println(objs[i]);}System.out.println("===============================");String[] strs = new String[3];for (int i = 0; i < strs.length; i++) {System.out.println(strs[i]);}// 采用静态初始化的方式String[] strs2 = {"abc", "def", "xyz"};for (int i = 0; i < strs2.length; i++) {System.out.println(strs2[i]);}// 存储Object,采用静态初始化呢?/*Object o1 = new Object();Object o2 = new Object();Object o3 = new Object();Object[] objects = {o1, o2, o3};*/Object[] objects = {new Object(), new Object(), new Object()};for (int i = 0; i < objects.length; i++) {/*Object o = objects[i];System.out.println(o);*/System.out.println(objects[i]);}}
}

方法的参数是数组

public class ArrayTest03 {// main方法的编写方式,还可以采用C++的语法格式哦!public static void main(String args[]) {// 调用方法时传一个数组int[] x = {1,2,3,4};printArray(x);// 创建String数组String[] stringArray = {"abc", "def", "hehe", "haha"};printArray(stringArray);String[] strArray = new String[10];printArray(strArray); // 10个nullSystem.out.println("================================");printArray(new String[3]);System.out.println("***********************************");printArray(new int[4]);}public static void printArray(int[] array){for(int i = 0; i < array.length; i++){System.out.println(array[i]);}}public static void printArray(String[] args){for(int i = 0; i < args.length; i++){System.out.println("String数组中的元素:" + args[i]);}}
}
public class ArrayTest04 {public static void main(String[] args) {// 静态初始化一维数组int[] a = {1,2,3};printArray(a);System.out.println("============================");// 没有这种语法。//printArray({1,2,3});// 如果直接传递一个静态数组的话,语法必须这样写。printArray(new int[]{1,2,3});// 动态初始化一维数组int[] a2 = new int[4];printArray(a2);System.out.println("=============================");printArray(new int[3]);}// 为什么要使用静态方法?方便呀,不需要new对象啊。public static void printArray(int[] array){for (int i = 0; i < array.length; i++) {System.out.println(array[i]);}}
}

main方法的String数组

main方法上面的“String[] args”有什么用?
分析一下:谁负责调用main方法(JVM)
JVM调用main方法的时候,会自动传一个String数组过来。

public class ArrayTest05 {// 这个方法程序员负责写出来,JVM负责调用。JVM调用的时候一定会传一个String数组过来。public static void main(String[] args) {// JVM默认传递过来的这个数组对象的长度?默认0// 通过测试得出:args不是null。System.out.println("JVM给传递过来的String数组参数,它这个数组的长度是?" + args.length);// 以下这一行代码表示的含义:数组对象创建了,但是数组中没有任何数据。//String[] strs = new String[0];//String[] strs = {}; // 静态初始化数组,里面没东西。//printLength(strs);// 这个数组什么时候里面会有值呢?// 其实这个数组是留给用户的,用户可以在控制台上输入参数,这个参数自动会被转换为“String[] args”// 例如这样运行程序:java ArrayTest05 abc def xyz// 那么这个时候JVM会自动将“abc def xyz”通过空格的方式进行分离,分离完成之后,自动放到“String[] args”数组当中。// 所以main方法上面的String[] args数组主要是用来接收用户输入参数的。// 把abc def xyz 转换成字符串数组:{"abc","def","xyz"}// 遍历数组for (int i = 0; i < args.length; i++) {System.out.println(args[i]);}}public static void printLength(String[] args){System.out.println(args.length); // 0}
}

数组中存储引用数据类型

对于数组来说,实际上只能存储java对象的“内存地址”。数组中存储的每个元素是“引用”。

public class ArrayTest07 {public static void main(String[] args) {// 创建一个Animal类型的数组Animal a1 = new Animal();Animal a2 = new Animal();Animal[] animals = {a1, a2};// 对Animal数组进行遍历for (int i = 0; i < animals.length; i++) {/*Animal a = animals[i];a.move();*/// 代码合并animals[i].move(); // 这个move()方法不是数组的。是数组当中Animal对象的move()方法。}// 动态初始化一个长度为2的Animal类型数组。Animal[] ans = new Animal[2];// 创建一个Animal对象,放到数组的第一个盒子中。ans[0] = new Animal();// Animal数组中只能存放Animal类型,不能存放Product类型。//ans[1] = new Product();// Animal数组中可以存放Cat类型的数据,因为Cat是一个Animal。// Cat是Animal的子类。ans[1] = new Cat();// 创建一个Animal类型的数组,数组当中存储Cat和BirdCat c = new Cat();Bird b = new Bird();Animal[] anis = {c, b};//Animal[] anis = {new Cat(), new Bird()}; // 该数组中存储了两个对象的内存地址。for (int i = 0; i < anis.length; i++){// 这个取出来的可能是Cat,也可能是Bird,不过肯定是一个Animal// 如果调用的方法是父类中存在的方法不需要向下转型。直接使用父类型引用调用即可。//anis[i]//Animal an = anis[i];//an.move();//Animal中没有sing()方法。//anis[i].sing();// 调用子对象特有方法的话,需要向下转型!!!if(anis[i] instanceof Cat){Cat cat = (Cat)anis[i];cat.catchMouse();}else if(anis[i] instanceof Bird){Bird bird = (Bird)anis[i];bird.sing();}}}
}class Animal{public void move(){System.out.println("Animal move...");}
}// 商品类
class Product{}// Cat是子类
class Cat extends Animal {public void move(){System.out.println("猫在走猫步!");}// 特有方法public void catchMouse(){System.out.println("猫抓老鼠!");}
}// Bird子类
class Bird extends Animal {public void move(){System.out.println("Bird Fly!!!");}// 特有的方法public void sing(){System.out.println("鸟儿在歌唱!!!");}
}

数组扩容

在java开发中,数组长度一旦确定不可变,那么数组满了怎么办?
数组满了,需要扩容。
java中对数组的扩容是:
先创建一个大容量的数组,然后将小容量数组中的数据一个一个拷贝到大数组当中。

public class ArrayTest08 {public static void main(String[] args) {// java中的数组是怎么进行拷贝的呢?//System.arraycopy(5个参数);// 拷贝源(从这个数组中拷贝)int[] src = {1, 11, 22, 3, 4};// 拷贝目标(拷贝到这个目标数组上)int[] dest = new int[20]; // 动态初始化一个长度为20的数组,每一个元素默认值0// 调用JDK System类中的arraycopy方法,来完成数组的拷贝//System.arraycopy(src, 1, dest, 3, 2);// 遍历目标数组/*for (int i = 0; i < dest.length; i++) {System.out.println(dest[i]); // 0 0 0 11 22 ... 0}*/System.arraycopy(src, 0, dest, 0, src.length);for (int i = 0; i < dest.length; i++) {System.out.println(dest[i]);}// 数组中如果存储的元素是引用,可以拷贝吗?当然可以。String[] strs = {"hello", "world!", "study", "java", "oracle", "mysql", "jdbc"};String[] newStrs = new String[20];System.arraycopy(strs, 0, newStrs, 0, strs.length);for (int i = 0; i < newStrs.length; i++) {System.out.println(newStrs[i]);}System.out.println("================================");Object[] objs = {new Object(), new Object(), new Object()};Object[] newObjs = new Object[5];// 思考一下:这里拷贝的时候是拷贝对象,还是拷贝对象的地址。(地址。)System.arraycopy(objs, 0, newObjs, 0, objs.length);for (int i = 0; i < newObjs.length; i++) {System.out.println(newObjs[i]);}}
}

结论:数组扩容效率较低。因为涉及到拷贝的问题。所以在以后的开发中请注意:尽可能少的进行数组的拷贝。可以在创建数组对象的时候预估多长合适,最好预估准确,这样可以减少数组的扩容次数,提高效率。

数组模拟栈数据结构

编写程序,使用一维数组,模拟栈数据结构。
要求:
1、这个栈可以存储java中的任何引用类型的数据。
2、在栈中提供push方法模拟压栈。(栈满了,要有提示信息。)
3、在栈中提供pop方法模拟弹栈。(栈空了,也有有提示信息。)
4、编写测试程序,new栈对象,调用push pop方法来模拟压栈弹栈的动作。
5、假设栈的默认初始化容量是10.(请注意无参数构造方法的编写方式。)

public class MyStack {// 向栈当中存储元素,我们这里使用一维数组模拟。存到栈中,就表示存储到数组中。// 因为数组是我们学习java的第一个容器。// 为什么选择Object类型数组?因为这个栈可以存储java中的任何引用类型的数据// new Animal()对象可以放进去,new Person()对象也可以放进去。因为Animal和Person的超级父类就是Object。// 包括String也可以存储进去。因为String父类也是Object。private Object[] elements;// 栈帧,永远指向栈顶部元素// 那么这个默认初始值应该是多少。注意:最初的栈是空的,一个元素都没有。//private int index = 0; // 如果index采用0,表示栈帧指向了顶部元素的上方。//private int index = -1; // 如果index采用-1,表示栈帧指向了顶部元素。private int index;/*** 无参数构造方法。默认初始化栈容量10.*/public MyStack() {// 一维数组动态初始化// 默认初始化容量是10.this.elements = new Object[10];// 给index初始化this.index = -1;}/*** 压栈的方法* @param obj 被压入的元素*/public void push(Object obj){if(index >= elements.length - 1){System.out.println("压栈失败,栈已满!");return;}// 程序能够走到这里,说明栈没满// 向栈中加1个元素,栈帧向上移动一个位置。index++;elements[index] = obj;// 在声明一次:所有的System.out.println()方法执行时,如果输出引用的话,自动调用引用的toString()方法。System.out.println("压栈" + obj + "元素成功,栈帧指向" + index);}/*** 弹栈的方法,从数组中往外取元素。每取出一个元素,栈帧向下移动一位。* @return*/public void pop(){if(index < 0){System.out.println("弹栈失败,栈已空!");return;}// 程序能够执行到此处说明栈没有空。System.out.print("弹栈" + elements[index] + "元素成功,");// 栈帧向下移动一位。index--;System.out.println("栈帧指向" + index);}// set和get也许用不上,但是你必须写上,这是规矩。你使用IDEA生成就行了。// 封装:第一步:属性私有化,第二步:对外提供set和get方法。public Object[] getElements() {return elements;}public void setElements(Object[] elements) {this.elements = elements;}public int getIndex() {return index;}public void setIndex(int index) {this.index = index;}
}

二维数组

1、二维数组其实是一个特殊的一维数组,特殊在这个一维数组当中的每一个元素是一个一维数组。
2、三维数组是什么?
三维数组是一个特殊的二维数组,特殊在这个二维数组中每一个元素是一个一维数组。
实际开发中使用最多的就是一维数组。二维数组也很少使用。三维数组几乎不用。
3、二维数组静态初始化
int[][] array = {{1,1,1},{2,3,4,5},{0,0,0,0},{2,3,4,5},{2,3,4,5},{2,3,4,5},{2,3,4,5}};

public class ArrayTest09 {public static void main(String[] args) {// 一维数组int[] array = {100, 200, 300};System.out.println(array.length); // 3System.out.println("=======================");// 二维数组// 以下代码当中:里面的是4个一维数组。int[][] a = {{100, 200, 300},{30, 20, 40, 50, 60},{6, 7, 9, 1},{0}};System.out.println(a.length); // 4System.out.println(a[0].length); // 3System.out.println(a[1].length); // 5System.out.println(a[2].length); // 4System.out.println(a[3].length); // 1// 里面的是5个一维数组。int[][] a2 = {{100, 200, 300},{30, 20, 40, 50, 60},{6, 7, 9, 1},{0},{1,2,3,4,5}};}
}

二维数组的元素访问

public class ArrayTest10 {public static void main(String[] args) {// 二维数组int[][] a = {{34,4,65},{100,200,3900,111},{0}};// 请取出以上二维数组中的第1个一维数组。int[] 我是第1个一维数组 = a[0];int 我是第1个一维数组中的第1个元素 = 我是第1个一维数组[0];System.out.println(我是第1个一维数组中的第1个元素);// 以下代码的由来是因为以上代码的合并导致的。System.out.println(a[0][0]);// 取出第2个一维数组当中第3个元素System.out.println("第二个一维数组中第三个元素:" + a[1][2]);// 取出第3个一维数组当中第1个元素System.out.println("第3个一维数组中第1个元素:" + a[2][0]);// 改a[2][0] = 11111;System.out.println(a[2][0]);// 注意别越界。//java.lang.ArrayIndexOutOfBoundsException//System.out.println(a[2][1]);}
}

遍历二维数组

public class ArrayTest11 {public static void main(String[] args) {// 二维数组String[][] array = {{"java", "oracle", "c++", "python", "c#"},{"张三", "李四", "王五"},{"lucy", "jack", "rose"}};// 遍历二维数组for(int i = 0; i < array.length; i++){ // 外层循环3次。(负责纵向。)String[] 一维数组 = array[i];// 负责遍历一维数组for(int j = 0; j < 一维数组.length; j++){System.out.print(一维数组[j] + " ");}// 输出换行符System.out.println();}// 合并代码for(int i = 0; i < array.length; i++){ // 外层循环3次。(负责纵向。)for(int j = 0; j < array[i].length; j++){System.out.print(array[i][j] + " ");}System.out.println();}}
}

方法的参数是二维数组

public class ArrayTest12 {public static void main(String[] args) {// 3行4列。// 3个一维数组,每一个一维数组当中4个元素。int[][] array = new int[3][4];// 二维数组遍历/*for (int i = 0; i < array.length; i++) { // 循环3次。for (int j = 0; j < array[i].length; j++) {System.out.print(array[i][j] + " ");}System.out.println();}*/// 静态初始化int[][] a = {{1,2,3,4},{4,5,6,76},{1,23,4}};printArray(a);// 没有这种语法//printArray({{1,2,3,4},{4,5,6,76},{1,23,4}});// 可以这样写。printArray(new int[][]{{1,2,3,4},{4,5,6,76},{1,23,4}});}public static void printArray(int[][] array){// 遍历二维数组。for (int i = 0; i < array.length; i++) {for (int j = 0; j < array[i].length; j++) {System.out.print(array[i][j] + " ");}System.out.println();}}
}

冒泡排序算法

1、每一次循环结束之后,都要找出最大的数据,放到参与比较的这堆数据的最右边。(冒出最大的那个气泡。)
2、核心:
拿着左边的数字和右边的数字比对,当左边 > 右边的时候,交换位置。
例:

public class BubbleSort {public static void main(String[] args) {// 这是int类型的数组对象//int[] arr = {3, 2, 7, 6, 8};int[] arr = {9, 8, 10, 7, 6, 0, 11};// 7条数据,循环6次。以下的代码可以循环6次。/*for(int i = 0; i < arr.length-1; i++){System.out.println(i);}*/// 7条数据,循环6次。以下的代码可以循环6次。(冒泡排序的外层循环采用这种方式)//int count = 0;int count2 = 0;for(int i = arr.length-1; i > 0; i--){for(int j = 0; j < i; j++){// 不管是否需要交换位置,总之是要比较一次的。//count++;if(arr[j] > arr[j+1]){// 交换位置。// arr[j] 和 arr[j+1] 交换int temp;temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;count2++;}}}//System.out.println("比较次数:" + count);System.out.println("交换位置的次数:" + count2); //13// 输出结果for (int i = 0; i < arr.length; i++) {System.out.println(arr[i]);}}
}

选择排序算法

1、每一次从这堆“参与比较的数据当中”找出最小值。
2、拿着这个最小值和“参与比较的这堆最前面的元素”交换位置。

选择排序比冒泡排序好在:每一次的交换位置都是有意义的。
选择排序比冒泡排序的效率高:高在交换位置的次数上。

public class SelectSort {public static void main(String[] args) {//int[] arr = {3, 1, 6, 2, 5};int[] arr = {9, 8, 10, 7, 6, 0, 11};int count = 0;int count2 = 0;// 选择排序// 5条数据循环4次。(外层循环4次。)for(int i = 0; i < arr.length - 1; i++){// i的值是0 1 2 3// i正好是“参加比较的这堆数据中”最左边那个元素的下标。//System.out.println(i);// i是一个参与比较的这堆数据中的起点下标。// 假设起点i下标位置上的元素是最小的。int min = i;for(int j = i+1; j < arr.length; j++){count++;//System.out.println("===>" + j);if(arr[j] < arr[min]){min = j; //最小值的元素下标是j}}// 当i和min相等时,表示最初猜测是对的。// 当i和min不相等时,表示最初猜测是错的,有比这个元素更小的元素,// 需要拿着这个更小的元素和最左边的元素交换位置。if(min != i){// 表示存在更小的数据// arr[min] 最小的数据// arr[i] 最前面的数据int temp;temp = arr[min];arr[min] = arr[i];arr[i] = temp;count2++;}}// 冒泡排序和选择排序实际上比较的次数没变。// 交换位置的次数减少了。System.out.println("比较次数" + count); // 21System.out.println("交换次数:" + count2); // 5// 排序之后遍历for (int i = 0; i < arr.length; i++) {System.out.println(arr[i]);}}
}

二分法(折半法)查找

1、二分法查找算法是基于排序的基础之上。(没有排序的数组是无法查找的)
2、二分法查找的终止条件:一直折半,直到中间的那个元素恰好是被查找的元素。
3、二分法查找的循环条件:开始下标小于等于结束下标。
例:

例:

public class ArrayUtil {public static void main(String[] args) {int[] arr = {100,200,230,235,600,1000,2000,9999};// 找出arr这个数组中200所在的下标。// 调用方法int index = binarySearch(arr, 230);System.out.println(index == -1 ? "该元素不存在!" : "该元素下标" + index);}/*** 从数组中查找目标元素的下标。* @param arr 被查找的数组(这个必须是已经排序的。)* @param dest 目标元素* @return -1表示该元素不存在,其它表示返回该元素的下标。*/public static int binarySearch(int[] arr, int dest) {// 开始下标int begin = 0;// 结束下标int end = arr.length - 1;// 开始元素的下标只要在结束元素下标的左边,就有机会继续循环。while(begin <= end) {// 中间元素下标int mid = (begin + end) / 2;if (arr[mid] == dest) {return mid;} else if (arr[mid] < dest) {// 目标在“中间”的右边// 开始元素下标需要发生变化(开始元素的下标需要重新赋值)begin = mid + 1; // 一直增} else {// arr[mid] > dest// 目标在“中间”的左边// 修改结束元素的下标end = mid - 1; // 一直减}}return -1;}}

String字符串

Java JDK中内置的一个类:java.lang.String
1、String表示字符串类型,属于引用数据类型,不属于基本数据类型。
2、在java中随便使用双引号括起来的都是String对象。例如:“abc”,“def”,“hello world!”,这是3个String对象
3、java中规定,双引号括起来的字符串,是不可变的,也就是说"abc"自出生到最终死亡,不可变,不能变成"abcd",也不能变成"ab"。
4、在JDK当中双引号括起来的字符串,例如:“abc” "def"都是直接存储在“方法区”的“字符串常量池”当中。
为什么SUN公司把字符串存储在一个“字符串常量池”当中呢?因为字符串在实际的开发中使用太频繁。为了执行效率,所以把字符串放到了方法区的字符串常量池当中。

存储原理

例1:

public class StringTest01 {public static void main(String[] args) {// 这两行代码表示底层创建了3个字符串对象,都在字符串常量池当中。String s1 = "abcdef";String s2 = "abcdef" + "xy";// 分析:这是使用new的方式创建的字符串对象。这个代码中的"xy"是从哪里来的?// 凡是双引号括起来的都在字符串常量池中有一份。// new对象的时候一定在堆内存当中开辟空间。String s3 = new String("xy");// i变量中保存的是100这个值。int i = 100;// s变量中保存的是字符串对象的内存地址。// s引用中保存的不是"abc",是0x1111// 而0x1111是"abc"字符串对象在“字符串常量池”当中的内存地址。String s = "abc";}
}


例2:

public class UserTest {public static void main(String[] args) {User user = new User(110, "张三");}
}


例3:

public class StringTest02 {public static void main(String[] args) {String s1 = "hello";// "hello"是存储在方法区的字符串常量池当中// 所以这个"hello"不会新建。(因为这个对象已经存在了!)String s2 = "hello";// 分析结果是true还是false?// == 双等号比较的是不是变量中保存的内存地址?是的。System.out.println(s1 == s2); // trueString x = new String("xyz");String y = new String("xyz");// 分析结果是true还是false?// == 双等号比较的是不是变量中保存的内存地址?是的。System.out.println(x == y); //false// 通过这个案例的学习,我们知道了,字符串对象之间的比较不能使用“==”// "=="不保险。应该调用String类的equals方法。// String类已经重写了equals方法,以下的equals方法调用的是String重写之后的equals方法。System.out.println(x.equals(y)); // trueString k = new String("testString");//String k = null;// "testString"这个字符串可以后面加"."呢?// 因为"testString"是一个String字符串对象。只要是对象都能调用方法。System.out.println("testString".equals(k)); // 建议使用这种方式,因为这个可以避免空指针异常。System.out.println(k.equals("testString")); // 存在空指针异常的风险。不建议这样写。}
}


例4:(面试题)

/*
分析以下程序,一共创建了几个对象*/
public class StringTest03 {public static void main(String[] args) {/*一共3个对象:方法区字符串常量池中有1个:"hello"堆内存当中有两个String对象。一共3个。*/String s1 = new String("hello");String s2 = new String("hello");}
}

构造方法

常用的构造方法:

  1. String s = new String("");
  2. String s = ""; //最常用
  3. String s = new String(char数组);
  4. String s = new String(char数组,起始下标,长度);
  5. String s = new String(byte数组);
  6. String s = new String(byte数组,起始下标,长度);
public class StringTest04 {public static void main(String[] args) {// 创建字符串对象最常用的一种方式String s1 =  "hello world!";// s1这个变量中保存的是一个内存地址。// 按说以下应该输出一个地址。// 但是输出一个字符串,说明String类已经重写了toString()方法。System.out.println(s1);//hello world!System.out.println(s1.toString()); //hello world!// 这里只掌握常用的构造方法。byte[] bytes = {97, 98, 99}; // 97是a,98是b,99是cString s2 = new String(bytes);// 前面说过:输出一个引用的时候,会自动调用toString()方法,默认Object的话,会自动输出对象的内存地址。// 通过输出结果我们得出一个结论:String类已经重写了toString()方法。// 输出字符串对象的话,输出的不是对象的内存地址,而是字符串本身。System.out.println(s2.toString()); //abcSystem.out.println(s2); //abc// String(字节数组,数组元素下标的起始位置,长度)// 将byte数组中的一部分转换成字符串。String s3 = new String(bytes, 1, 2);System.out.println(s3); // bc// 将char数组全部转换成字符串char[] chars = {'我','是','中','国','人'};String s4 = new String(chars);System.out.println(s4);// 将char数组的一部分转换成字符串String s5 = new String(chars, 2, 3);System.out.println(s5);String s6 = new String("helloworld!");System.out.println(s6); //helloworld!}
}

常用方法

charAt

//1(掌握).char charAt(int index)
char c = "中国人".charAt(1); // "中国人"是一个字符串String对象。只要是对象就能“点.”
System.out.println(c); // 国

compareTo

// 2(了解).int compareTo(String anotherString)
// 字符串之间比较大小不能直接使用 > < ,需要使用compareTo方法。
int result = "abc".compareTo("abc");
System.out.println(result); //0(等于0) 前后一致  10 - 10 = 0int result2 = "abcd".compareTo("abce");
System.out.println(result2); //-1(小于0) 前小后大 8 - 9 = -1int result3 = "abce".compareTo("abcd");
System.out.println(result3); // 1(大于0) 前大后小 9 - 8 = 1// 拿着字符串第一个字母和后面字符串的第一个字母比较。能分胜负就不再比较了。
System.out.println("xyz".compareTo("yxz")); // -1

contains

// 3(掌握).boolean contains(CharSequence s)
// 判断前面的字符串中是否包含后面的子字符串。
System.out.println("HelloWorld.java".contains(".java")); // true
System.out.println("http://www.baidu.com".contains("https://")); // false

endsWith

// 4(掌握). boolean endsWith(String suffix)
// 判断当前字符串是否以某个子字符串结尾。
System.out.println("test.txt".endsWith(".java")); // false
System.out.println("test.txt".endsWith(".txt")); // true
System.out.println("fdsajklfhdkjlsahfjkdsahjklfdss".endsWith("ss")); // true

equals

// 5(掌握).boolean equals(Object anObject)
// 比较两个字符串必须使用equals方法,不能使用“==”
// equals方法有没有调用compareTo方法? 老版本可以看一下。JDK13中并没有调用compareTo()方法。
// equals只能看出相等不相等。
// compareTo方法可以看出是否相等,并且同时还可以看出谁大谁小。
System.out.println("abc".equals("abc")); // true

equalsIgnoreCase

// 6(掌握).boolean equalsIgnoreCase(String anotherString)
// 判断两个字符串是否相等,并且同时忽略大小写。
System.out.println("ABc".equalsIgnoreCase("abC")); // true

getBytes

// 7(掌握).byte[] getBytes()
// 将字符串对象转换成字节数组
byte[] bytes = "abcdef".getBytes();
for(int i = 0; i < bytes.length; i++){System.out.println(bytes[i]); //输出97 98 99 100 101 102
}

indexOf

// 8(掌握).int indexOf(String str)
// 判断某个子字符串在当前字符串中第一次出现处的索引(下标)。
System.out.println("oraclejavac++.netc#phppythonjavaoraclec++".indexOf("java")); // 6

isEmpty

// 9(掌握).boolean isEmpty()
// 判断某个字符串是否为“空字符串”。底层源代码调用的应该是字符串的length()方法。
//String s = "";
String s = "a";
System.out.println(s.isEmpty());

length

// 10(掌握). int length()
// 面试题:判断数组长度和判断字符串长度不一样
// 查看数组长度是length属性,查看字符串长度是length()方法。
System.out.println("abc".length()); // 3
System.out.println("".length()); // 0

lastIndexOf

// 11(掌握).int lastIndexOf(String str)
// 判断某个子字符串在当前字符串中最后一次出现的索引(下标)
System.out.println("oraclejavac++javac#phpjavapython".lastIndexOf("java")); //22

replace

// 12(掌握). String replace(CharSequence target, CharSequence replacement)
// 替换。
// String的父接口就是:CharSequence
String newString = "http://www.baidu.com".replace("http://", "https://");
System.out.println(newString); //https://www.baidu.com
// 把以下字符串中的“=”替换成“:”
String newString2 = "name=zhangsan&password=123&age=20".replace("=", ":");
System.out.println(newString2); //name:zhangsan&password:123&age:20

split

// 13(掌握).String[] split(String regex)
// 拆分字符串
String[] ymd = "1980-10-11".split("-"); //"1980-10-11"以"-"分隔符进行拆分。
for(int i = 0; i < ymd.length; i++){System.out.println(ymd[i]);
}
String param = "name=zhangsan&password=123&age=20";
String[] params = param.split("&");
for(int i = 0; i <params.length; i++){System.out.println(params[i]);// 可以继续向下拆分,可以通过“=”拆分。
}

startsWith

// 14(掌握)、boolean startsWith(String prefix)
// 判断某个字符串是否以某个子字符串开始。
System.out.println("http://www.baidu.com".startsWith("http")); // true
System.out.println("http://www.baidu.com".startsWith("https")); // false

substring

// 15(掌握)、 String substring(int beginIndex) 参数是起始下标。
// 截取字符串
System.out.println("http://www.baidu.com".substring(7)); //www.baidu.com// 16(掌握)、String substring(int beginIndex, int endIndex)
// beginIndex起始位置(包括)
// endIndex结束位置(不包括)
System.out.println("http://www.baidu.com".substring(7, 10)); //www

toCharArray

// 17(掌握)、char[] toCharArray()
// 将字符串转换成char数组
char[] chars = "我是中国人".toCharArray();
for(int i = 0; i < chars.length; i++){System.out.println(chars[i]);
}

toLowerCase和toUpperCase

// 18(掌握)、String toLowerCase()
// 转换为小写。
System.out.println("ABCDefKXyz".toLowerCase());// 19(掌握)、String toUpperCase();
System.out.println("ABCDefKXyz".toUpperCase());

trim

// 20(掌握). String trim();
// 去除字符串前后空白
System.out.println("           hello      world             ".trim());

静态方法valueOf

// 21(掌握). String中只有一个方法是静态的,不需要new对象
// 这个方法叫做valueOf
// 作用:将“非字符串”转换成“字符串”
//String s1 = String.valueOf(true);
//String s1 = String.valueOf(100);
//String s1 = String.valueOf(3.14);// 这个静态的valueOf()方法,参数是一个对象的时候,会自动调用该对象的toString()方法吗?
String s1 = String.valueOf(new Customer());
//System.out.println(s1); // 没有重写toString()方法之前是对象内存地址 com.bjpowernode.javase.string.Customer@10f87f48
System.out.println(s1); //我是一个VIP客户!!!!// 我们是不是可以研究一下println()方法的源代码了?
System.out.println(100);
System.out.println(3.14);
System.out.println(true);Object obj = new Object();
// 通过源代码可以看出:为什么输出一个引用的时候,会调用toString()方法!!!!
// 本质上System.out.println()这个方法在输出任何数据的时候都是先转换成字符串,再输出。
System.out.println(obj);System.out.println(new Customer());

StringBuffer

思考:我们在实际的开发中,如果需要进行字符串的频繁拼接,会有什么问题?
因为java中的字符串是不可变的,每一次拼接都会产生新字符串。
这样会占用大量的方法区内存,造成内存空间的浪费。
String s = “abc”;
s += “hello”;
就以上两行代码,就导致在方法区字符串常量池当中创建了3个对象:
“abc” “hello” “abchello”

如果以后需要进行大量字符串的拼接操作,建议使用JDK中自带的:
java.lang.StringBuffer
java.lang.StringBuilder

public class StringBufferTest02 {public static void main(String[] args) {// 创建一个初始化容量为16个byte[] 数组。(字符串缓冲区对象)StringBuffer stringBuffer = new StringBuffer();// 拼接字符串,以后拼接字符串统一调用 append()方法。// append是追加的意思。stringBuffer.append("a");stringBuffer.append("b");stringBuffer.append("d");stringBuffer.append(3.14);stringBuffer.append(true);// append方法底层在进行追加的时候,如果byte数组满了,会自动扩容。stringBuffer.append(100L);System.out.println(stringBuffer.toString());// 指定初始化容量的StringBuffer对象(字符串缓冲区对象)StringBuffer sb = new StringBuffer(100);sb.append("hello");sb.append("world");sb.append("hello");sb.append("kitty");System.out.println(sb);}
}

如何优化StringBuffer的性能?
在创建StringBuffer的时候尽可能给定一个初始化容量。
最好减少底层数组的扩容次数。预估一下,给一个大一些的初始化容量。
关键点:给一个合适的初始化容量,可以提高程序的执行效率。(无参构造方法中默认是给定数组长度16)

StringBuilder

StringBuffer和StringBuilder的区别?
StringBuffer中的方法都有synchronized关键字修饰。表示StringBuffer在多线程环境下运行是安全的。
StringBuilder中的方法都没有synchronized关键字修饰。表示StringBuilder在多线程环境下运行是不安全的。

StringBuffer是线程安全的。
StringBuilder是非线程安全的。

作用上基本和StringBuffer一样。

public class StringBuilderTest01 {public static void main(String[] args) {// 使用StringBuilder也是可以完成字符串的拼接。StringBuilder sb = new StringBuilder();sb.append(100);sb.append(true);sb.append("hello");sb.append("kitty");System.out.println(sb);}
}

String为什么是不可变的?

面试题:String为什么是不可变的?
String类中有一个byte[]数组,这个byte[]数组采用了final修饰,因为数组一旦创建长度不可变,并且被final修饰的引用一旦指向某个对象之后,不可再指向其它对象,所以String是不可变的!

StringBuffer/StringBuilder为什么是可变的呢?
StringBuffer/StringBuilder内部实际上是一个byte[]数组,这个byte[]数组没有被final修饰,StringBuffer/StringBuilder的初始化容量是16,当存满之后会进行扩容,底层调用了数组拷贝的方法System.arraycopy()…。所以StirngBuffer/StringBuilder适合于使用字符串的频繁拼接操作。

拼接就是往byte[]数组中继续放拼接字符串拆分后的单个字符的字节,如果数组不够放了就扩容,进行数组复制,原来的数组在堆内存中就被垃圾回收了,byte[]数组引用指向新的数组对象。这样就不会浪费内存空间!!!

String不可变就是因为底层是final修饰的数组变量,byte[]数组引用不能指向新的数组对象了!!!

public class StringBufferTest04 {public static void main(String[] args) {// 字符串不可变是什么意思?// 是说双引号里面的字符串对象一旦创建不可变。String s = "abc"; //"abc"放到了字符串常量池当中。"abc"不可变。// s变量是可以指向其它对象的。// 字符串不可变不是说以上变量s不可变。说的是"abc"这个对象不可变。s = "xyz";//"xyz"放到了字符串常量池当中。"xyz"不可变。}
}

包装类

java中为8种基本数据类型又对应准备了8种包装类型。8种包装类属于引用数据类型,父类是Object。

包装类存在的意义

思考:为什么要再提供8种包装类呢?
因为8种基本数据类型不够用。
所以SUN又提供对应的8种包装类型。

public class IntegerTest01 {//入口public static void main(String[] args) {// 有没有这种需求:调用doSome()方法的时候需要传一个数字进去。// 但是数字属于基本数据类型,而doSome()方法参数的类型是Object。// 可见doSome()方法无法接收基本数据类型的数字。那怎么办呢?可以传一个数字对应的包装类进去。// 把100这个数字经过构造方法包装成对象。MyInt myInt = new MyInt(100);// doSome()方法虽然不能直接传100,但是可以传一个100对应的包装类型。doSome(myInt);}public static void doSome(Object obj){//System.out.println(obj);System.out.println(obj.toString());}
}// 这种包装类目前是我自己写的。实际开发中我们不需要自己写。
// 8种基本数据类型对应的8种包装类,SUN公司已经写好了。我们直接用。
public class MyInt {int value;public MyInt() {}public MyInt(int value) {this.value = value;}@Overridepublic String toString() {return String.valueOf(value);}
}

八种包装类

8种基本数据类型对应的包装类型名是什么?

基本数据类型 包装类型
byte java.lang.Byte(父类Number)
short java.lang.Short(父类Number)
int java.lang.Integer(父类Number)
long java.lang.Long(父类Number)
float java.lang.Float(父类Number)
double java.lang.Double(父类Number)
boolean java.lang.Boolean(父类Object)
char java.lang.Character(父类Object)

Number类中的公共方法

八种包装类中其中6个都是数字对应的包装类,他们的父类都是Number,研究一下Number中公共的方法:
Number是一个抽象类,无法实例化对象。t法
Number类中有这样的方法:
byte byteValue() :以byte形式返回指定的数值。
abstract double doubleValue() :以double形式返回指定的数值。
abstract float floatValue() :以float形式返回指定的数值。
abstract int intValue() :以int形式返回指定的数值。
abstract long longValue() :以long形式返回指定的数值。
short shortValue() :以short形式返回指定的数值。
这些方法所有的数字包装类的子类都有,这些方法是负责拆箱的。

装箱和拆箱的概念

装箱:基本数据类型 --> 引用数据类型
拆箱:引用数据类型 --> 基本数据类型

public class IntegerTest02 {public static void main(String[] args) {// 123这个基本数据类型,进行构造方法的包装达到了:基本数据类型向引用数据类型的转换。// 基本数据类型 -(转换为)->引用数据类型(装箱)Integer i = new Integer(123);// 将引用数据类型--(转换为)-> 基本数据类型float f = i.floatValue();System.out.println(f); //123.0// 将引用数据类型--(转换为)-> 基本数据类型(拆箱)int retValue = i.intValue();System.out.println(retValue); //123}
}

Integer的构造方法

Integer类的构造方法,有两个:
Integer(int)
Integer(String)

public class IntegerTest03 {public static void main(String[] args) {// Java9之后不建议使用这个构造方法了。出现横线表示已过时。// 将数字100转换成Integer包装类型(int --> Integer)Integer x = new Integer(100);System.out.println(x);// 将String类型的数字,转换成Integer包装类型。(String --> Integer)Integer y = new Integer("123");System.out.println(y);// double -->DoubleDouble d = new Double(1.23);System.out.println(d);// String --> DoubleDouble e = new Double("3.14");System.out.println(e);}
}

通过常量获取最大值和最小值

public class IntegerTest04 {public static void main(String[] args) {// 通过访问包装类的常量,来获取最大值和最小值System.out.println("int的最大值:" + Integer.MAX_VALUE);System.out.println("int的最小值:" + Integer.MIN_VALUE);System.out.println("byte的最大值:" + Byte.MAX_VALUE);System.out.println("byte的最小值:" + Byte.MIN_VALUE);}
}

自动装箱和自动拆箱

好消息:在java5之后,引入了一种新特性,自动装箱和自动拆箱。
自动装箱:基本数据类型自动转换成包装类。
自动拆箱:包装类自动转换成基本数据类型。

有了自动拆箱之后,Number类中的方法就用不着了!

自动装箱和自动拆箱的好处?方便编程。

public class IntegerTest05 {public static void main(String[] args) {// 900是基本数据类型// x是包装类型// 基本数据类型 --(自动转换)--> 包装类型:自动装箱Integer x = 900;System.out.println(x);// x是包装类型// y是基本数据类型// 包装类型 --(自动转换)--> 基本数据类型:自动拆箱int y = x;System.out.println(y);// z是一个引用,z是一个变量,z还是保存了一个对象的内存地址。Integer z = 1000; // 等同于:Integer z = new Integer(1000);// 分析为什么这个没有报错呢?// +两边要求是基本数据类型的数字,z是包装类,不属于基本数据类型,这里会进行自动拆箱。将z转换成基本数据类型// 在java5之前你这样写肯定编译器报错。System.out.println(z + 1);Integer a = 1000; // Integer a = new Integer(1000); a是个引用,保存内存地址指向对象。Integer b = 1000; // Integer b = new Integer(1000); b是个引用,保存内存地址指向对象。// == 比较的是对象的内存地址,a和b两个引用中保存的对象内存地址不同。// == 这个运算符不会触发自动拆箱机制。(只有+ - * /等运算的时候才会。)System.out.println(a == b); //false}
}

java中为了提高程序的执行效率,将[-128到127]之间所有的包装对象提前创建好,放到了一个方法区的“整数型常量池”当中了,目的是只要用这个区间的数据不需要再new了,直接从整数型常量池当中取出来。
面试题:(非常重要)

/*
这个题目是Integer非常重要的面试题。*/
public class IntegerTest06 {public static void main(String[] args) {Integer a = 128;Integer b = 128;System.out.println(a == b); //false/*java中为了提高程序的执行效率,将[-128到127]之间所有的包装对象提前创建好,放到了一个方法区的“整数型常量池”当中了,目的是只要用这个区间的数据不需要再new了,直接从整数型常量池当中取出来。原理:x变量中保存的对象的内存地址和y变量中保存的对象的内存地址是一样的。*/Integer x = 127;Integer y = 127;// == 永远判断的都是两个对象的内存地址是否相同。System.out.println(x == y); //true}
}

Integer常用方法

parseInt

// 重点方法
// static int parseInt(String s)
// 静态方法,传参String,返回int
//网页上文本框中输入的100实际上是"100"字符串。后台数据库中要求存储100数字,此时java程序需要将"100"转换成100数字。
int retValue = Integer.parseInt("123"); // String -转换-> int
//int retValue = Integer.parseInt("中文"); // NumberFormatException
System.out.println(retValue + 100);// 照葫芦画瓢
double retValue2 = Double.parseDouble("3.14");
System.out.println(retValue2 + 1); //4.140000000000001(精度问题)float retValue3 = Float.parseFloat("1.0");
System.out.println(retValue3 + 1); //2.0

toBinaryString

// static String toBinaryString(int i)
// 静态的:将十进制转换成二进制字符串。
String binaryString = Integer.toBinaryString(3);
System.out.println(binaryString); //"11" 二进制字符串

toHexString

// static String toHexString(int i)
// 静态的:将十进制转换成十六进制字符串。
String hexString = Integer.toHexString(16);
System.out.println(hexString); // "10"// 十六进制:1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 13 14 15 16 17 18 19 1a
hexString = Integer.toHexString(17);
System.out.println(hexString); // "11"

toOctalString

//static String toOctalString(int i)
// 静态的:将十进制转换成八进制字符串。
String octalString = Integer.toOctalString(8);
System.out.println(octalString); // "10"

valueOf

// valueOf方法作为了解
//static Integer valueOf(int i)
// 静态的:int-->Integer
Integer i1 = Integer.valueOf(100);
System.out.println(i1);// static Integer valueOf(String s)
// 静态的:String-->Integer
Integer i2 = Integer.valueOf("100");
System.out.println(i2);

总结一下之前的经典异常

空指针异常:NullPointerException
类型转换异常:ClassCastException
数组下标越界异常:ArrayIndexOutOfBoundsException
数字格式化异常:NumberFormatException

// 编译的时候没问题,一切符合java语法,运行时会不会出问题呢?
// 不是一个“数字”可以包装成Integer吗?不能。运行时出现异常。
// java.lang.NumberFormatException
Integer a = new Integer("中文");

String int Integer类型互换

对日期的处理

知识点1:怎么获取系统当前时间
知识点2:String —> Date
知识点3:Date —> String

1、获取系统当前时间(精确到毫秒的系统当前时间)
java.util.Date

// 获取系统当前时间(精确到毫秒的系统当前时间)
// 直接调用无参数构造方法就行。
Date nowTime = new Date();// java.util.Date类的toString()方法已经被重写了。
// 输出的应该不是一个对象的内存地址,应该是一个日期字符串。
//System.out.println(nowTime); //Thu Mar 05 10:51:06 CST 2020

2、Date —> String
java.text.SimpleDateFormat

// 日期可以格式化吗?
// 将日期类型Date,按照指定的格式进行转换:Date --转换成具有一定格式的日期字符串-->String
// SimpleDateFormat是java.text包下的。专门负责日期格式化的。
/*
yyyy 年(年是4位)
MM 月(月是2位)
dd 日
HH 时
mm 分
ss 秒
SSS 毫秒(毫秒3位,最高999。1000毫秒代表1秒)
注意:在日期格式中,除了y M d H m s S这些字符不能随便写之外,剩下的符号格式自己随意组织。*/
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
//SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
//SimpleDateFormat sdf = new SimpleDateFormat("yy/MM/dd HH:mm:ss");String nowTimeStr = sdf.format(nowTime);
System.out.println(nowTimeStr);

3、String —> Date

// 假设现在有一个日期字符串String,怎么转换成Date类型?
// String --> Date
String time = "2008-08-08 08:08:08 888";
//SimpleDateFormat sdf2 = new SimpleDateFormat("格式不能随便写,要和日期字符串格式相同");
// 注意:字符串的日期格式和SimpleDateFormat对象指定的日期格式要一致。不然会出现异常:java.text.ParseException
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
Date dateTime = sdf2.parse(time);
System.out.println(dateTime); //Fri Aug 08 08:08:08 CST 2008

统计方法执行时长

Syetem.currentTimeMillis()获取自1970年1月1日 00:00:00 000到当前系统时间的总毫秒数。

public class DateTest02 {public static void main(String[] args) {// 获取自1970年1月1日 00:00:00 000到当前系统时间的总毫秒数。long nowTimeMillis = System.currentTimeMillis();System.out.println(nowTimeMillis); //1583377912981// 统计一个方法耗时// 在调用目标方法之前记录一个毫秒数long begin = System.currentTimeMillis();print();// 在执行完目标方法之后记录一个毫秒数long end = System.currentTimeMillis();System.out.println("耗费时长"+(end - begin)+"毫秒");}// 需求:统计一个方法执行所耗费的时长public static void print(){for(int i = 0; i < 1000000000; i++){System.out.println("i = " + i);}}
}

System类的相关属性和方法

System.out :out是System类的静态变量,类型是PrintStream
System.out.println() :println()方法不是System类的,是PrintStream类的方法。
System.gc() :建议启动垃圾回收器
System.currentTimeMillis() :获取自1970年1月1日到系统当前时间的总毫秒数
System.exit(0) :退出JVM

Date的构造方法

import java.text.SimpleDateFormat;
import java.util.Date;public class DateTest03 {public static void main(String[] args) {// 这个时间是什么时间?// 1970-01-01 00:00:00 001Date time = new Date(1); // 注意:参数是一个毫秒SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");String strTime = sdf.format(time);// 北京是东8区。差8个小时。System.out.println(strTime); // 1970-01-01 08:00:00 001// 获取昨天的此时的时间。Date time2 = new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24);String strTime2 = sdf.format(time2);System.out.println(strTime2); //2020-03-04 11:44:14 829// 获取“去年的今天”的时间// 自己玩。}
}

数字类

数字格式化

java.text.DecimalFormat

public class DecimalFormatTest01 {public static void main(String[] args) {// java.text.DecimalFormat专门负责数字格式化的。//DecimalFormat df = new DecimalFormat("数字格式");/*数字格式有哪些?# 代表任意数字, 代表千分位. 代表小数点0 代表不够时补0###,###.##表示:加入千分位,保留2个小数。*/DecimalFormat df = new DecimalFormat("###,###.##");//String s = df.format(1234.56);String s = df.format(1234.561232);System.out.println(s); // "1,234.56"DecimalFormat df2 = new DecimalFormat("###,###.0000"); //保留4个小数位,不够补上0String s2 = df2.format(1234.56);System.out.println(s2); // "1,234.5600"}
}

高精度BigDecimal

java.math.BigDecimal属于大数据,精度极高。不属于基本数据类型,属于java对象(引用数据类型)。
这是SUN提供的一个类,专门用在财务软件当中。

public class BigDecimalTest01 {public static void main(String[] args) {// 这个100不是普通的100,是精度极高的100BigDecimal v1 = new BigDecimal(100);// 精度极高的200BigDecimal v2 = new BigDecimal(200);// 求和// v1 + v2; // 这样不行,v1和v2都是引用,不能直接使用+求和。BigDecimal v3 = v1.add(v2); // 调用方法求和。System.out.println(v3); //300BigDecimal v4 = v2.divide(v1);System.out.println(v4); // 2}
}

随机数Random

java.util.Random

import java.util.Random;public class RandomTest01 {public static void main(String[] args) {// 创建随机数对象Random random = new Random();// 随机产生一个int类型取值范围内的数字。int num1 = random.nextInt();System.out.println(num1);// 产生[0~100]之间的随机数。不能产生101。// nextInt翻译为:下一个int类型的数据是101,表示只能取到100.int num2 = random.nextInt(101); //不包括101System.out.println(num2);}
}

例:编写程序,生成5个不重复的随机数[0-100]。重复的话重新生成。最终生成的5个随机数放到数组中,要求数组中这5个随机数不重复。

import java.util.Arrays;
import java.util.Random;public class RandomTest02 {public static void main(String[] args) {// 创建Random对象Random random = new Random();// 准备一个长度为5的一维数组。int[] arr = new int[5]; // 默认值都是0for(int i = 0; i < arr.length; i++){arr[i] = -1;}// 循环,生成随机数int index = 0;while(index < arr.length){// 生成随机数//int num = random.nextInt(101);//int num = random.nextInt(6); // 只能生成[0-5]的随机数!int num = random.nextInt(4); // 只能生成[0-3]的随机数!永远都有重复的,永远都凑不够5个。System.out.println("生成的随机数:" + num);// 判断arr数组中有没有这个num// 如果没有这个num,就放进去。if(!contains(arr, num)){arr[index++] = num;}}// 遍历以上的数组for(int i = 0; i < arr.length; i++){System.out.println(arr[i]);}}/*** 单独编写一个方法,这个方法专门用来判断数组中是否包含某个元素* @param arr 数组* @param key 元素* @return true表示包含,false表示不包含。*/public static boolean contains(int[] arr, int key){/*// 这个方案bug。(排序出问题了。)// 对数组进行升序//Arrays.sort(arr);// 进行二分法查找// 二分法查找的结果 >= 0说明,这个元素找到了,找到了表示存在!//return Arrays.binarySearch(arr, key) >= 0;*/for(int i = 0; i < arr.length; i++){if(arr[i] == key){// 条件成立了表示包含,返回truereturn true;}}// 这个就表示不包含!return false;}
}

枚举类型

为什么使用枚举类型

需求(这是设计者说的!):以下程序,计算两个int类型数据的商,计算成功返回1,计算失败返回0
@param a int类型的数据
@param b int类型的数据
@return 返回1表示成功,返回0表示失败!

public static int divide(int a, int b){try {int c = a / b;// 程序执行到此处表示以上代码没有发生异常。表示执行成功!return 1;} catch (Exception e){// 程序执行到此处表示以上程序出现了异常!// 表示执行失败!return 0;}
}*/// 设计缺陷?在这个方法的返回值类型上。返回一个int不恰当。
// 既然最后的结果只是成功和失败,最好使用布尔类型。因为布尔类型true和false正好可以表示两种不同的状态。
/*
public static int divide(int a, int b){try {int c = a / b;// 返回10已经偏离了需求,实际上已经出错了,但是编译器没有检查出来。// 我们一直想追求的是:所有的错误尽可能让编译器找出来,所有的错误越早发现越好!return 10;} catch (Exception e){return 0;}
}
*/
// 这种设计不错。
public static boolean divide(int a, int b){try {int c = a / b;return true;} catch (Exception e){return false;}
}/*
思考:以上的这个方法设计没毛病,挺好,返回true和false表示两种情况,
但是在以后的开发中,有可能遇到一个方法的执行结果可能包括三种情况,
四种情况,五种情况不等,但是每一个都是可以数清楚的,一枚一枚都是可以
列举出来的。这个布尔类型就无法满足需求了。此时需要使用java语言中的
枚举类型。*/

枚举类型的使用

总结:
1、枚举是一种引用数据类型
2、枚举类型怎么定义,语法是?
enum 枚举类型名{
枚举值1,枚举值2
}
3、结果只有两种情况的,建议使用布尔类型。
结果超过两种并且还是可以一枚一枚列举出来的,建议使用枚举类型。
例如:颜色、四季、星期等都可以使用枚举类型。
采用枚举的方式改造程序:

public class EnumTest02 {public static void main(String[] args) {Result r = divide(10, 2);System.out.println(r == Result.SUCCESS ? "计算成功" : "计算失败");}/*** 计算两个int类型数据的商。* @param a int数据* @param b int数据* @return Result.SUCCESS表示成功,Result.FAIL表示失败!*/public static Result divide(int a, int b){try {int c = a / b;return Result.SUCCESS;} catch (Exception e){return Result.FAIL;}}
}// 枚举:一枚一枚可以列举出来的,才建议使用枚举类型。
// 枚举编译之后也是生成class文件。
// 枚举也是一种引用数据类型。
// 枚举中的每一个值可以看做是常量。
enum Result{// SUCCESS 是枚举Result类型中的一个值// FAIL 是枚举Result类型中的一个值// 枚举中的每一个值,可以看做是“常量”SUCCESS, FAIL
}

例:

public enum Season {/*春夏秋冬*/SPRING, SUMMER, AUTUMN, WINTER
}public class SwitchTest {public static void main(String[] args) {// switch语句支持枚举类型// switch也支持String、int// 低版本的JDK,只支持int// 高版本的JDK,支持int、String、枚举。// byte short char也可以,因为存在自动类型转换。switch (Season.SPRING) {// 必须省略Season.case SPRING:System.out.println("春天");break;case SUMMER:System.out.println("夏天");break;case AUTUMN:System.out.println("秋天");break;case WINTER:System.out.println("冬天");break;}}
}

异常

异常描述

1、什么是异常,java提供异常处理机制有什么用?
以下程序执行过程中发生了不正常的情况,而这种不正常的情况叫做:异常
java语言是很完善的语言,提供了异常的处理方式,以下程序执行过程中出现了不正常情况,java把该异常信息打印输出到控制台,供程序员参考。程序员看到异常信息之后,可以对程序进行修改,让程序更加的健壮。

什么是异常:程序执行过程中的不正常情况。
异常的作用:增强程序的健壮性。

2、以下程序执行控制台出现了:
Exception in thread “main” java.lang.ArithmeticException: / by zero
at com.bjpowernode.javase.exception.ExceptionTest01.main(ExceptionTest01.java:14)
这个信息被我们称为:异常信息。这个信息是JVM打印的。

public class ExceptionTest01 {public static void main(String[] args) {int a = 10;int b = 0;// 实际上JVM在执行到此处的时候,会new异常对象:new ArithmeticException("/ by zero");// 并且JVM将new的异常对象抛出,打印输出信息到控制台了。int c = a / b;System.out.println(a + "/" + b + "=" + c);// 此处运行也会创建一个:ArithmeticException类型的异常对象。//System.out.println(100 / 0);// 我观察到异常信息之后,对程序进行修改,更加健壮。/*int a = 10;int b = 2;if(b == 0) {System.out.println("除数不能为0");return;}// 程序执行到此处表示除数一定不是0int c = a / b;System.out.println(a + "/" + b + "=" + c);*/}
}

java语言中异常以什么形式存在

1、异常在java中以类的形式存在,每一个异常类都可以创建异常对象。
2、异常对应的现实生活中是怎样的?
火灾(异常类):
2008年8月8日,小明家着火了(异常对象)
2008年9月8日,小刚家着火了(异常对象)
2008年9月8日,小红家着火了(异常对象)
类是:模板。
对象是:实际存在的个体。

钱包丢了(异常类):
2008年1月8日,小明的钱包丢了(异常对象)
2008年1月9日,小芳的钱包丢了(异常对象)

public class ExceptionTest02 {public static void main(String[] args) {// 通过“异常类”实例化“异常对象”NumberFormatException nfe = new NumberFormatException("数字格式化异常!");// java.lang.NumberFormatException: 数字格式化异常!System.out.println(nfe);// 通过“异常类”创建“异常对象”NullPointerException npe = new NullPointerException("空指针异常发生了!");//java.lang.NullPointerException: 空指针异常发生了!System.out.println(npe);}
}

java的异常处理机制

异常的继承结构


Throwable:不管是错误,还是异常,都是可抛出的。
Error:所有的错误只要发生,Java程序只有一个结果,那就是终止程序的执行。退出JVM,错误是不能处理的。

RuntimeException:所有的RuntimeException及子类都属于运行时异常。运行时异常在编写程序阶段,可以选择处理,也可以不处理。运行时异常发生概率较低。运行时异常还有另外一些名字:未受检异常,或者非受控异常

ExceptionSubClass(Exception的直接子类):所有Exception的直接子类,都叫做编译时异常。编译时异常是在编译阶段发生的吗?不是。编译时异常是表示必须在编写程序的时候预先对这种异常进行处理,如果不处理编译器报错。编译时异常发生概率较高。编译时异常又被称为受检异常,或者受控异常。

继承结构:
Object
Object下有Throwable(可抛出的)
Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
Exception下有两个分支:
Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。)
RuntimeException:运行时异常(在编写程序阶段程序员可以预先处理,也可以不管,都行。)

编译时异常因什么而得名

编译时异常和运行时异常,都是发生在运行阶段。编译阶段异常是不会发生的。
编译时异常因什么而得名?
因为编译时异常必须在编译(编写程序)阶段预先处理,如果不处理编译器会报错,因此得名。
所有异常都是在运行阶段发生的。因为只有程序运行阶段才可以new对象。因为异常的发生就是new异常对象。

编译时异常和运行时异常的区别

编译时异常一般发生的概率比较高
举个例子:
你看到外面下雨了,倾盆大雨的。
你出门之前会预料到:如果不打伞,我可能会生病(生病是一种异常)。
而且这个异常发生的概率很高,所以我们出门之前要拿一把伞。
“拿一把伞”就是对“生病异常”发生之前的一种处理方式。

运行时异常一般发生的概率比较低
举个例子:
小明走在大街上,可能会被天上的飞机轮子砸到。
被飞机轮子砸到也算一种异常。
但是这种异常发生概率较低。
在出门之前你没必要提前对这种发生概率较低的异常进行预处理。
如果你预处理这种异常,你将活的很累。

假设你在出门之前,你把能够发生的异常都预先处理,你这个人会更加的安全,但是你这个人活得很累。

假设java中没有对异常进行划分,没有分为:编译时异常和运行时异常,所有的异常都需要在编写程序阶段对其进行预处理,将是怎样的效果呢?
首先,如果这样的话,程序肯定是绝对的安全的。但是程序员编写程序太累,代码到处都是处理异常的代码。

编译时异常还有其他名字:
受检异常:CheckedException
受控异常
运行时异常还有其他名字:
未受检异常:UnCheckedException
非受控异常

再次强调:所有异常都是发生在运行阶段的。

异常的两种处理方式

第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。
谁调用我,我就抛给谁。抛给上一级。

第二种方式:使用try…catch语句进行异常的捕捉。
这件事发生了,谁也不知道,因为我把它抓住了。

举个例子:
我是某集团的一个销售员,因为我的失误,导致公司损失了1000元,“损失1000元”这可以看作是一个异常发生了。我有两种处理方式:
第一种方式:我把这件事告诉我的领导【异常上抛】
第二种方式:我自己掏腰包把这个钱补上【异常的捕捉】

上抛后如果上级觉得处理不了,就会继续往上抛:张三 --> 李四 --> 王五 --> CEO

思考:
异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式

注意:Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果:终止java程序的执行。

运行时异常编写程序时可以不处理

public class ExceptionTest03 {public static void main(String[] args) {/*程序执行到此处发生了ArithmeticException异常,底层new了一个ArithmeticException异常对象,然后抛出了,由于是main方法调用了100 / 0,所以这个异常ArithmeticException抛给了main方法,main方法没有处理,将这个异常自动抛给了JVM。JVM最终终止程序的执行。ArithmeticException 继承 RuntimeException,属于运行时异常。在编写程序阶段不需要对这种异常进行预先的处理。*/System.out.println(100 / 0);// 这里的HelloWorld没有输出,没有执行。System.out.println("Hello World!");}
}

方法声明位置上使用throws

以下代码报错的原因是什么?
因为doSome()方法声明位置上使用了:throws ClassNotFoundException
而ClassNotFoundExcepiton是编译时异常。必须编写代码时处理,没有处理编译器报错。

public class ExceptionTest04 {public static void main(String[] args) {// main方法中调用doSome()方法// 因为doSome()方法声明位置上有:throws ClassNotFoundException// 我们在调用doSome()方法的时候必须对这种异常进行预先的处理。// 如果不处理,编译器就报错。//编译器报错信息: Unhandled exception: java.lang.ClassNotFoundExceptiondoSome();}/*** doSome方法在方法声明的位置上使用了:throws ClassNotFoundException* 这个代码表示doSome()方法在执行过程中,有可能会出现ClassNotFoundException异常。* 叫做类没找到异常。这个异常直接父类是:Exception,所以ClassNotFoundException属于编译时异常。* @throws ClassNotFoundException*/public static void doSome() throws ClassNotFoundException{System.out.println("doSome!!!!");}
}

异常处理的原理

public class ExceptionTest05 {// 第一种处理方式:在方法声明的位置上继续使用:throws,来完成异常的继续上抛。抛给调用者。// 上抛类似于推卸责任。(继续把异常传递给调用者。)/*public static void main(String[] args) throws ClassNotFoundException {doSome();}*/// 第二种处理方式:try..catch进行捕捉。// 捕捉等于把异常拦下了,异常真正的解决了。(调用者是不知道的。)public static void main(String[] args) {try {doSome();} catch (ClassNotFoundException e) {e.printStackTrace();}}public static void doSome() throws ClassNotFoundException{System.out.println("doSome!!!!");}}

异常捕捉和上报的联合使用

处理异常的第一种方式:
在方法声明的位置上使用throws关键字抛出,谁调用我这个方法,我就抛给谁。抛给调用者来处理。
这种处理异常的态度:上报。

处理异常的第二种方式:
使用try…catch语句对异常进行捕捉。
这个异常不会上报,自己把这个事儿处理了。
异常抛到此处为止,不再上抛了。

注意:
只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。
另外需要注意,try语句块中的某一行出现异常,该行后面的代码不会执行。
try…catch捕捉异常之后,后续代码可以执行。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;public class ExceptionTest06 {// 一般不建议在main方法上使用throws,因为这个异常如果真正的发生了,一定会抛给JVM。JVM只有终止。// 异常处理机制的作用就是增强程序的健壮性。怎么能做到,异常发生了也不影响程序的执行。所以// 一般main方法中的异常建议使用try..catch进行捕捉。main就不要继续上抛了。/*public static void main(String[] args) throws FileNotFoundException {System.out.println("main begin");m1();System.out.println("main over");}*/public static void main(String[] args) {// 100 / 0这是算术异常,这个异常是运行时异常,你在编译阶段,可以处理,也可以不处理。编译器不管。//System.out.println(100 / 0); // 不处理编译器也不管// 你处理也可以。/*try {System.out.println(100 / 0);} catch(ArithmeticException e){System.out.println("算术异常了!!!!");}*/System.out.println("main begin");try {// try尝试m1();// 以上代码出现异常,直接进入catch语句块中执行。System.out.println("hello world!");} catch (FileNotFoundException e){ // catch后面的好像一个方法的形参。// 这个分支中可以使用e引用,e引用保存的内存地址是那个new出来异常对象的内存地址。// catch是捕捉异常之后走的分支。// 在catch分支中干什么?处理异常。System.out.println("文件不存在,可能路径错误,也可能该文件被删除了!");System.out.println(e); //java.io.FileNotFoundException: D:\course\01-课\学习方法.txt (系统找不到指定的路径。)}// try..catch把异常抓住之后,这里的代码会继续执行。System.out.println("main over");}private static void m1() throws FileNotFoundException {System.out.println("m1 begin");m2();// 以上代码出异常,这里是无法执行的。System.out.println("m1 over");}// 抛别的不行,抛ClassCastException说明你还是没有对FileNotFoundException进行处理//private static void m2() throws ClassCastException{// 抛FileNotFoundException的父对象IOException,这样是可以的。因为IOException包括FileNotFoundException//private static void m2() throws IOException {// 这样也可以,因为Exception包括所有的异常。//private static void m2() throws Exception{// throws后面也可以写多个异常,可以使用逗号隔开。//private static void m2() throws ClassCastException, FileNotFoundException{private static void m2() throws FileNotFoundException {System.out.println("m2 begin");// 编译器报错原因是:m3()方法声明位置上有:throws FileNotFoundException// 我们在这里调用m3()没有对异常进行预处理,所以编译报错。// m3();m3();// 以上如果出现异常,这里是无法执行的!System.out.println("m2 over");}private static void m3() throws FileNotFoundException {// 调用SUN jdk中某个类的构造方法。// 这个类还没有接触过,后期IO流的时候就知道了。// 我们只是借助这个类学习一下异常处理机制。// 创建一个输入流对象,该流指向一个文件。/*编译报错的原因是什么?第一:这里调用了一个构造方法:FileInputStream(String name)第二:这个构造方法的声明位置上有:throws FileNotFoundException第三:通过类的继承结构看到:FileNotFoundException父类是IOException,IOException的父类是Exception,最终得知,FileNotFoundException是编译时异常。错误原因?编译时异常要求程序员编写程序阶段必须对它进行处理,不处理编译器就报错。*///new FileInputStream("D:\\course\\01-开课\\学习方法.txt");// 我们采用第一种处理方式:在方法声明的位置上使用throws继续上抛。// 一个方法体当中的代码出现异常之后,如果上报的话,此方法结束。new FileInputStream("D:\\course\\01-课\\学习方法.txt");System.out.println("如果以上代码出异常,这里会执行吗??????????????????不会!!!");}
}

一般不建议在main方法上使用throws,因为这个异常如果真正的发生了,一定会抛给JVM。JVM只有终止。
异常处理机制的作用就是增强程序的健壮性。怎么能做到异常发生了也不影响程序的执行。所以一般main方法中的异常建议使用try…catch进行捕捉,main就不要继续上抛了。

try catch深入

1、catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型。
2、catch可以写多个。建议catch的时候,精确的一个一个处理。这样有利于程序的调试。
3、catch写多个的时候,从上到下,必须遵守从小到大。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;public class ExceptionTest07 {/*public static void main(String[] args) throws Exception, FileNotFoundException, NullPointerException {}*//*public static void main(String[] args) throws Exception {}*/public static void main(String[] args) {//编译报错/*try {FileInputStream fis = new FileInputStream("D:\\course\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");} catch(NullPointerException e) {}*//*try {FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");System.out.println("以上出现异常,这里无法执行!");} catch(FileNotFoundException e) {System.out.println("文件不存在!");}System.out.println("hello world!");*//*try {FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");} catch(IOException e) { // 多态:IOException e = new FileNotFoundException();System.out.println("文件不存在!");}*//*try {FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");} catch(Exception e) { // 多态:Exception e = new FileNotFoundException();System.out.println("文件不存在!");}*//*try {//创建输入流FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");//读文件fis.read();} catch(Exception e) { //所有的异常都走这个分支。System.out.println("文件不存在!");}*//*try {//创建输入流FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");//读文件fis.read();} catch(FileNotFoundException e) {System.out.println("文件不存在!");} catch(IOException e){System.out.println("读文件报错了!");}*/// 编译报错。/*try {//创建输入流FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");//读文件fis.read();} catch(IOException e){System.out.println("读文件报错了!");} catch(FileNotFoundException e) {System.out.println("文件不存在!");}*/// JDK8的新特性!try {//创建输入流FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");// 进行数学运算System.out.println(100 / 0); // 这个异常是运行时异常,编写程序时可以处理,也可以不处理。} catch(FileNotFoundException | ArithmeticException | NullPointerException e) {System.out.println("文件不存在?数学异常?空指针异常?都有可能!");}}
}

上报和捕捉怎么选择

在以后的开发中,处理编译时异常,应该上报还是捕捉呢?怎么选?
如果希望调用者来处理,选择throws上报。
其他情况使用捕捉的方式。

异常对象的常用方法

异常对象有两个非常重要的方法:
获取异常简单的描述信息:
String msg = exception.getMessage();
打印异常追踪的堆栈信息:
exception.printStackTrace();//一般都是使用这个

public class ExceptionTest08 {public static void main(String[] args) {// 这里只是为了测试getMessage()方法和printStackTrace()方法。// 这里只是new了异常对象,但是没有将异常对象抛出。JVM会认为这是一个普通的java对象。NullPointerException e = new NullPointerException("空指针异常fdsafdsafdsafds");// 获取异常简单描述信息:这个信息实际上就是构造方法上面String参数。String msg = e.getMessage(); //空指针异常fdsafdsafdsafdsSystem.out.println(msg);// 打印异常堆栈信息// java后台打印异常堆栈追踪信息的时候,采用了异步线程的方式打印的。e.printStackTrace();for(int i = 0; i < 1000; i++){System.out.println("i = " + i);}System.out.println("Hello World!");}
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;public class ExceptionTest09 {public static void main(String[] args) {try {m1();} catch (FileNotFoundException e) {// 获取异常的简单描述信息String msg = e.getMessage();System.out.println(msg); //C:\jetns-agent.jar (系统找不到指定的文件。)//打印异常堆栈追踪信息!!!//在实际的开发中,建议使用这个。养成好习惯!// 这行代码要写上,不然出问题你也不知道!//e.printStackTrace();/*java.io.FileNotFoundException: C:\jetns-agent.jar (系统找不到指定的文件。)at java.base/java.io.FileInputStream.open0(Native Method)at java.base/java.io.FileInputStream.open(FileInputStream.java:213)at java.base/java.io.FileInputStream.<init>(FileInputStream.java:155)at java.base/java.io.FileInputStream.<init>(FileInputStream.java:110)at com.bjpowernode.javase.exception.ExceptionTest09.m3(ExceptionTest09.java:31)at com.bjpowernode.javase.exception.ExceptionTest09.m2(ExceptionTest09.java:27)at com.bjpowernode.javase.exception.ExceptionTest09.m1(ExceptionTest09.java:23)at com.bjpowernode.javase.exception.ExceptionTest09.main(ExceptionTest09.java:14)因为31行出问题导致了27行27行出问题导致23行23行出问题导致14行。应该先查看31行的代码。31行是代码错误的根源。*/}// 这里程序不耽误执行,很健壮。《服务器不会因为遇到异常而宕机。》System.out.println("Hello World!");}private static void m1() throws FileNotFoundException {m2();}private static void m2() throws FileNotFoundException {m3();}private static void m3() throws FileNotFoundException {new FileInputStream("C:\\jetns-agent.jar");}
}

我们以后查看异常的追踪信息,应该怎么看,可以快速地调试程序呢?
异常追踪信息,从上往下一行一行看。
但是需要注意的是:SUN写的代码就不用看了(看包名就知道是自己的还是SUN的)。
主要的问题是出现在自己编写的代码上。

finally子句的使用

1、在finally子句中的代码是最后执行的,并且是一定会执行的,即使try语句块中的代码出现了异常。
finally子句必须和try一起出现,不能单独编写。

2、finally语句通常使用在哪些情况下呢?
通常在finally语句块中完成资源的释放/关闭。
因为finally中的代码比较有保障。
即使try语句块中的代码出现异常,finally中代码也会正常执行。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;public class ExceptionTest10 {public static void main(String[] args) {FileInputStream fis = null; // 声明位置放到try外面。这样在finally中才能用。try {// 创建输入流对象fis = new FileInputStream("D:\\course\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");// 开始读文件....String s = null;// 这里一定会出现空指针异常!s.toString();System.out.println("hello world!");//不执行// 流使用完需要关闭,因为流是占用资源的。// 即使以上程序出现异常,流也必须要关闭!// 放在这里有可能流关不了。//fis.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch(IOException e){e.printStackTrace();} catch(NullPointerException e) {e.printStackTrace();} finally {System.out.println("hello 浩克!");// 流的关闭放在这里比较保险。// finally中的代码是一定会执行的。// 即使try中出现了异常!if (fis != null) { // 避免空指针异常!try {// close()方法有异常,采用捕捉的方式。fis.close();} catch (IOException e) {e.printStackTrace();}}}System.out.println("hello kitty!");}
}

**再次强调:**放在finally语句块中的代码是一定会执行的!!!

public class ExceptionTest11 {public static void main(String[] args) {/*try和finally,没有catch可以吗?可以。try不能单独使用。try finally可以联合使用。以下代码的执行顺序:先执行try...再执行finally...最后执行 return (return语句只要执行方法必然结束。)*/try {System.out.println("try...");return;} finally {// finally中的语句会执行。能执行到。System.out.println("finally...");}// 这里不能写语句,因为这个代码是无法执行到的。//System.out.println("Hello World!");}
}

先执行try…
再执行finally…
最后执行 return(return语句只要执行方法必然结束)

退出JVM finally语句不执行

public class ExceptionTest12 {public static void main(String[] args) {try {System.out.println("try...");// 退出JVMSystem.exit(0); // 退出JVM之后,finally语句中的代码就不执行了!} finally {System.out.println("finally...");}}
}

finally面试题

public class ExceptionTest13 {public static void main(String[] args) {int result = m();System.out.println(result); //100}/*java语法规则(有一些规则是不能破坏的,一旦这么说了,就必须这么做!):java中有一条这样的规则:方法体中的代码必须遵循自上而下顺序依次逐行执行(亘古不变的语法!)java中海油一条语法规则:return语句一旦执行,整个方法必须结束(亘古不变的语法!)*/public static int m(){int i = 100;try {// 这行代码出现在int i = 100;的下面,所以最终结果必须是返回100// return语句还必须保证是最后执行的。一旦执行,整个方法结束。return i;} finally {i++;}}
}/*
反编译之后的效果
public static int m(){int i = 100;int j = i;i++;return j;
}*/

java语法规则(有一些规则是不能破坏的,一旦这么说了,就必须这么做!)
java中有一条这样的规则:
方法体中的代码必须遵循自上而下顺序依次逐行执行(亘古不变的语法!)
java中还有一条语法规则:
return语句一旦执行,整个方法必须结束(亘古不变的语法!)

final finally finalize有什么区别

final 关键字
final修饰的类无法继承
final修饰的方法无法覆盖
final修饰的变量不能重新赋值

finally 关键字
和try一起联合使用
finally语句块中的代码是必须执行的

finalize 标识符
是一个Object类中的方法名
这个方法是由垃圾回收器GC负责调用的

java中自定义异常

1、SUN提供的JDK内置的异常肯定是不够用的。在实际的开发中,有很多业务,这些业务出现异常之后,JDK中都是没有的,和业务挂钩的。那么异常类我们可以自己定义吗?可以。

2、java中怎么自定义异常呢?
两步:
第一步:编写一个类继承Exception或者RuntimeException。
第二步:提供两个构造方法,一个无参数的,一个带有String参数的。

public class MyException extends Exception{ // 编译时异常public MyException(){}public MyException(String s){super(s);}
}/*
public class MyException extends RuntimeException{ // 运行时异常}*/public class ExceptionTest15 {public static void main(String[] args) {// 创建异常对象(只new了异常对象,并没有手动抛出)MyException e = new MyException("用户名不能为空!");// 打印异常堆栈信息e.printStackTrace();// 获取异常简单描述信息String msg = e.getMessage();System.out.println(msg);}
}

自定义异常在实际开发中的作用

异常最重要的案例,必须掌握:

/*** 栈操作异常:自定义异常!*/
public class MyStackOperationException extends Exception{ // 编译时异常!public MyStackOperationException(){}public MyStackOperationException(String s){super(s);}}/*编写程序,使用一维数组,模拟栈数据结构。要求:1、这个栈可以存储java中的任何引用类型的数据。2、在栈中提供push方法模拟压栈。(栈满了,要有提示信息。)3、在栈中提供pop方法模拟弹栈。(栈空了,也有有提示信息。)4、编写测试程序,new栈对象,调用push pop方法来模拟压栈弹栈的动作。5、假设栈的默认初始化容量是10.(请注意无参数构造方法的编写方式。)*/
public class MyStack {// 向栈当中存储元素,我们这里使用一维数组模拟。存到栈中,就表示存储到数组中。// 因为数组是我们学习java的第一个容器。// 为什么选择Object类型数组?因为这个栈可以存储java中的任何引用类型的数据// new Animal()对象可以放进去,new Person()对象也可以放进去。因为Animal和Person的超级父类就是Object。// 包括String也可以存储进去。因为String父类也是Object。private Object[] elements;// 栈帧,永远指向栈顶部元素// 那么这个默认初始值应该是多少。注意:最初的栈是空的,一个元素都没有。//private int index = 0; // 如果index采用0,表示栈帧指向了顶部元素的上方。//private int index = -1; // 如果index采用-1,表示栈帧指向了顶部元素。private int index;/*** 无参数构造方法。默认初始化栈容量10.*/public MyStack() {// 一维数组动态初始化// 默认初始化容量是10.this.elements = new Object[10];// 给index初始化this.index = -1;}/*** 压栈的方法* @param obj 被压入的元素*/public void push(Object obj) throws MyStackOperationException {if(index >= elements.length - 1){// 改良之前//System.out.println("压栈失败,栈已满!");//return;// 创建异常对象//MyStackOperationException e = new MyStackOperationException("压栈失败,栈已满!");// 手动将异常抛出去!//throw e; //这里捕捉没有意义,自己new一个异常,自己捉,没有意义。栈已满这个信息你需要传递出去。// 合并(手动抛出异常!)throw new MyStackOperationException("压栈失败,栈已满!");}// 程序能够走到这里,说明栈没满// 向栈中加1个元素,栈帧向上移动一个位置。index++;elements[index] = obj;// 在声明一次:所有的System.out.println()方法执行时,如果输出引用的话,自动调用引用的toString()方法。System.out.println("压栈" + obj + "元素成功,栈帧指向" + index);}/*** 弹栈的方法,从数组中往外取元素。每取出一个元素,栈帧向下移动一位。* @return*/public void pop() throws MyStackOperationException {if(index < 0){//System.out.println("弹栈失败,栈已空!");//return;throw new MyStackOperationException("弹栈失败,栈已空!");}// 程序能够执行到此处说明栈没有空。System.out.print("弹栈" + elements[index] + "元素成功,");// 栈帧向下移动一位。index--;System.out.println("栈帧指向" + index);}// set和get也许用不上,但是你必须写上,这是规矩。你使用IDEA生成就行了。// 封装:第一步:属性私有化,第二步:对外提供set和get方法。public Object[] getElements() {return elements;}public void setElements(Object[] elements) {this.elements = elements;}public int getIndex() {return index;}public void setIndex(int index) {this.index = index;}
}public class ExceptionTest16 {public static void main(String[] args) {// 创建栈对象MyStack stack = new MyStack();// 压栈try {stack.push(new Object());stack.push(new Object());stack.push(new Object());stack.push(new Object());stack.push(new Object());stack.push(new Object());stack.push(new Object());stack.push(new Object());stack.push(new Object());stack.push(new Object());// 这里栈满了stack.push(new Object());} catch (MyStackOperationException e) {// 输出异常的简单信息。System.out.println(e.getMessage());}// 弹栈try {stack.pop();stack.pop();stack.pop();stack.pop();stack.pop();stack.pop();stack.pop();stack.pop();stack.pop();stack.pop();// 弹栈失败stack.pop();} catch (MyStackOperationException e) {System.out.println(e.getMessage());}}
}

异常与方法覆盖

重写(覆盖)之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少,甚至不抛出。

class Animal {public void doSome(){}public void doOther() throws Exception{}
}class Cat extends Animal {// 编译正常。运行时异常都可以抛public void doSome() throws RuntimeException{}// 编译报错。/*public void doSome() throws Exception{}*/// 编译正常。/*public void doOther() {}*/// 编译正常。/*public void doOther() throws Exception{}*/// 编译正常。public void doOther() throws NullPointerException{}
}

例子

例1:编写程序模拟用户注册:
1、程序开始执行时,提示用户输入“用户名”和“密码”信息。
2、输入信息之后,后台java程序模拟用户注册。
3、注册时用户名要求长度在[6-14]之间,小于或者大于都表示异常。

/*** 自定义异常*/
public class IllegalNameException extends Exception {public IllegalNameException(){}public IllegalNameException(String s){super(s);}
}/*
用户业务类,处理用户相关的业务:例如登录、注册等功能。*/
public class UserService {/*** 用户注册* @param username 用户名* @param password 密码* @throws IllegalNameException 当用户名为null,或者用户名长度小于6,或者长度大于14,会出现该异常!*/public void register(String username, String password) throws IllegalNameException {/*引用等于null的这个判断最好放到所有条件的最前面。*//*if(username == null || username.length() < 6 || username.length() > 14){}*//*再分享一个经验:username == null 不如写成 null == username"abc".equals(username) 比 username.equals("abc") 好。*//*if(null == username || username.length() < 6 || username.length() > 14){}*/if(null == username || username.length() < 6 || username.length() > 14) {/*System.out.println("用户名不合法,长度必须在[6-14]之间");return;*/throw new IllegalNameException("用户名不合法,长度必须在[6-14]之间");}// 程序能够执行到此处说明,用户名合法System.out.println("注册成功,欢迎["+username+"]");}
}public class Test {public static void main(String[] args) {// 创建UserService对象UserService userService = new UserService();// 用户名和密码就不再从控制台接收了try {userService.register("jack", "123");} catch (IllegalNameException e) {//System.out.println(e.getMessage());e.printStackTrace();}}
}

插曲笔记:
类在强制类型转换过程中,如果是类转换成接口类型,那么类和接口之间不需要存在继承关系也可以转换,java语法中允许。

什么是UML

UML(Unified Modeling Language)是一种统一建模语言。一种图标式语言(画图的)。
UML不是只有java中使用,只要是面向对象的编程语言,都有UML。
一般画UML图的都是软件架构师或者系统分析师,这些级别的人员使用的。软件设计人员使用UML。

在UML图中可以描述类和类之间的关系,程序执行的流程,对象的状态等。

【JavaSE进阶(上)】自学笔记 记得收藏时时回顾相关推荐

  1. 【JavaSE进阶(下)】自学笔记 记得收藏时时回顾

    集合 集合概述 1.什么是集合?有什么用? 数组其实就是一个集合.集合实际上就是一个容器,可以来容纳其他类型的数据. 集合为什么说在开发中使用较多? 集合是一个容器,是一个载体,可以一次容纳多个对象. ...

  2. Java自学笔记——Java面向对象——04.抽象类、接口、内部类

    Java 面向对象各节 Java自学笔记--Java面向对象--01.回顾方法 Java自学笔记--Java面向对象--02.构造器.类和对象 Java自学笔记--Java面向对象--03.封装.继承 ...

  3. JavaSE自学笔记016_Real(多线程)

    JavaSE自学笔记016_Real(多线程) 一.进程与线程 1.进程 一个正在执行中的程序叫做一个进程.系统会为了这个进程发配独立的[内存资源],进程是程序的依次执行过程,他有着自己独立的生命周期 ...

  4. JavaSE自学笔记013_Real(抽象类、接口、两种设计模式)

    JavaSE自学笔记013_Real(抽象类.接口) 一.基本概述 (一)抽象类(关键字:abstract) //抽象方法 public abstract class Animal{abstract ...

  5. JavaSE自学笔记Real_004

    JavaSE自学笔记Real_004 封装 Private get set public class Fengzhuang {public static void main(String[] args ...

  6. JavaSE自学笔记Real_008(多线程基础)

    JavaSE自学笔记Real_008(多线程基础) 线程的优先级设置(priority) 线程的优先级用数字表示,范围是1到10(在范围之外会报错) Thread.MIN_PRIORITY = 1 T ...

  7. JavaSE进阶学习笔记-目录汇总(待完成)

    声明:此博客来自于黑马程序员学习笔记,并非商用,仅仅是为了博主个人日后学习复习用,如有冒犯,请联系qq208820388立即删除博文,最后,来跟我一起喊黑马牛逼黑马牛逼黑马牛逼 JavaSE进阶学习笔 ...

  8. JavaSE 进阶 - 第23章 IO流

    JavaSE 进阶 - 第23章 IO流 1.IO流,什么是IO? 2.IO流的分类 3.流应该怎样学习? 4.java IO流的四大家族 5.java.io包下需要掌握的16个流 5.1 FileI ...

  9. 【V-REP自学笔记(八)】控制youBot抓取和移动物体

    [V-REP自学笔记(八)]控制youBot抓取和移动物体 [导读] 在这一系列的V-REP自学笔记中,我们定了一个小目标,完成一个Demo.使用官方提供的KUKA公司的YouBot机器人模型来实验机 ...

最新文章

  1. value_counts()
  2. Codeforces Round #521 (Div. 3)
  3. int 互换 java_Java基础中Int类型变量值互换的几种方法
  4. linux centos更换用户名和密码忘记了,centos7系统中忘记root管理员账号密码,怎么修改密码的解决方式...
  5. IISExpress Log 文件路径
  6. 快速搭建CentOS+ASP.NET Core环境支持WebSocket
  7. oom 如何避免 高并发_微博短视频百万级高可用、高并发架构如何设计?
  8. libcurl上传文件
  9. Python数据清理之数据质量
  10. Cover团队在Kovan以太坊测试网部署xCOVER智能合约
  11. Arm 与中国联通成功部署物联网设备管理平台解决方案
  12. Alex 的 Hadoop 菜鸟教程: 第19课 华丽的控制台 HUE 安装以及使用教程
  13. 无人机飞控系统硬件设计
  14. 微信小程序自定义屏幕调试
  15. 计算机表格出现value,excel中出现#value!的解决办法
  16. 孤军大作战!疯狂DIY 1U硬件防火墙实录(转)
  17. 怎么把多个pdf文件合并成一个pdf?
  18. android app 运行时提示 应用专为旧版 Android 打造
  19. cas:337526-88-2 ;Ir(bt)2 (acac),齐岳提供金属配合物材料
  20. 洛谷 P1566 加等式

热门文章

  1. 微软应用商城下载ShareX老出错
  2. C++ function关键字
  3. mysql null处理_MySQL中处理Null时要注意两大陷阱
  4. Web 开发中地图使用——高德地图定位功能
  5. 如何安装R以及RStudio?打开RStudio页面告诉你没安装R或者出现页面空白问题
  6. 21款奔驰S400商务型升级原厂HUD抬头显示系统,提升行车安全性
  7. 可用在404 的几句诗词
  8. python arduino 微信_MicroPython动手做(27)——物联网之微信小程序
  9. PikPak离线下载,磁力网盘
  10. element ui 级联选择器,渲染后不显示数据