九、Map接口
Map翻译为映射
从结构图上看,Map并不是集合,而是类似两个集合的映射关系,所以Map中没有实现Collection接口
在Map中,要求A集合的每一个元素(key)都可以在B集合中找到唯一的值(value)与之对应,意味着A集合中的元素是不可以重复的而B集合中的元素却可以重复,所以A集合应该是一个Set集合,而B集合是一个List集合
其实一个Map中就由很多的key-value(kv键值对)组成的,每一个键值对我们用Entry表示
其实我们也可以把Map看成是多个Entry的集合
总的来说,我们还是习惯把Map称为集合,不过和List、Set不同,List、Set是单元素集合,Map是双元素集合
- 单元素集合:每一次只能存储一个元素,比如List、Set
- 双元素集合:每次需要存储两个元素(一个key一个value),比如Map注意:
- Map接口并没有继承Collection接口也没有继承Iterable(可迭代的)接口,所以不可以直接对Map使用for-each遍历操作
- 其中value就表示存储的数据,而key就是这个value的名字
9.1、Map常用的API
9.1.1、添加操作
- boolean put(Object key,Object value):存储一个键值对到Map中
- boolean putAll(Map m):把m中的所有键值对添加到当前Map中
9.1.2、删除操作
Object remove(Object key):从Map中删除指定key的键值对,并返回被删除key对应的value
9.1.3、修改操作
无专门的方法,可以调用put方法,存储相同key,不同value的键值对,可以覆盖原来的。
9.1.4、查询操作
- int size():返回当前Map中键值对个数
- boolean isEmpty():判断当前Map中键值对个数是否为0.
- Object get(Object key):返回Map中指定key对应的value值,如果不存在该key,返回null
- boolean containsKey(Object key):判断Map中是否包含指定key
- boolean containsValue(Object value):判断Map中是否包含指定value
- Set keySet():返回Map中所有key所组成的Set集合
- Collection values():返回Map中所有value所组成的Collection集合
- Set entrySet():返回Map中所有键值对所组成的Set集合
9.1.5、Lambda 8表达式集合遍历
map.forEach((k,v)->{ System.out.println(k+" "+v); }); 复制代码
9.2、HashMap
HashMap底层是基于哈希表算法,Map中存储的key对象和HashCode值决定了在哈希表中存储的位置,因为Map中的key是Set,所以不能保证添加的先后顺序,也不允许重复
import java.util.HashMap; import java.util.Map; public class HashMapDemo1{ public static void main(String[] args) { Map<String, String> map = new HashMap<>(); map.put("girl1", "西施"); map.put("girl2", "王昭君"); map.put("girl3", "貂蝉"); map.put("girl4", "杨玉环"); System.out.println("map中有多少键值对:"+map.size()); System.out.println(map); System.out.println("是否包含key为girl1:"+map.containsKey("girl1")); System.out.println("是否包含value为貂蝉:"+map.containsValue("貂蝉")); //替换key为girl3的value值 map.put("girl3", "小乔"); System.out.println(map); //删除key为girl3的键值对 map.remove("girl3"); System.out.println(map); } } 复制代码
9.2.1、Map的迭代遍历
//获取Map中所有的key Set<String> keys = map.keySet(); System.out.println("Map中所有key:"+keys); //获取Map中所有的value Collection<String> values = map.values(); System.out.println("Map中所有value:"+values); //获取Map中所有的key-value(键值对) Set<Entry<String, String>> entrys = map.entrySet(); for (Entry<String, String> entry : entrys) { String key = entry.getKey(); String value = entry.getValue(); System.out.println(key+"->"+value); } 复制代码
9.2.2、练习
统计一个字符串中每隔字符出现次数
package day14_ArrayList2.classing; import java.util.HashMap; import java.util.Scanner; /** * @author Xiao_Lin * @version 1.0 * @date 2020/12/17 16:54 */ public class Test1 { //需求:统计一个字符串中每一个字符出现的次数 public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String str = scanner.nextLine(); HashMap<Character, Integer> map = new HashMap();//新建一个map,map的key存储字符,value存储出现的次数 //进行map的遍历 for (int i = 0 ;i<str.length();i++){ char c = str.charAt(i);//将str字符串遍历并且强转为字符 if (map.containsKey(c)){//map是否包含遍历的字符 Integer value = map.get(c);//如果包含就将原来的字符对应的value(次数)值取出来并且自增 value+=1;//次数自增 map.put(c,value);//将修改后的value放入map集合中 }else { map.put(c,1);//否则将字符放进集合中,并且将次数置为1 } } System.out.println(map); } } 复制代码
9.3、TreeMap
TreeMap底层的key是基于红黑树,因为Map中的key也是一个Set集合,所以不能保证添加的先后顺序,且也不允许重复,因为Map中存储的key会默认使用自然排序(从小到大),和TreeSet一样,除了可以使用自然排序也可以自定义自己的排序
import java.util.Map; import java.util.TreeMap; public class App { public static void main(String[] args) { Map<String, String> map = new TreeMap<>(); map.put("girl4", "杨玉环"); map.put("girl2", "王昭君"); map.put("key1", "西施"); map.put("key3", "貂蝉"); System.out.println(map); //------------------------------------------- map = new TreeMap<>(map); System.out.println(map); } } 复制代码
package day14_ArrayList2.classing; import java.util.Comparator; import java.util.TreeSet; /** * @author Xiao_Lin * @version 1.0 * @date 2020/12/17 15:55 */ public class TreeSet1 { public static void main(String[] args) { //使用自定义的比较器,需要传入一个Comparator接口的实现类,这里使用匿名内部类 TreeSet<String> set = new TreeSet<String>(new Comparator<String>() { @Override public int compare(String s1, String s2) { if (s1.length()==s2.length()){ return s1.compareTo(s2); }else { return s1.length()-s2.length(); } } }); set.add("s12"); set.add("see"); set.add("s1234"); System.out.println(set); } } 复制代码
十、异常
异常是指在程序的运行过程中所发生的不正常现象,它会中断正在运行的程序,异常不是错误,更不是逻辑的错误
异常会导致程序中断进行
Java异常处理机制
Java编程语言使用异常处理机制为程序提供了异常处理的能力,异常处理机制可以保证程序出现异常后,继续向正确的方向运行 。
异常处理的分类
异常处理包含两种代码块:
- try...catch
- try...catch...finally
10.1、异常对象
异常对象是出现异常时的那条语句自动产生的一个对象,由JVM自动创建,异常在Java类中通过Exception
或者其他具体的子类创建,命名规则是:异常类型+Exception,Exception是所有异常的父类,他有如下方法
方法 | 作用 |
toString() | 返回异常类型和异常信息 |
getMessage() | 返回异常信息 |
printStackTrace | 打印堆栈信息(红色) |
堆栈的信息大致如下:
第一句:由异常类型和异常的Message构成
最后一句:异常发生的具体位置
10.2、try...catch
把可能产生异常的代码放到try中,如果代码产生了异常由catch捕获异常,然后交由catch里面的代码请求处理
10.2.1、语法格式
try{ //可能产生异常的代码块 }catch(异常类型 e){//异常由catch捕获 //异常处理 } 复制代码
10.2.2、try...catch执行情况分析
10.2.2.1、无异常
如果没有发现异常,则程序正常执行
10.2.2.2 、异常被catch捕获
程序出现了异常,且异常被catch捕获,也就是说异常的类型符合catch中的类型,此时进行异常处理,处理完成后程序正常执行
package day15_exception.classing.exception; import java.util.Scanner; /** * @author Xiao_Lin * @version 1.0 * @date 2020/12/19 11:31 */ public class TestException { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); try { System.out.println("请输入被除数:"); int num1 = scanner.nextInt(); System.out.println("请输入除数:"); int num2 = scanner.nextInt(); System.out.println(num1/num2); }catch (Exception e){ System.out.println("出现异常"); System.out.println(e.getMessage()); } } } 复制代码
总结
异常发生后,从异常发生的那句代码开始,程序不继续向下运行,立即转入异常处理
10.2.2.3、异常不匹配
异常不匹配时,程序中断
package day15_exception.classing.exception; import java.util.Scanner; /** * @author Xiao_Lin * @version 1.0 * @date 2020/12/19 11:31 */ public class TestException { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); try { System.out.println("请输入被除数:"); int num1 = scanner.nextInt(); System.out.println("请输入除数:"); int num2 = scanner.nextInt(); System.out.println(num1/num2); }catch (ArithmeticException e){//这里我们捕获的是除数异常,但是我们模拟的是InputMismatchException(输入匹配异常),所以程序2到这里并没有匹配到合适的异常中断程序 System.out.println("出现异常"); e.printStackTrace(); } } } 复制代码
10.2.3、多重catch
我们可以为 try 代码书写多个 catch 用于捕获多个具体的异常,但是要注意在书写的时候,子类在上,父类在下,因为Java的代码从上往下执行,没有合适的异常就用最大的异常来进行捕获。
package day15_exception.classing.exception; import java.util.InputMismatchException; import java.util.Scanner; /** * @author Xiao_Lin * @version 1.0 * @date 2020/12/19 11:31 */ public class TestException { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); try { int num1 = scanner.nextInt(); System.out.println("请输入除数:"); int num2 = scanner.nextInt(); int r = num1 / num2; System.out.println(r); }catch (InputMismatchException e) { System.out.println("输入数据有误"); }catch(ArithmeticException e) { System.out.println("除数不能为 0"); }catch(Exception e) { System.out.println("未知异常"); } System.out.println("程序正常结束!"); } } 复制代码
10.3、try...catch...finally
try...catch 和之前一样用于捕获并处理异常,finally 代码块用于处理异常后的收尾工作。
不管是否发生异常,finally 总是执行,但是finally的工作仅仅只是包含释放内存、关闭文件、关闭网络连接、关闭数据库连接等收尾工作。
在finally中不建议书写代码和修改变量的值,因为finally无法修改临时堆栈的值
package day15_exception.classing.exception; import java.util.InputMismatchException; import java.util.Scanner; /** * @author Xiao_Lin * @version 1.0 * @date 2020/12/19 11:31 */ public class TestException { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); try { System.out.println("请输入被除数"); int num1 = scanner.nextInt(); System.out.println("请输入除数:"); int num2 = scanner.nextInt(); int r = num1 / num2; System.out.println(r); }catch (InputMismatchException e) { System.out.println("输入数据有误"); }catch(ArithmeticException e) { System.out.println("除数不能为 0"); }catch(Exception e) { System.out.println("未知异常"); }finally { System.out.println("我是finally"); } System.out.println("程序正常结束!"); } } 复制代码
finally唯一不执行的情况是:JVM正常退出
package day15_exception.classing.exception; import java.util.InputMismatchException; import java.util.Scanner; /** * @author Xiao_Lin * @version 1.0 * @date 2020/12/19 11:31 */ public class TestException { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); try { System.out.println("请输入被除数"); int num1 = scanner.nextInt(); System.out.println("请输入除数:"); int num2 = scanner.nextInt(); int r = num1 / num2; System.out.println(r); } catch (Exception e) { System.out.println(e.toString()); // jvm 正常退出 System.exit(0); } finally { System.out.println("我是 finally"); } System.out.println("程序正常结束!"); } } 复制代码
10.3.1、存在 return 的 try...catch...finally
package day15_exception.classing.exception; import java.util.InputMismatchException; import java.util.Scanner; /** * @author Xiao_Lin * @version 1.0 * @date 2020/12/19 11:31 */ public class TestException { public static int div(int a, int b) { int r = 0; try { r = a / b; return r; } catch (Exception e) { System.out.println(e.toString()); } finally { System.out.println("我是 finally"); } return r; } public static void main(String[] args) { System.out.println(div(10, 0)); System.out.println("程序正常结束!"); } } 复制代码
结论
- 存在 return 的 try...catch...finally中,funally先执行,然后再return
- return总是最后执行的
快捷键
快速打出try...catch的快捷键
- Eclipse : Shift+Alt+Z
- IDEA : Ctrl+Alt+T
10.4、异常分类
异常的继承体系,Throwable 类是 Java 语言中所有错误(Error)或异常的父类。
10.5、Error
Error,表示代码运行时 JVM(Java 虚拟机)出现的问题。如系统崩溃或内存溢出等,不需要处理 Error,我们唯一能做的就是等待,在开发中极少见到,常见的有以下几个
Error类型 | 描述 |
StackOverflowError | 当应用程序递归太深而发生堆栈溢出时,抛出该错误。比如死循环或者没有出口的递归调用 |
OutOfMemoryError | 因为内存溢出或没有可用的内存提供给垃圾回收器时,Java 虚拟机无法分配一个对象,这时抛出该错误。比如 new 了非常庞大数量的对象而没释放。 |
10.6、Exception
Exception
表示程序在运行时出现的一些不正常情况,一般大多数表示轻度到中度的问题,属于可预测、可恢复问题。如除数为 0,数组索引越界等,这种情况下,程序员通过合理的异常处理,确保程序的正常运行直到结束。
异常的构造方法
//构造一个异常信息为null的新异常 Exception(); //构造一个异常信息为 message的新异常 Exception(String message) 复制代码
异常的分类
- 运行时异常
- 检查时异常
10.6.1、运行时异常
RuntimeException(运行时异常)是指在程序运行过程中出现的异常时可处理、可不处理的异常,他的父类是RuntimeException
运行时异常 | 描述 |
InputMismatchException | 输入不匹配异常 |
ArithmeticException | 数学计算异常(比如除数为0) |
IndexOutOfBoundsException | 下标/索引越界异常 |
ArrayIndexOutOfBoundsException | 数组下标越界异常 |
StringIndexOutOfBoundsException | 字符串下标越界 |
NullPointerException | 空指针异常 |
IllegalArgumentException | 方法接收到非法参数 |
ClassCastException | 强制类型转换异常 |
NumberFormatException | 数字格式转换异常,如把"abc"转换成数字 |
10.6.2、检查时异常
检查时异常(Checked Exception):也称编译时异常,指在编译期间检查程序可能存在不正常的情况,在程序运行过程中必须处理,否则编译不通过。在 Java 中没有特定的父类,一般用 Exception 表示检查时异常。
检查时异常 | 描述 |
ParseException | 格式(日期时间等)解析异常 |
ClassNotFoundException | class没找到异常 |
FileNotFoundException | 文件未找到异常 |
IOException | IO异常 |
SQLException | SQL异常 |
UnsupportedEncodingException | 不支持字符串编码异常 |
10.7、声明异常
10.7.1、throws
我们可以通过throws来声明某个方法可能出现的异常。
当方法的定义者在定义方法的时候不知道调用者在调用该方法的时候会出现异常,但是定义者又不知道如何处理时,此时可以选择使用throws关键字来声明异常,可以声明多个异常,用逗号分隔
[修饰符] 返回值类型 方法名(形参列表) throws 异常 1, 异常 2, 、、、{ } 复制代码
声明异常时要根据异常的分类来确定是否外界(调用处)是否必须处理该方法产生的异常。如果需要外界必须处理,需要声明检查时异常,否则声明运行时异常。
package day15_exception.classing.exception; import java.util.InputMismatchException; import java.util.Scanner; /** * @author Xiao_Lin * @version 1.0 * @date 2020/12/19 11:31 */ public class TestException { public static int div(int a, int b) throws Exception{//抛出了检查时异常 int r = 0; r = a / b; return r; } public static void main(String[] args) { System.out.println(div(10, 0));//这里并没有解决异常,会报错,编译无法通过 System.out.println("程序正常结束!"); } } 复制代码
public static void main(String[] args) {//改为这样才可以 try { System.out.println(div(10, 0)); } catch (Exception e) { e.printStackTrace(); } System.out.println("程序正常结束!"); } 复制代码
10.7.2、声明异常与重载的关系
无关系
10.7.3、声明异常和重写的关系
如果父类声明运行时异常,子类可以声明运行时异常或者不声明;如果父类声明检查时异常,子类可以声明检查时异常或者不声明或者运行时异常。
如果父类没有声明任何异常,子类要么不声明任意异常,要么可以声明运行时异常,但不能声明检查时异常。
总结:子类方法的异常 <= 父类方法的异常
10.7.4、异常上抛
在实际开发过程中,如果调用 A 类 方法存在异常,调用者也不知如何处理这个异常时,可以继续向上声明异常,我们把这个过程称为异常的上抛。
在实际中遇到的异常能内部处理的优先处理,无法处理再上抛
public class Sub extends Parent{ @Override public void showInfo() throws Exception{ } } 复制代码
public class Test01 { public static void main(String[] args) throws Exception{ Sub sub = new Sub(); sub.showInfo(); System.out.println("程序正常结束!"); } } 复制代码
10.7.5、手动抛出异常
在实际开发过程中,开发者也可以根据程序的需要,手动抛出异常,通过 throw 关键字,程序员可以主动抛出某种特定类型的异常。
package com.kal02.exception07; public class Student { private String name; private String gender; public void setGender(String gender) throws RuntimeException{ if(gender.equals(Student.MAN)||gender.equals(Student.WOMAN)){ this.gender = gender; }else { // 手动抛出异常 throw new RuntimeException("性别只能是男或者女"); } } } 复制代码
10.8、自定义异常
当 JDK 中的异常类型不能满足程序的需要时(也即需要定义具体的业务异常时),可以自定义异常。
自定义异常的步骤:
- 确定异常类型(检查时异常、运行时异常)。
- 继承异常的父类(检查时异常继承Exception,运行时异常继承RuntimeException)
- 声明构造方法(自定义异常类的构造方法,并且将异常信息传进去,父类的构造方法子类无法继承)
Student类
package day15_exception.classing.customize; /** * @author Xiao_Lin * @date 2020/12/19 17:25 */ public class Student { private String name; private String Gender; public Student(String name, String gender) { this.name = name; Gender = gender; } public Student() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return Gender; } public void setGender(String gender) throws GenderException{ if (gender.equals("男") || gender.equals("女") || gender.equals("保密")){ Gender = gender; }else { throw new GenderException("性别不合法");//s } } } 复制代码
自定义异常类
package day15_exception.classing.customize; /** * @author Xiao_Lin * @date 2020/12/19 17:27 */ public class GenderException extends Exception{ public GenderException() { } public GenderException(String message) { super(message); } } 复制代码
测试类
package day15_exception.classing.customize; /** * @author Xiao_Lin * @date 2020/12/19 17:28 */ public class CustomizeException { public static void main(String[] args) { Student student = new Student("宿舍","钕"); try { student.setGender("宿舍"); } catch (GenderException e) { e.printStackTrace(); } } } 复制代码
10.9、面试题
Java异常中throw和throws的区别
- throws:
- 用来声明一个方法可能产生的所有异常,不做任何处理而是将异常往上传,谁调用我我就传给谁(甩锅)。
- 用在方法声明后面跟的是异常类名。
- 可以声明多个异常,用,隔开。
- throws表示的是一种可能性,仅仅只是说可能会出现这种异常,并不是百分之百会出现这些异常。
- throw:
- throw表示用来抛出某一个具体的异常。
- 用在方法体内,跟的是某一个具体的异常的对象名。
- 只能抛出一个异常对象名。
- 表示抛出了异常,由方法体内的语句处理。
- throw表示出现了异常,则throws表示一定出现了某种异常。