Java进阶 - Map集合、异常处理、线程进程引入
1.Map主要实现类 HashMap<K,V>(无序集合) 集合底层是哈希表 由数组加单向链表或红黑树
HashMap主要子类 LinkedHashMap<K,V> (可预知的迭代顺序)集合底层是哈希表加链表 存储和取出元素的顺序是一致的
Map接口中定义了很多方法,常用的如下:
(1)public V put(K key, V value) : 把指定的键与指定的值添加到Map集合中。
存键值对的时候,key不重复,返回值V是null
存键值对的时候,key重复,会使用新的value替换旧的,返回被替换的value
(2)public V remove(Object key) : 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
删除时,如果key存在,则返回其value值
删除时,如果key不存在,则返回null
(3)public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
取值时,key存在,则返回对应的值
取值时,如果不存在,则返回null
(4)public boolean containsKey(Object key):判断集合中是否包含指定的键
(5)public Set<K> keySet() : 获取Map集合中所有的键,存储到Set集合中。
for(String s : map.keySet()){ System.out.println(s + map.get(s)); }
(6)public Set<Map.Entry<K,V>> entrySet() : 获取到Map集合中所有的键值对对象的集合(Set集合)。
键值对方式:即通过集合中每个键值对(Entry)对象,获取键值对(Entry)对象中的键与值。
操作步骤与图解:
1. 获取Map集合中,所有的键值对(Entry)对象,以Set集合形式返回。方法提示: entrySet() 。
2. 遍历包含键值对(Entry)对象的Set集合,得到每一个键值对(Entry)对象。
3. 通过键值对(Entry)对象,获取Entry对象中的键与值。 方法提示: getkey() getValue()
public class DemoMap { public static void main(String[] args) { show(); } public static void show(){ Map<String,String> map = new HashMap<>(); String v = map.put("姓名", "李晨"); map.put("成龙","中国"); map.put("吴彦祖","中国"); Set<Map.Entry<String, String>> entrySet = map.entrySet(); //第一种方法,迭代器比那里 Iterator<Map.Entry<String, String>> iterator = entrySet.iterator(); while (iterator.hasNext()){ Map.Entry<String, String> entry = iterator.next(); String key = entry.getKey(); String value = entry.getValue(); System.out.println(key + "=" + value); } //第二种方法,增强for循环遍历 for(Map.Entry<String,String> entry : entrySet){ //从entrySet集合中拿到entry对象 String key = entry.getKey(); String value = entry.getValue(); System.out.println(key + "=" + value); } } }
HashMap存储自定义类型键值:
Map集合需保证键是唯一的:
作为Key的元素,必须重写hashcode和equals方法,以保证key唯一
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class DemoMap2 { public static void main(String[] args) { method02(); } private static void method02() { //key用Person类来做,person类就必须重写Hashcode和equals方法 HashMap<Person,String> map = new HashMap<>(); map.put(new Person("女王",18),"英国"); map.put(new Person("秦始皇",22),"秦国"); map.put(new Person("普京",25),"俄国"); map.put(new Person("女王",18),"毛里求斯"); Set<Map.Entry<Person, String>> entrySet = map.entrySet(); for(Map.Entry<Person,String> entry : entrySet){ Person key = entry.getKey(); String value = entry.getValue(); System.out.println(value + " - - > " + key); } } public static void method01(){ //创建HashMap对象,因为String作为Key已经重写了hashcode方法和equals方法,所以key不会出现重复 HashMap<String,Person> map = new HashMap<>(); map.put("北京",new Person("张三",22)); map.put("上海",new Person("李四",18)); map.put("广州",new Person("王五",25)); } }
Map集合练习
需求:计算一个字符串中每个字符出现次数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class MapTest { public static void main(String[] args) { //友情提示 System.out.println("请录入一个字符串:"); String line = new Scanner(System.in).nextLine(); // 定义 每个字符出现次数的方法 findChar(line); } private static void findChar(String line) { //1:创建一个集合 存储 字符 以及其出现的次数 HashMap<Character, Integer> map = new HashMap<Character, Integer>(); //2:遍历字符串 for (int i = 0; i < line.length(); i++) { char c = line.charAt(i); //判断 该字符 是否在键集中 if (!map.containsKey(c)) {//说明这个字符没有出现过 //那就是第一次 map.put(c, 1); } else { //先获取之前的次数 Integer count = map.get(c); //count++; //再次存入 更新 map.put(c, ++count); } } System.out.println(map); } }
Map斗地主案例:
LinkedHashMap(HashMap的子类)
我们知道HashMap保证成对元素唯一,并且查询速度很快,可是成对元素存放进去是没有顺序的,那么我们要保证有序,还要速度快怎么办呢?
在HashMap下面有一个子类LinkedHashMap,它是链表和哈希表组合的一个数据存储结构。
2.Hashtable<K,V>集合 底层也是一个哈希表,是一个线程安全的集合,单线程的集合,速度慢
之前学的所有的集合都可以存储空,包括HashMap能存储空值空键,但HashTable不行,不能存储空值
Hashtable(HashMap取代了它)和vector(Arraylist取代)一样,在JDK1.2之后,被更先进的集合取代,但其子类Properties依然活跃在历史舞台
Properties集合是一个唯一和IO流相结合的集合
3.JDK9中的集合优化 of方法 只适用于List,Map,set接口 不适用与其子类
(1)of方法返回值是一个不可改变的集合,不能使用add和put方法
(2)Set和Map接口在调用of方法时,不能有重复的元素
斗地主案例Map版本
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package basicpart.day01.DataStructure; import java.util.*; public class MapDouDiZhu { public static void main(String[] args) { //通过pai方法获取牌 Map<Integer, String> map = pai(); System.out.println(map); //获取一个打乱牌的顺序 List<Integer> order = getOrder(); System.out.println(order); //遍历顺序集合去发牌 gameSet(map, order); } private static void gameSet(Map<Integer, String> map, List<Integer> order) { List<String> player01 = new ArrayList<>(); List<String> player02 = new ArrayList<>(); List<String> player03 = new ArrayList<>(); List<String> dipai = new ArrayList<>(); for (int i = 0; i < order.size(); i++) { if(i >= 51){ String s = map.get(order.get(i)); dipai.add(s); } else if(i %3 ==0){ String s = map.get(order.get(i)); player01.add(s); } else if(i %3 == 1){ String s = map.get(order.get(i)); player02.add(s); } else if(i %3 ==2){ String s = map.get(order.get(i)); player03.add(s); } } System.out.println(dipai); System.out.println(player01); System.out.println(player02); System.out.println(player03); } private static Map<Integer, String> pai() { List<String> colors = List.of("♦", "♣", "♥", "♠"); List<String> numbers = List.of("2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3"); List<String> pocker = new ArrayList<>(); pocker.add("大王"); pocker.add("小王"); for (String number : numbers) { for (String color : colors) { pocker.add(color + number); } } Map<Integer, String> map = new HashMap<>(); for (int i = 0; i <= 53; i++) { map.put(i, pocker.get(i)); } return map; } public static List<Integer> getOrder() { List<Integer> order = new ArrayList<>(); for (int i = 0; i < 54; i++) { order.add(i); } Collections.shuffle(order); return order; } }
4.异常处理
异常(Exception)的分类:根据在编译时期还是运行时期去检查异常?
编译时期异常:checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常)
运行时期异常:runtime异常。在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)。(如数学异常)
throw:
使用格式:throw new xxxException(“异常产生的原因”)
注意:
1.throw关键字必须写在方法的内部
2.new 后边的对象必须是Exception或者Exception的子类对象
3.throw关键字抛出指定的异常对象,我们就必须处理处理这个异常对象,如果是RuntimeException或其子类异常,可以不处理,交给JVM
如果是编译异常,就必须处理,要么throws,要么try...catch...
注意:空指针异常和索引越界异常都是运行期异常,我们不用处理,默认交给JVM处理
Objects非空判断
还记得我们学习过一个类Objects吗,曾经提到过它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的),那么在它的源码中,对对象 为null的值进行了抛出异常操作。
public static <T> T requireNonNull(T obj) :查看指定引用对象不是null。
5.声明异常throws(交给别人处理,谁调用的方法,就交给谁,最终给JVM处理)
声明异常:将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理(稍后讲
解该方式),那么必须通过throws进行声明,让调用者去处理。
关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).
声明异常格式:修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }
public class DemoException { public static void main(String[] args) throws FileNotFoundException { readFile("c://a.txt"); } private static void readFile(String fileName) throws FileNotFoundException { if(!fileName.equals("d://a.txt")) { throw new FileNotFoundException("路径错误"); } } }
注意:FileNotFoundException是编译异常,找到了就要进行处理,IOException也是,且是它的父类,两个都出现是,抛出父类异常就可以了
6.捕获异常 try...catch...
try-catch的方式就是捕获异常。
捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。
捕获异常语法如下:
public class DemoException { public static void main(String[] args) { try{ readFile("c://a.txt");} catch (FileNotFoundException e){ System.out.println("catch -- 传递的文件路径错误"); } System.out.println("程序继续"); } private static void readFile(String fileName) throws FileNotFoundException { if(!fileName.equals("d://a.txt")) { throw new FileNotFoundException("路径错误"); } } }
如何获取异常信息:
Throwable类中定义了一些查看方法:
apublic String getMessage() :获取异常的描述信息,原因(提示给用户的时候,就提示错误原因。
public String toString() :获取异常的类型和异常描述信息(不用)。
public void printStackTrace() :打印异常的跟踪栈信息并输出到控制台。
包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。
7.finally代码块 一般用于资源回收 无论程序是否出现异常,都会执行该代码块
必须和try一起使用
使用一个try,多个catch时:
若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。 (因为会出现多态)
(1)如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
(2)父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
8.自定义异常
异常类如何定义:
1. 自定义一个编译期异常: 自定义类 并继承于 java.lang.Exception 。
2. 自定义一个运行时期的异常类:自定义类 并继承于 java.lang.RuntimeException 。
9.多线程
并发与并行:
并发:指两个或多个事件在同一个时间段内发生。(交替执行)
并行:指两个或多个事件在同一时刻发生(同时发生)。
线程与进程:
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运 行一个程序即是一个进程从创建、运行到消亡的过程。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。
一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
开启多线程:
1.先创建一个线程继承Thread: public class MyThread01 extends Thread { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("run" + i); } } } 2.创建其子类,并执行start方法启动线程 public class DemoThread { public static void main(String[] args) { MyThread01 thread1 = new MyThread01(); Person p1 = new Person("chris"); p1.run(); thread1.start(); } }
内存图解: