一、异常
1.1异常的基本处理
1.抛出异常:throw
2.捕获异常:try-catch
1.2异常的作用
1.定位程序bug的关键信息
2.可以作为方法内部的一种特殊返回值,通知给上层调用,方便处理
//需求:将两个数的除返回
public class Demo1 {public static void main(String[] args) {try{System.out.println(div(5,0));System.out.println("程序运行成功");}catch(RuntimeException e){e.printStackTrace();System.out.println("底层方法运行失败");}}public static int div(int a , int b){if(b==0){ //当出现异常时,返回给上层调用者,还能告知上层调用,底层是否执行成功//注意,不能直接在这return,因为必须返回一个int值throw new RuntimeException("除数不能为0!"); //抛出运行异常//throw new Exception("除数不能为0!") 抛出编译式异常//注意,抛出了异常后,必须到上层调用的地方处理异常,使用try-catch语句};int res = a / b;return res;}
}
1.2 自定义异常
处理开发中自己的问题,需要自己来定义异常类,来代表某种问题。
可以继承RuntimeException或是直接继承Exception。
语法:
定义一个异常类继承Exception,重写构造器,通过throw new 异常类(XXX)来创建异常对象并抛出。
直接继承编译式异常,在编译阶段就报错,提醒比较激进。
//认识自定义异常
//需求:公司的系统中,接收到年龄大于150岁,就是一个异常
public class ExceptionDemo2 {public static void main(String[] args) {try{printAge(180);System.out.println("运行成功");}catch(Age_ILLegal_Exception e){e.printStackTrace();System.out.println("失败了");}}public static void printAge(int age) throws Age_ILLegal_Exception{if(age > 120){throw new Age_ILLegal_Exception("年龄不能高于120!!!");}else{System.out.println(age);}}
}
自定义运行时异常,语法跟上面无异,就是继承的RuntimeException即可。
小区别:编译时异常,必须显式的抛出(方法定义那throws)并处理,runtime不需要显式地抛出和处理。
事实上,继承RuntimeException更好。
1.3异常的处理方案
方案一:底层异常层层往上抛,最外层捕获异常,记录下异常信息,并响应适合用户观看的信息进行提示。推荐
方案二:最外层捕获异常,尝试重新修复,代码如下:
public class ExceptionDemo2 {public static void main(String[] args) {boolean flag = true;while(flag){try{printAge();System.out.println("运行成功");flag = false;}catch(Age_ILLegal_Exception e){e.printStackTrace();System.out.println("失败了,再来一遍");}}}
直到运行成功,才把flag设置为false,以避免异常影响。
二、泛型
泛型是:定义类,接口,方法时,同时声明了一个或多个类型变量 如: <E>.
称为泛型类,泛型接口,泛型方法,统称为泛型。
作用:泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力!这样可以避免强制类型转换,及可能出现的异常。
泛型的本质:把具体的数据类型作为参数传递给类型变量E,以约束传递的数据类型。
1.1泛型类
使用了泛型的类型变量,就是泛型类,比如:修饰符 class 类名<类型变量,类型变量。。。。>{}
public class ArrayList<E,T,K,V> (一般用大写)
public class MyArrayList<E> {public boolean add(E e) {return true;}
}
//学会自定义泛型类
//需求:模拟ArrayList集合,自定义一个集合MyListpublic class demo2 {public static void main(String[] args) {MyArrayList<String> list = new MyArrayList<>();System.out.println(list.add("acssa"));}
}//true
1.2泛型接口
修饰符 interface 接口名<类型变量,类型变量。。。>{}
//这就是一个泛型接口,可以操作不同类型的变量:比如老师,学生。public interface Data <T>{void add(T t);void remove(T t);void update(T t);T query(int id);
}
public class StudentData implements Data<student>{@Overridepublic void add(student student) {}@Overridepublic void remove(student student) {}@Overridepublic void update(student student) {}@Overridepublic student query(int id) {return null;}
}
StudentData类,来管理学生数据,用它去实现接口即可,StudentData类里的所有方法都重写接口,传入的参数是student。同理,管理老师的数据,新建一个TeacherData类即可。
1.3泛型方法、通配符、上下限
泛型方法:
修饰符<类型变量,类型变量...> 返回值类型 方法名 (形参列表){ }
//目标:学会定义泛型方法,搞清楚作用
//需求:打印任意数组的内容
public class demo {public static void main(String[] args) {}// public static void printArray(int[] arr) {} 如果这样写,只能打印int类型的数组了,不能打印其他类型的数组public static <T> void printArray(T[] arr) {//使用泛型,自定义数组里的数据类型,}public static <T> T getMax(T[] arr) {return arr[0];}
}
通配符:
就是"?"(问号在java中叫做通配符),可以在使用泛型时,代表一切类型。E,T,K,V是在定义泛型的时候使用
泛型上限:
? extends Car : 代表?能接受的必须是Car及其子类
? super Car :代表 ? 能接受的必须是car及其父类
//理解通配符和上下限
//需求,开发一个极品飞车游戏,很多类型的车来比赛
public class demo2 {public static void main(String[] args) {ArrayList list = new ArrayList();go(list);}public static void go(ArrayList < ? extends Objects> list) {//这里是在使用泛型,代表ArrayList里接收的是一切继承Objects的对象}}
1.4泛型支持的类型、包装类
泛型不支持基本数据类型(int ,String,double... ),只能支持对象类型(引用数据类型).
由此引出了包装类,将基本数据类型包装成对象类型,使得泛型可以支持。
包装类:
把基本类型包装成包装类对象:
1.手工包装:
Integer it = Integer.valueof(100);//推荐
2.自动装箱与自动拆箱
自动装箱:基本数据类型的数据可以直接变成包装对象的数据,不需要其他操作
Integer it = 100; 这里就等于 Integer it = Integer.valueof();
自动拆箱:把包装类型的对象直接给基本类型的数据
Integer it2 = 133;
int it3 = it2;
把133赋值给it3
ArrayList<Integer> list = new ArrayList<>();指定arraylist中只能装整数
list.add(123);添加整数,实际上添加的是包装类对象
包装类本身也具有它的功能
比如:
1.将基本类型的数据转化成字符串类型
int j =23;
String rs = Integer.toString(j); 输出"23"
2.把字符串类型的数值,转换成数值本身对应的真实数据类型(常用)
public static int parseInt(String s)
public static Integer valueOf(String s)
这两个方法都可以
String s = 98;int res = Integer.parseInt(s);
int res = Integer.valueof(s);
把字符串转化成98整数
三、集合框架
集合是一种容器,它的大小可变,十分常用.
集合体系:
Collection:单列集合,每个元素只包含一个值
Map:双列集合,每个元素包含两个值(键值对)
Collection集合的常用功能:
先学Colletion集合的功能,因为它的子类会继承它的功能
3.1Colletion的三种遍历方式
1.迭代器遍历:迭代器是用来遍历集合的专属方式,在JAVA中,迭代器的代表是Iterator。
Collection集合获取迭代器的方法:Iterator<E> iterator() 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素。
Iterator迭代器中的常用方法:
boolean hasNext() 访问当前位置是否有元素存在,若有,返回true
E next() 用于获取当前位置的元素
public class CollectionTraversal {public static void main(String[] args) {Collection<Integer> ls = new ArrayList<>();ls.add(1);ls.add(2);ls.add(123);System.out.println(ls);//1.得到迭代器对象Iterator<Integer> it = ls.iterator();while (it.hasNext()) {System.out.println(it.next());} //问当前元素是否存在!!!取出当前元素移动到下一位,初始默认指向第一个元素的位置// 不要以为hasNext是问的下一个元素
2.增强for循环
for (元素的数据类型 变量名 : 数组或者集合){}
3.Lambda表达式
需要结合Collection的forEach方法来完成
//2.使用forEach方法遍历ls.forEach(s -> System.out.println(s));
三种遍历方式的区别:
认识并发修改异常问题:遍历集合的同时,若又存在增删集合元素的行为时可能会出业务异常,这种现象称为并发修改异常问题。
//目标:认识并发修改异常问题,搞清楚每种遍历的区别
public class demo2 {public static void main(String[] args) {ArrayList<String> ls = new ArrayList<>();ls.add("JAVA入门");ls.add("宁夏枸杞");ls.add("黑枸杞");ls.add("高等数学");ls.add("特级枸杞");//需求:把所有枸杞都删了/* for(int i = 0 ;i < ls.size() ; i ++){String name = ls.get(i);if(name.contains("枸杞")){ls.remove(name);}}System.out.println(ls); //发现并没有删干净![JAVA入门, 黑枸杞, 高等数学]原因:每删除一个元素,后一个元素会补上来,导致漏删。解决方法:倒着遍历,或者正着遍历,每遍历一个元素,i--*///方案2:对于没有索引的集合,使用迭代器遍历并删除,也存在并发修改异常问题//解决方案:使用迭代器自己的方法来删除,不要用集合的方法来删Iterator<String> it = ls.iterator();while (it.hasNext()) {String name = it.next();if(name.contains("枸杞")){it.remove();}}System.out.println(ls);//观点:若集合没有索引,想同时遍历并删除,只能使用迭代器;有索引,可以用for遍历并删除//增强for循环,只能用来遍历
3.2 List集合
1.List的特点、特有功能
特点:有序,可重复,有索引。
ArrayList和LinkedList他们都有序,可重复,有索引,但他们的底层实现不同,适合的场景不同
2.ArrayList的底层实现
基于数组存储数据。
数组特点:根据索引查询数据快,查询数据通过地址值和索引定位,但是查询任意数据耗时是相同的。
增删数据的效率低:数组的长度是固定的,而ArrayList长度可变,这就导致增数据需要扩容;删数据要一个一个的把删除数据之后的元素前移。
3.LinkedList的底层实现
基于双向链表存储数据
特点:查询慢,无论查询哪个数都要从头开始找
链表的增删相对快,试想LeetCode做过的那些题。
LinkedList常见的应用场景:
实现队列:先进先出