上一篇:10【List、Set、Queue】

下一篇:12【多线程、锁机制、lock锁】

目录:【JavaSE零基础系列教程目录】


文章目录

  • 11【泛型、双列集合、异常】
  • 一、泛型
    • 1.1 泛型概述
    • 1.2 集合泛型的使用
      • 1.2.1 未使用泛型
      • 1.2.2 使用泛型
    • 1.3 泛型类
      • 1.3.1 泛型类的使用
      • 1.2.2 泛型类的继承
    • 1.4 泛型方法
    • 1.5 泛型通配符
      • 1.5.1 通配符的使用
        • 1)参数列表带有泛型
        • 2)泛型通配符
      • 1.5.2 泛型上下边界
    • 1.6 泛型的擦除
      • 1.6.1 限制擦除
      • 1.6.2 泛型的桥接方法
  • 二、Map双列集合
    • 2.1 Map集合概述
    • 2.2 Map接口的共有方法
      • 2.2.1 数据的存取
      • 2.2.2 数据的遍历
      • 2.2.3 Entry对象
    • 2.3 HashMap
      • 2.3.1 HashMap简介
      • 2.3.2 HashMap的去重
    • 2.4 LinkedHashMap
      • 1.4.1 LinkedHashMap 特点
      • 1.4.2 LinkedHashMap 使用
    • 2.4 TreeMap
    • 2.5 Hashtable
      • 2.5.1 Dictionary类
      • 2.5.2 Hashtable与HashMap的区别
  • 三、异常
    • 3.1 异常概述
      • 3.1.1 什么是异常
      • 3.1.2 异常体系
      • 3.1.3 异常分类
    • 3.2 异常的处理
      • 3.2.1 异常的捕获
      • 3.2.2 异常的常用方法
      • 3.2.3 异常的抛出
      • 3.2.4 声明异常
        • 1)运行时异常
        • 2)编译时异常
    • 3.3 自定义异常
    • 3.4 方法的重写与异常
  • 记得点赞~!!!

11【泛型、双列集合、异常】

一、泛型

1.1 泛型概述

泛型定义:把类型明确的工作延迟到创建对象或调用方法的时候才去明确的特殊的类型;

例如,我们知道集合是可以存储任意元素的,那么这样一想,add方法上的参数应该是Object(所有类的父类),但是这样会引入一个新的问题,我们知道,子类都是比父类强大的,我们在使用的时候肯定是希望获取的是当初存进去的具体子类对象;因此我们每次都需要进行强制转换;

但add方法真的是Object吗?

查看ArrayList的add方法:

class ArrayList<E>{ public boolean add(E e){ }public E get(int index){ }....
}

Collection类:

public interface Collection<E> extends Iterable<E> {}

上面的E就是泛型,集合的定义者也不知道我们需要存储什么元素到集合中,具体的类型只能延迟到创建对象时来决定了

1.2 集合泛型的使用

1.2.1 未使用泛型

定义一个Province对象:

package com.dfbz.demo01;/*** @author lscl* @version 1.0* @intro:*/
public class Province {private String name;                        // 名称private String shortName;                    // 简称private String location;                    // 所属区域public void intro() {System.out.println("名称: " + name + ",简称: " + shortName + ",所属地区: " + location);}@Overridepublic String toString() {return "Province{" +"name='" + name + '\'' +", shortName='" + shortName + '\'' +", location='" + location + '\'' +'}';}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getShortName() {return shortName;}public void setShortName(String shortName) {this.shortName = shortName;}public String getLocation() {return location;}public void setLocation(String location) {this.location = location;}public Province() {}public Province(String name, String shortName, String location) {this.name = name;this.shortName = shortName;this.location = location;}
}

测试类:

package com.dfbz.demo01;import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;/*** @author lscl* @version 1.0* @intro:*/
public class Demo01_泛型问题引出 {public static void main(String[] args) {Collection list = new ArrayList();// 往集合存储对象list.add(new Province("台湾", "台","华东"));list.add(new Province("澳门", "澳","华南"));list.add(new Province("香港", "港","华南"));list.add(new Province("河北", "冀","华北"));// 获取迭代器Iterator iterator = list.iterator();while (iterator.hasNext()) {// 集合中的元素都被提升为Object对象了Object obj = iterator.next();// 强制转换为子类Province province = (Province) obj;// 调用子类特有的功能province.intro();}}
}

我们没有给泛型进行明确的定义,对象存储到集合中都被提升为Object类型了,取出来时需要强制转换为具体子类,非常麻烦;

不仅如此,这样的代码还存在这隐藏的风险,集合中可以存储任意的对象,如果往集合中存储其他对象呢?

定义个Book对象:

package com.dfbz.demo01;/*** @author lscl* @version 1.0* @intro:*/
public class Book {private String name;private String author;public void detail() {System.out.println("书名: " + name + ",作者: " + author);}@Overridepublic String toString() {return "Book{" +"name='" + name + '\'' +", author='" + author + '\'' +'}';}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}public Book() {}public Book(String name, String author) {this.name = name;this.author = author;}
}

测试类:

package com.dfbz.demo01;import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;/*** @author lscl* @version 1.0* @intro:*/
public class Demo02_泛型出现的问题 {public static void main(String[] args) {Collection list = new ArrayList();// 添加几个省份list.add(new Province("山西", "晋","华北"));list.add(new Province("河南", "豫","华中"));list.add(new Province("江西", "赣","华东"));// 添加几本书list.add(new Book("《史记》","司马迁"));list.add(new Book("《三国志》","陈寿"));// 获取迭代器Iterator iterator = list.iterator();while (iterator.hasNext()) {// 集合中的元素都被提升为Object对象了Object obj = iterator.next();// 强制转换为子类Province province = (Province) obj;         // 不可以将Book转换为Province(存在隐藏问题)// 调用子类特有的功能province.intro();}}
}

运行结果:

上述代码编译时是没有什么问题,但运行时出现了类型转换异常:ClassCastException,代码存在一定的安全隐患;

1.2.2 使用泛型

  • 查看List源码:
public interface List<E> extends Collection<E> {boolean add(E e);....
}

集合的定义者发现,无法在定义集合类时就确定该集合存储的具体类型,因此使用泛型进行占位,使用者创建集合时明确该泛型的类型;

  • 指定泛型后:
class List<Province>{ boolean add(Province province);....
}

Tips:在创建对象时指定泛型的类型,泛型一旦指定了具体的类型,原来泛型的占位符(E),都将变为此类型;

如何指定泛型类类型?

格式如下:

List<泛型的具体类型> list=new ArrayList();

测试类:

package com.dfbz.demo01;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;/*** @author lscl* @version 1.0* @intro:*/
public class Demo03_使用泛型来存储元素 {public static void main(String[] args) {// 创建集合,并确定好泛型(存储的类型)List<Province> list = new ArrayList();// 往集合存储对象list.add(new Province("甘肃", "甘|陇","西北"));list.add(new Province("陕西", "陕|秦","西北"));list.add(new Province("贵州", "贵|黔","西南"));list.add(new Province("云南", "云|滇","西南"));list.add(new Province("四川", "川|蜀","西南"));// 获取City类型的迭代器Iterator<Province> iterator = list.iterator();while (iterator.hasNext()){// 直接获取province类型Province province = iterator.next();province.intro();}}
}

1.3 泛型类

1.3.1 泛型类的使用

很明显,Collection、List、Set以及其下的子类都是泛型类,我们根据使用情况也可以定义泛型类;让泛型类的类型延迟到创建对象的时候指定;

  • 使用格式:
修饰符 class 类名<代表泛型的变量> {}

例如,API中的List接口:

class List<E>{ boolean add(E e);....
}

在创建对象的时候确定泛型

例如,List<String> list = new ArrayList<String>();

此时,变量E的值就是String类型,那么我们的类型就可以理解为:

public interface List<String> extends Collection<String> {boolean add(String e);...
}

再例如,ArrayList<Integer> list = new ArrayList<Integer>();

此时,变量E的值就是Integer类型,那么我们的类型就可以理解为:

public interface List<Integer> extends Collection<Integer> {boolean add(Integer e);...
}

举例自定义泛型类:

package com.dfbz.demo01;public class GetClass<P> {// 使用泛型类型,P具体的类型还不明确,等到创建GetClass对象时再明确P的类型private P p;public P getC() {return p;}public void setC(P p) {this.p = p;}
}

使用:

package com.dfbz.demo01;/*** @author lscl* @version 1.0* @intro:*/
public class Demo04_自定义泛型类 {public static void main(String[] args) {// 创建对象,并明确泛型类型为CityGetClass<Province> getClass = new GetClass<>();// setC(Province province)getClass.setC(new Province("新疆","新","西北"));// Province getC()Province province = getClass.getC();province.intro();}
}

1.2.2 泛型类的继承

定义一个父类,带有泛型:

public class 类名<泛型类型> {  }

例如,

package com.dfbz.demo01;/*** @author lscl* @version 1.0* @intro:*/
public class Fu<E> {public void show(E e) {System.out.println(e.toString());}
}

使用格式:

  • 1)定义类时确定泛型的类型

例如

package com.dfbz.demo01;/*** @author lscl* @version 1.0* @intro: 在定义子类时就确定好泛型的类型*/
public class Zi1 extends Fu<Province> {@Overridepublic void show(Province province) {System.out.println("zi1: " + province);}
}

此时,泛型E的值就是Province类型。

  • 2)始终不确定泛型的类型,直到创建对象时,确定泛型的类型

例如

package com.dfbz.demo01;/*** @author lscl* @version 1.0* @intro: 子类也变为泛型类,泛型类型具体到创建对象的时候再确定*/
public class Zi2<P> extends Fu<P> {@Overridepublic void show(P p) {System.out.println("zi2: " + p);}
}

确定泛型:

package com.dfbz.demo01;/*** @author lscl* @version 1.0* @intro:*/
public class Demo05_测试泛型类的继承 {public static void main(String[] args) {// 注意: Zi1类以及不是一个泛型类了,不能够指定泛型
//        Zi1<Book> zi1=new Zi1();          // 报错// 创建子类时确定泛型类型Zi2<Province> zi_a = new Zi2<>();zi_a.show(new Province("青海", "青", "西北"));Zi2<Book> zi_b = new Zi2<>();zi_b.show(new Book("《说岳全传》", "钱彩"));}
}

1.4 泛型方法

泛型类是在创建类时定义泛型类型,在创建对象时确定泛型类型;泛型方法则是在创建方法是定义泛型类型,在调用方法时确定泛型类型;

  • 定义格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){}

例如:

package com.dfbz.demo01;/*** @author lscl* @version 1.0* @intro:*/
public class GetClassMethod {// 定义泛型方法public <P> P GetClass(P p) {return p;}
}

使用格式:调用方法时,确定泛型的类型

package com.dfbz.demo01;/*** @author lscl* @version 1.0* @intro:*/
public class Demo06_测试泛型方法 {public static void main(String[] args) {GetClassMethod getClassMethod = new GetClassMethod();// Province getClass(Province province)Province province = getClassMethod.GetClass(new Province("西藏", "藏", "西北"));province.intro();// Book getClass(Book book)Book book = getClassMethod.GetClass(new Book("《天龙八部》", "金庸"));book.detail();}
}

1.5 泛型通配符

1.5.1 通配符的使用

1)参数列表带有泛型

泛型在程序运行中全部会被擦除,我们把这种现象称为泛型擦除;但编译时期在使用泛型类进行方法传参时,不仅要匹配参数本身的类型,还要匹配泛型的类型;

Number是所有数值类的父类:

  • 定义一个泛型类:
package com.dfbz.泛型通配符;/*** @author lscl* @version 1.0* @intro:*/
public class TestClass<E> {private E e;public E getE() {return e;}public void setE(E e) {this.e = e;}
}
  • 测试带有泛型的方法参数列表:
package com.dfbz.泛型通配符;/*** @author lscl* @version 1.0* @intro:*/
public class Demo01_方法参数带有泛型的问题 {public static void main(String[] args) {TestClass<Number> t1 = new TestClass<>();test1(t1);TestClass<Integer> t2 = new TestClass<>();// 符合方法的参数类型(TestClass),但不符合泛型类型(Number),编译报错
//        test1(t2);}public static void test1(TestClass<Number> testClass) {// 可以获取具体的泛型对象Number number = testClass.getE();System.out.println("test1...");}/*和上面的方法冲突了,因为泛型在运行期间会被擦除相当于: public static void test1(TestClass testClass)*//*public static void test1(TestClass<Integer> testClass) {System.out.println("test1...");}
*/}

2)泛型通配符

泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。

  • 示例:
package com.dfbz.泛型通配符;import com.dfbz.demo01.Book;
import com.dfbz.demo01.Province;/*** @author lscl* @version 1.0* @intro:*/
public class Demo02_泛型通配符的使用 {public static void main(String[] args) {TestClass<String> t1 = new TestClass<>();test1(t1);TestClass<Integer> t2 = new TestClass<>();test1(t2);TestClass<Number> t3 = new TestClass<>();test1(t3);TestClass<Province> t4 = new TestClass<>();test1(t4);TestClass<Book> t5 = new TestClass<>();test1(t5);}// ?: 代表可以接收任意泛型public static void test1(TestClass<?> testClass) {// 被?接收的泛型都将提升为ObjectObject obj = testClass.getE();System.out.println("test1...");}
}

Tips:需要注意的是,被<?>接收过的类型都将提升为Object类型;

1.5.2 泛型上下边界

利用泛型通配符?可以接收任意泛型,但是随之而然带来一个问题,就是所有类型都提升为Object;范围太广了,使用起来非常不方便,因此为了让泛型也可以利用多态的特点,泛型的上下边界的概念由此引出;

利用泛型的通配符可以指定泛型的边界;

泛型的上限

  • 格式类型名称 <? extends 类> 对象名称
  • 含义只能接收该类型及其子类

泛型的下限

  • 格式类型名称 <? super 类> 对象名称
  • 含义只能接收该类型及其父类型

可以看到基本数据类型的包装类都是继承与Number类;

  • 测试泛型上下边界:
package com.dfbz.泛型通配符;import java.util.ArrayList;
import java.util.Collection;/*** @author lscl* @version 1.0* @intro:*/
public class Demo03_泛型的上下边界 {public static void main(String[] args) {Collection<Integer> list1 = new ArrayList<Integer>();Collection<String> list2 = new ArrayList<String>();Collection<Number> list3 = new ArrayList<Number>();Collection<Object> list4 = new ArrayList<Object>();getElement1(list1);     // Integer 是Number类型的子类
//        getElement1(list2);     // 报错,String不是Number类型的子类getElement1(list3);     // Number类型可以传递
//        getElement1(list4);     // 报错,Object不是Number类型的子类//        getElement2(list1);     // 报错,Integer不是Number类型的父类
//        getElement2(list2);     // 报错,String不是Number类型的父类getElement2(list3);     // Number类型可以传递getElement2(list4);     // Object是Number类型的父类}// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类public static void getElement1(Collection<? extends Number> coll) {}// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类public static void getElement2(Collection<? super Number> coll) {}
}

1.6 泛型的擦除

Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程称为类型擦除。

1.6.1 限制擦除

泛型在擦除过程中有限制擦除无限制擦除

  • 有限制擦除:

  • 无限制擦除:将泛型类型提升为Object类型

1.6.2 泛型的桥接方法

在接口使用泛型时,在运行期间接口中的泛型也一样会被擦除;

那如果编写了实现类来实现这个泛型接口,实现类中的泛型在运行期间也会被擦除,这样一来就会出现接口的方法并没有在实现类中得到实现:

好在JVM进行了特殊处理,如果我们编写的类实现了一个带有泛型的接口时,在运行期期间JVM会在实现类中帮我们自动的生产一个方法来帮助我们实现泛型接口中被擦除过后的那个方法,这个方法被称为桥接方法

  • 如图所示:

二、Map双列集合

2.1 Map集合概述

Map 用于保存具有映射关系的数据,如一个学生的ID对应一个学生,一个商品的ID对应一个商品,一个部门ID对应多个员工;这种具有对应关系的数据成为映射;

因此 Map 集合里保存着两组值,一组值用于保存 Map 里的 Key,另外一组用于保存 Map 里的 Value,Map 中的 key 和 value 都可以是任何引用类型的数据;

  • Map接口的继承体系:

Tips:Collection接口是单列集合的顶层接口,Map则是双列集合的顶层接口;

2.2 Map接口的共有方法

Map是所有双列集合的顶层父类,因此Map中具备的是所有双列集合的共性方法;常用的方法如下:

  • public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
  • public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
  • public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
  • boolean containsKey(Object key) 判断集合中是否包含指定的键。
  • public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。
  • public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。
  • public Collection<V> values():获取该map集合的所有value

2.2.1 数据的存取

示例代码:

package com.dfbz.demo01;import java.util.Collection;
import java.util.HashMap;public class Demo {public static void main(String[] args) {//创建 map对象HashMap<String, String> map = new HashMap();//添加元素到集合map.put("江西", "南昌");map.put("湖南", "长沙");map.put("湖北", "武汉");// 获取该map集合的所有valueCollection<String> values = map.values();System.out.println(values);             // [长沙, 武汉, 南昌]}public static void test1(){//创建 map对象HashMap<String, String> map = new HashMap();//添加元素到集合map.put("江西", "南昌");map.put("湖南", "长沙");map.put("湖北", "武汉");// 存取是无序的System.out.println(map);        //  {湖南=长沙, 湖北=武汉, 江西=南昌}// String remove(String key): 根据key来删除记录,并将key对应的value返回System.out.println(map.remove("江西"));       // 南昌System.out.println(map);            // {湖南=长沙, 湖北=武汉}// 查看 湖北的省会 是哪座城市System.out.println(map.get("湖北"));  // 武汉System.out.println(map.get("湖南"));  // 长沙}
}

运行结果:

2.2.2 数据的遍历

方法:

  • public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
  • public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。

步骤:

  • 1)根据keySet()方法获取所有key的集合
  • 2)通过foreach方法遍历key集合,拿到每一个key
  • 3)通过get()方法,传递key获取key对应的value;

示例代码:

package com.dfbz.demo01;import java.util.HashMap;
import java.util.Map;
import java.util.Set;/*** @author lscl* @version 1.0* @intro:*/
public class Demo02 {public static void main(String[] args) {Map<String, String> cities = new HashMap<>();//添加元素到集合cities.put("广西", "南宁");cities.put("云南", "昆明");cities.put("贵州", "贵阳");// 1.获取key的集合Set<String> provinces = cities.keySet();       // [贵州, 广西, 云南]System.out.println(provinces);// 2.遍历key的集合for (String province : provinces) {// 3.根据key(省份)拿到value(省会城市)String city = cities.get(province);System.out.println(province + "省的省会是:" + city);}}
}

运行结果:

2.2.3 Entry对象

Map集合中几条记录存储的是两个对象,一个是key,一个是value,这两个对象加起来是map集合中的一条记录,也叫一个记录项;这个记录项在Java中被Entry对象所描述;一个Entry对象中包含有两个值,一个是key,另一个则是key对应的value,因此一个Map对象我们可以看做是多个Entry对象的集合,即一个Set<Entry>对象;

Entry是一个接口,是Map接口中的一个内部接口,源码如下:

interface Entry<K,V> {K getKey();V getValue();V setValue(V value);....
}

HashMap中则提供了Node类对Entry提供了实现,可以看到一个Entry对象(Node对象)中包含有key、value等值:

Map接口中提供有方法获取该Map集合的Entry集合对象:

  • public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。

使用Entry对象来遍历Map集合:

package com.dfbz.demo01;import java.util.HashMap;
import java.util.Map;
import java.util.Set;/*** @author lscl* @version 1.0* @intro:*/
public class Demo03 {public static void main(String[] args) {Map<String, String> cities = new HashMap<>();//添加元素到集合cities.put("山东", "济南");cities.put("山西", "太原");cities.put("河南", "郑州");cities.put("河北", "石家庄");// 1.获取该Map的Entry集合Set<Map.Entry<String, String>> entrySet = cities.entrySet();// 2.遍历该Entry集合,获取每一个entry,也就是Map中的每一条记录for (Map.Entry<String, String> entry : entrySet) {// 获取当前entry对象的key(省份)String province = entry.getKey();// 获取当前entry对象的value(城市)String city = entry.getValue();System.out.println(province + "省的省会是:" + city);}}
}

运行结果:

2.3 HashMap

2.3.1 HashMap简介

HashMap是Map集合中比较常用的实现类,其特点依旧是我们之前学习的HashSet特点;即存储数据采用的哈希表结构(JDK8改为hash表+红黑树),元素的存取顺序不一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。

我们之前学习的HashSet底层就是一个HashMap,当我们在讨论HashSet的底层原理时,其实讨论的是HashMap的key的底层原理;

  • HashSet底层就是一个HashMap:

查看HashSet的add方法源码:

HashSet的存储的元素都是HashMap的Key,此HashMap的value总是一个固定的Object;

所以,HashSet的去重原理实质上指的就是HashMap的Key的去重原理;

2.3.2 HashMap的去重

我们知道HashSet底层就是依靠HashMap的key去重原理来是实现的,因此Map接口的HashMap、LinkedHashMap等接口的去重都是和HashSet、LinkedHashSet一致;

定义一个City对象:

package com.dfbz.demo01;/*** @author lscl* @version 1.0* @intro:*/
public class City {private String name;            // 城市名称private String shortName;       // 简称private String location;            // 所属地区public void intro() {System.out.println("名称: " + name + ",简称: " + shortName + ",所属地区: " + location);}public City() {}public City(String name, String shortName, String location) {this.name = name;this.shortName = shortName;this.location = location;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getShortName() {return shortName;}public void setShortName(String shortName) {this.shortName = shortName;}public String getLocation() {return location;}public void setLocation(String location) {this.location = location;}
}

注意:上面没有重写equals和hashCode方法;

存储两个属性一样的City对象:

package com.dfbz.demo01;import java.util.HashMap;
import java.util.Map;public class Deom04 {public static void main(String[] args) {Map<City,String> map=new HashMap<>();map.put(new City("内蒙古","蒙","华北"),"1");map.put(new City("内蒙古","蒙","华北"),"1");// 没有重写equals方法底层比较的是地址值,两个City对象肯定不一致,因此存储了2个System.out.println(map.size());     // 2}
}

回顾学习HashSet时的去重原理:

存储的两个条件:

  • 1)hashCode不同时存储

  • 2)当hashCode冲突时,equals为false时存储

没有重写hashCode时两个对象的hashCode一般情况下是不一致的,如果hashCode一致了(hash冲突),equals方法也不可能为true;

重写City的hashCode和equals方法:

按住alt+insert

@Override
public boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;City city = (City) o;return Objects.equals(name, city.name) &&Objects.equals(shortName, city.shortName) &&Objects.equals(location, city.location);
}@Override
public int hashCode() {return Objects.hash(name, shortName, location);
}

HashMap基本上没有对Map集合中的方法进行扩展;并且大部分方法在之前学习List接口的时候我们都使用过了,这里就不再演示了;

2.4 LinkedHashMap

1.4.1 LinkedHashMap 特点

我们之前在学习LinkedHashSet时说过,LinkedHashSet是继承与HashSet的,在HashSet底层的基础上增加了一个循环链表,因此LinkedHashSet除了具备HashSet的特点外(唯一),存储的元素还是有序的;

LinkedHashMap继承与HashMap,并且LinkedHashSet底层就是借助于LinkedHashMap来实现的;

LinkedHashSet源码如下:

查看HashSet对应的构造:

查看LinkedHashSet的add方法源码:

HashSet的add方法源码我们之前看过了,实质上是添加到内置的HashMap中去了;

注意:此时的map是LinkedHashMap;

1.4.2 LinkedHashMap 使用

示例代码:

package com.dfbz.demo02;import java.util.LinkedHashMap;/*** @author lscl* @version 1.0* @intro:*/
public class Demo01 {public static void main(String[] args) {LinkedHashMap<String,String> map=new LinkedHashMap<>();map.put("安徽","合肥");map.put("江苏","南京");map.put("浙江","杭州");// LinkedHashMap存取是有序的System.out.println(map);        // {安徽=合肥, 江苏=南京, 浙江=杭州}}
}

运行结果:

2.4 TreeMap

TreeMap也是TreeSet的底层实现,创建TreeSet的同时也创建了一个TreeMap,在往TreeSet集合中做添加操作是,实质也是往TreeMap中添加操作,TreeSet要添加元素成为了TreeMap的key;

我们来回顾一下TreeSet的特点(也是TreeMap的key的特点):

  1. 必须实现Compareable接口;
  2. 存储的数据是无序的,但提供排序功能(Comparable接口);
  3. 存储的元素不再是唯一,具体结果根据compareTo方法来决定;

使用示例1:

package com.dfbz.demo03;import java.util.Map;
import java.util.TreeMap;/*** @author lscl* @version 1.0* @intro:*/
public class Demo01 {public static void main(String[] args) {Map<Integer,String> map=new TreeMap<>();map.put(10,"abc");map.put(5,"abc");map.put(6,"abc");map.put(3,"abc");map.put(1,"abc");// 根据key排序System.out.println(map);        // {1=abc, 3=abc, 5=abc, 6=abc, 10=abc}}
}

存储示例2:存储自定义对象,自定义排序规则

定义一个Book类,并重写Compareable方法,按住价格升序排序:

package com.dfbz.demo03;public class Book implements Comparable<Book> {private String name;private Double price;@Overridepublic int compareTo(Book book) {// 根据价格降序排序return (int) Math.ceil(this.price - book.getPrice());}@Overridepublic String toString() {return "Book{" +"name='" + name + '\'' +", price=" + price +'}';}public String getName() {return name;}public void setName(String name) {this.name = name;}public Double getPrice() {return price;}public void setPrice(Double price) {this.price = price;}public Book() {}public Book(String name, Double price) {this.name = name;this.price = price;}
}

测试代码:

package com.dfbz.demo03;import java.util.Map;
import java.util.TreeMap;public class Demo02 {public static void main(String[] args) {Map<Book,String> map=new TreeMap<>();map.put(new Book("《计算机网络原理》",48.9),"abc");map.put(new Book("《高性能MySQL》",68.8),"abc");map.put(new Book("《深入理解Java虚拟机》",68.9),"abc");// 根据key排序  // {Book{name='《计算机网络原理》', price=48.9}=abc, Book{name='《高性能MySQL》', price=68.8}=abc, Book{name='《深入理解Java虚拟机》', price=68.9}=abc}System.out.println(map);}
}

2.5 Hashtable

Hashtable是原始的java.util的一部分,属于一代集合类, 是一个Dictionary具体的实现 。Java1.2重构的Hashtable实现了Map接口,因此,Hashtable现在集成到了集合框架中。它和HashMap类很相似,但是它支持同步。

2.5.1 Dictionary类

Dictionary类是一代集合中的双列集合顶层类,Dictionary类中的方法都是双列集合中最基本的方法;严格意义来说Java中所有的双列集合都应该继承与Dictionary类,但Java2推出了一系列二代集合,其中二代集合中的Map接口也已经替代了Dictionary接口,成为双列集合的顶层接口,因此Dictionary接口下面没有太多的实现类;

Tips:目前JDK已经不推荐使用Dictionary类了;

  • Dictionary接口方法如下:
方法 说明
Enumeration<V> elements() 返回此字典中值的枚举。
V get(Object key) 返回该字典中键映射到的值。
boolean isEmpty() 检测该字典是否为空。
Enumeration<K> keys() 返回此字典中键的枚举。
V put(K key, V value) 添加一对key,value到字典中
V remove(Object key) 根据对应的key从字典中删除value。
int size() 返回此字典中的条目数。
  • 方法测试:
package com.dfbz.hashtable;import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;/*** @author lscl* @version 1.0* @intro:*/
public class Demo01_Hashtable基本使用 {public static void main(String[] args) {Dictionary<Integer, String> hashtable = new Hashtable<>();hashtable.put(1, "南昌拌粉");hashtable.put(2, "粉蒸肉");hashtable.put(3, "福羹");hashtable.put(4, "藜蒿炒腊肉");hashtable.put(5, "瓦罐汤");String s1 = hashtable.get(3);System.out.println(s1);                             // 福羹String s2 = hashtable.remove(2);System.out.println(s2);                             // 粉蒸肉System.out.println(hashtable);                      // {5=瓦罐汤, 4=藜蒿炒腊肉, 3=福羹, 1=南昌拌粉}System.out.println("-------------");// 获取到Hashtable的所有keyEnumeration<Integer> keys = hashtable.keys();while (keys.hasMoreElements()){Integer key = keys.nextElement();System.out.println(key);}System.out.println("-------------");// 获取到Hashtable的所有valueEnumeration<String> vals = hashtable.elements();while (vals.hasMoreElements()){String val = vals.nextElement();System.out.println(val);}System.out.println("-----------------");System.out.println(hashtable.size());                   // 4}
}

2.5.2 Hashtable与HashMap的区别

  • 1)Hashtable属于一代集合,继承了Dictionary类,也实现了Map接口,HashMap属于二代集合,实现与Map接口,没有与Dictionary类产生关系;

  • 2)Hashtable支持iterator遍历(Map接口中的),也支持Enumeration遍历(Dictionary),HahsMap只支持iterator遍历

  • 2)Hashtable与HashMap底层都是采用hash表这种数据结构,JDK8对HashMap进行了优化(引入红黑树),但并没有对Hashtable进行优化;

  • 3)HashMap默认的数组大小是16,Hashtable则是11,两者的负载因子都是0.75,并且都允许传递初始化的数组大小和负载因子

  • 4)HashMap对null key和null value进行了特殊处理,可以存储null key和null value,Hashtable则不能存储null key和null value;

  • 5)当HashMap存储的元素数量>数组容量*负载因子,数组扩容至原来的2倍,Hashtable则是2倍+1;

  • 6)HashMap在添加元素时使用的是:元素本身的hash算法 ^ (元素本身的hash算法>>> 16),而Hashtable则是直接采用元素本身的hash算法;

    Tips:>>代表有符号位移,>>>代表无符号位移;

  • 7)HashMap在使用foreach迭代时不能对元素内容进行增删,否则触发并发修改异常。Hahstable中支持Enumeration迭代,使用Enumeration迭代元素时,可以对集合进行增删操作;

  • 8)Hashtable是线程安全的,效率低,安全性高;HashMap是线程不安全的,效率高,安全性低;


测试存储Null key和Null value:

package com.dfbz.hashtable;import java.util.HashMap;
import java.util.Hashtable;/*** @author lscl* @version 1.0* @intro:*/
public class Demo02_HashMap与Hashtable的区别_null问题 {public static void main(String[] args) {HashMap<Integer, String> hashMap = new HashMap<>();/*HashMap对null key和null value并且,HashMap对null key做了特殊处理,HashMap永远将Null key存储在第0位数组上*/hashMap.put(1, null);hashMap.put(null, "大闸蟹");System.out.println(hashMap);            // {null=大闸蟹, 1=null}}public static void test1(){Hashtable<Integer, String> hashtable = new Hashtable<>();// Hashtable存储null key和null value的时候会出现空指针异常: Exception in thread "main" java.lang.NullPointerExceptionhashtable.put(1, null);hashtable.put(null, "大闸蟹");}
}
  • 测试并发修改异常问题:
package com.dfbz.hashtable;import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Set;/*** @author lscl* @version 1.0* @intro:*/
public class Demo03_HashMap与Hashtable的区别_并发修改问题 {public static void main(String[] args) {Hashtable<Integer, String> hashtable = new Hashtable<>();hashtable.put(1, "拌粉");hashtable.put(2, "汤粉");hashtable.put(3, "炒粉");hashtable.put(4, "泡粉");Enumeration<Integer> keys = hashtable.keys();while (keys.hasMoreElements()) {Integer key = keys.nextElement();if (key == 2) {/*Hashtable在使用Enumeration遍历时,允许对集合进行增删操作注意: Hashtable使用foreach迭代也不能对元素进行增删操作*/hashtable.put(5, "扎粉");
//                hashtable.remove(3);}}System.out.println(hashtable);}/*** hashMap在使用foreach迭代时不允许对集合进行增删等操作*/public static void test1() {HashMap<Integer, String> hashMap = new HashMap<>();hashMap.put(1, "拌粉");hashMap.put(2, "汤粉");hashMap.put(3, "炒粉");hashMap.put(4, "泡粉");Set<Integer> keys = hashMap.keySet();for (Integer key : keys) {if (key == 2) {// hashMap在迭代时不允许对集合进行增删等操作hashMap.remove(3);
//                hashMap.put(5, "扎粉");}}}
}

三、异常

3.1 异常概述

3.1.1 什么是异常

程序运行过程中出现的问题在Java中被称为异常,异常本身也是一个Java类,封装着异常信息;我们可以通过异常信息来快速定位问题所在;我们也可以针对性的定制异常,如用户找不到异常、密码错误异常、页面找不到异常、支付失败异常、文件找不到异常等等…

当程序出现异常时,我们可以提取异常信息,然后进行封装优化等操作,提示用户;

注意:语法错误并不是异常,语法错了编译都不能通过(但Java有提供编译时异常),不会生成字节码文件,根本不能运行;

默认情况下,出现异常时JVM默认的处理方式是中断程序执行,因此我们需要控制异常,当出现异常后进行相应修改,提供其他方案等操作,不要让程序中断执行;

我们之前有见到过很多的异常:

  • 空指针异常:java.lang.NullPointerException
String str=null;
str.toString();
  • 数字下标越界异常:java.lang.ArrayIndexOutOfBoundsException
int[] arr = {1, 3, 4};
System.out.println(arr[3]);
  • 类型转换异常:java.lang.ClassCastException
class A {}class B extends A {}class C extends A {}
public static void main(String[] args) {A a = new B();C c = (C) a;
}
  • 算数异常:java.lang.ArithmeticException
int i=1/0;
  • 日期格式化异常:java.text.ParseException
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date parse = sdf.parse("2000a10-24");

3.1.2 异常体系

Java程序运行过程中所发生的异常事件可分为两类:

  • Error:表示严重错误,一般是JVM系统内部错误、资源耗尽等严重情况,无法通过代码来处理;
  • Exception:表示异常,一般是由于编程不当导致的问题,可以通过Java代码来处理,使得程序依旧正常运行;

Tips:我们平常说的异常指的就是Exception;因为Exception可以通过代码来控制,而Error一般是系统内部问题,代码处理不了;

3.1.3 异常分类

异常的分类是根据是在编译器检查异常还是在运行时检查异常;

  • 编译时期异常:在编译时期就会检查该异常,如果没有处理异常,则编译失败;
  • 运行时期异常:在运行时才出发异常,编译时不检测异常;

Tips:在Java中如果一个类直接继承与Exception,那么这个异常将是编译时异常;如果继承与RuntimeException,那么这个类是运行时异常。即使RuntimeException也继承与Exception;

  • 编译时异常举例:
public class Demo {public static void main(String[] args) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");Date date = sdf.parse("2000-10-24");}
}

  • 运行时异常:
public class Demo {public static void main(String[] args) {int i = 1 / 0;}
}

3.2 异常的处理

Java程序的执行过程中如出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统(JVM),这个过程称为抛出(throw)异常。

如果一个方法内抛出异常,该异常会被抛到调用方法中。如果异常没有在调用方法中处理,它继续被抛给这个调用方法的调用者。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常。如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。

流程如下:

3.2.1 异常的捕获

异常的捕获和处理需要采用 try 和 catch 来处理,具体格式如下:

  • 1)try...catch(){}
try {// 可能会出现异常的代码
} catch (Exception1 e) {// 处理异常1
} catch (Exception2 e) {// 处理异常2
} catch (ExceptionN e) {// 处理异常N
}

Tips:后处理的异常必须是前面处理异常的父类异常;

  • 2)try...catch(){}...finally{}
try {// 可能会出现异常的代码
} catch (Exception1 e) {// 处理异常1
} catch (Exception2 e) {// 处理异常2
} catch (ExceptionN e) {// 处理异常N
} finally {// 不管是否出现异常都会执行的代码
}
  • 3)try...finally{}
try {// 可能会出现异常的代码
}  finally {// 不管是否出现异常都会执行的代码
}

示例代码:

package com.dfbz.demo01;public class Demo01 {public static void main(String[] args) {method();System.out.println("程序终止我就不能执行了~");}public static void method() {try {String str = null;System.out.println(str.toString());} catch (Exception e) {System.out.println("执行代码出现异常了!");} finally {System.out.println("释放一些资源...");}}
}

运行结果:

tips:try…catch语句是可以单独使用的;即:不要finally代码块;

需要注意的是:如果finally有return语句,则永远返回finally中的结果。我们在开发过程中应该避免该情况;

  • 示例代码:
package com.dfbz.demo02;public class Demo02 {public static void main(String[] args) {int result = method();System.out.println("method方法的返回值: " + result);}public static int method() {try {int i = 10;return 1;} catch (Exception e) {e.printStackTrace();return 2;} finally {return 3;           // 不管是否出现异常都是返回3}}
}

3.2.2 异常的常用方法

在Throwable类中具备如下几个常用异常信息提示方法:

  • public void printStackTrace():获取异常的追踪信息;

    包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。

  • public String getMessage():异常的错误信息;

异常触发被抓捕时,异常的错误信息都被封装到了catch代码块中的Exception类中了,我可以通过该对象获取异常错误信息;

示例代码:

package com.dfbz.demo01;public class Demo02 {public static void main(String[] args) {method();System.out.println("我继续执行~");}public static void method() {try {int i=1/0;} catch (Exception e) {System.out.println("异常的错误信息: " + e.getMessage());// 打印异常的追踪信息e.printStackTrace();}}
}

运行结果如下:

异常的追踪信息可以帮助我们追踪异常的调用链路,一步一步找出异常所涉及到的方法,在实际开发非常常用;

3.2.3 异常的抛出

我们已经学习过出现异常该怎么抓捕了,有时候异常就当做提示信息一样,在调用者调用某个方法出现异常后及时针对性的进行处理,目前为止异常都是由JVM自行抛出,当然我们可以选择性的自己手动抛出某个异常;

Java提供了一个throw关键字,它用来抛出一个指定的异常对象;抛给上一级;

Tips:自己抛出的异常和JVM抛出的异常是一样的效果,都要进行处理,如果是自身抛出的异常一直未处理,最终抛给JVM时程序一样会终止执行;

语法格式:

throw new 异常类名(参数);

示例:

throw new NullPointerException("调用方法的对象是空的!");throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");

示例代码:

package com.dfbz.demo01;public class Demo03 {public static void main(String[] args) {method(null);System.out.println("我还会执行吗?");}public static void method(Object object) {if (object == null) {// 手动抛出异常(抛出异常后,后面的代码将不会被执行)throw new NullPointerException("这个对象是空的!不能调用方法!");}System.out.println(object.toString());}
}

运行结果:

手动抛出的异常和JVM抛出的异常是一个效果,也需要我来处理抛出的异常;

修改代码:

package com.dfbz.demo01;public class Demo03 {public static void main(String[] args) {try {method(null);} catch (Exception e) {System.out.println("调用method方法出现异常了");}System.out.println("我还会执行吗?");}public static void method(Object object) {if (object == null) {// 手动抛出异常(抛出异常后,后面的代码将不会被执行)throw new NullPointerException("这个对象是空的!不能调用方法!");}System.out.println("如果出现了异常我是不会执行了,你能执行到这里说明没有异常");System.out.println(object.toString());}
}

运行结果:

3.2.4 声明异常

1)运行时异常

在定义方法时,可以在方法上声明异常,用于提示调用者;

Java提供throws关键字来声明异常;关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常);

语法格式:

... 方法名(参数) throws 异常类名1,异常类名2…{   }

代码示例:

package com.dfbz.demo01;import java.text.ParseException;public class Demo04 {public static void main(String[] args) {// 可以处理,也可以不处理method("你好~");// 编译时异常必须处理try {method2("hello~");} catch (ParseException e) {System.out.println("出现异常啦!");}}// 调用此方法可能会出现空指针异常,提示调用者处理异常public static void method(Object obj) throws NullPointerException {System.out.println(obj.toString());}// 抛出的不是运行时异常,调用者调用该方法时必须处理public static void method2(Object obj) throws ParseException {System.out.println(obj.toString());}// 也可以同时抛出多个异常public static void method3(Object obj) throws ClassCastException,ArithmeticException {System.out.println(obj.toString());}
}

2)编译时异常

在声明和抛出异常时需要注意如下几点:

  • 1)如果是抛出(throw)编译是异常,那么必须要处理,可以选择在方法上声明,或者try…catch处理
  • 2)如果调用的方法上声明(throws)了编译时异常,那么在调用方法时就一定要处理这个异常,可以选择继续网上抛,也可以选择try…catch处理

  • 示例代码:
package com.dfbz.demo04;/*** @author lscl* @version 1.0* @intro:*/
public class Demo02 {public static void main(String[] args) throws Exception{// 如果调用的方法上声明了编译时异常,那么在调用方法时就一定要处理这个异常,可以选择继续网上抛,也可以选择try..catch处理test(500);}// 如果方法声明了编译时异常,那么在调用方法时就一定要处理这个异常public static void test(Integer num) throws MyException{if (num == 100) {// 如果是抛出编译是异常,那么必须要处理,可以选择在方法上声明,或者try...catch处理throw new MyException();}}
}// 直接继承与Exception,那么这个异常是一个编译时异常
class MyException extends Exception {}

3.3 自定义异常

我们说了Java中不同的异常类,分别表示着某一种具体的异常情况,那么在开发中总是有些异常情况是Java中没有定义好的,此时我们根据自己业务的异常情况来定义异常类。

我们前面提到过异常分类编译时异常运行时异常

  • 1)继承于java.lang.Exception的类为编译时异常,编译时必须处理;

  • 2)继承于java.lang.RuntimeException的类为运行时异常,编译时可不处理;

  • 自定义用户名不存在异常:

package com.dfbz.demo04;public class UsernameNotFoundException extends RuntimeException {/*** 空参构造*/public UsernameNotFoundException() {}/*** @param message 表示异常提示*/public UsernameNotFoundException(String message) {// 调用父类的构造方法super(message);}
}
  • 测试类:
package com.dfbz.demo04;public class Demo01 {// 定义内置账户public static String[] users = {"xiaohui", "xiaolan", "xiaoliu"};public static void main(String[] args) {findByUsername("abc");}public static void findByUsername(String username) throws UsernameNotFoundException {for (String user : users) {if (username.equals(user)) {System.out.println("找到了: " + user);return;}}// 用户名没找到throw new UsernameNotFoundException("没有用户: " + username);}
}

运行结果:

3.4 方法的重写与异常

  • 1)子类在重写方法时,父类方法没有声明编译时异常,则子类方法也不能声明编译时异常;

需要注意的是:运行时异常没有这个规定;也就是子类在重写父类方法时,不管父类方法是否有声明异常,子类方法都可以声明异常;

package com.dfbz.demo05;/*** @author lscl* @version 1.0* @intro:*/
public class Demo01 {public static void main(String[] args) throws Exception {}
}// 定义一个编译时异常
class MyException extends Exception {}class Fu {public void method() {}public void method2() {}
}class Zi extends Fu {// 语法通过,运行时异常随意public void method() throws NullPointerException {}// 语法报错,父类方法没有声明编译时异常,那么子类重写方法时也不能声明编译时异常public void method2() throws MyException {}
}
  • 2)同样是在编译时异常中,在子类重写父类方法时,子类不可以声明比父类方法大的异常;
package com.dfbz.demo06;/*** @author lscl* @version 1.0* @intro:*/
public class Demo01 {public static void main(String[] args) throws Exception {}
}// 定义一个编译时异常
class MyException extends Exception {}class Fu {public void method() throws NullPointerException{}public void method2()throws MyException{}}class Zi extends Fu {// 运行时异常没有问题public void method() throws RuntimeException {}// 语法报错,如果是编译时异常,子类在重写父类方法时,不可以抛出比父类大的编译时异常public void method2() throws Exception {}
}

上一篇:10【List、Set、Queue】

下一篇:12【多线程、锁机制、lock锁】

目录:【JavaSE零基础系列教程目录】


记得点赞~!!!

11【泛型、Map、异常】相关推荐

  1. (10)Java泛型-Map集合-集合框架工具类-可变参数-静态导入

    -- 部分1.5新特性Java泛型-Map集合-集合框架工具类 泛型 概述: JDK1.5版本以后出现的新特性,用于解决安全问题,是一个类型安全机制. 对于泛型可以这样理解: 没有使用泛型时,只要是对 ...

  2. Dart入门—库、泛型与异常

    Dart入门-库.泛型与异常 库 Pub仓库 在Pub上可以下载到支持各种功能的包,首页在根目录下建立pubspec.yaml文件 name: test_app dependencies:sqljoc ...

  3. Java中的泛型与异常机制

    Java中的泛型与异常机制 ※泛型的使用 如果在使用集合时没有限定集合所保存的数据类型,则在代码运行期间容易会出现ClassCastException类转换的异常 通过设置泛型,可以在编译期间发生问题 ...

  4. 已解决java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.Map异常的正确解决方法,亲测有效!!

    已解决java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.Map异常的正确解决方法,亲测有效!! ...

  5. C# 篇基础知识11——泛型和集合

    .NET提供了一级功能强大的集合类,实现了多种不同类型的集合,可以根据实际用途选择恰当的集合类型. 除了数组 Array 类定义在System 命名空间中外,其他的集合类都定义在System.Coll ...

  6. 基于C++11的Map和Multimap分析

    基本内容 Map和multimap将将键值对(key/value pair)当作元素进行管理.他们可以根据key的排序准则自动为元素排序.multimap允许元素重复,map不允许. 使用map和mu ...

  7. 11.05T3 map

    4854 -- [十连赛day2]Divisors Description 给定 m 个不同的正整数 a1, a2, ..., am,请对 0 到 m 每一个 k 计算,在区间 [1, n] 里有多少 ...

  8. 泛型和异常、 Properties文件读取加载

    1.泛型 概念:允许在定义类,接口的时候通过一个字母标识表示类中的属性的类别或者方法返回值,使用时传入棱形尖括号的是引用类型 泛型版本: List list = new ArrayList(); // ...

  9. 11.2运行异常和编译异常

    异常体系 ------------|Throwable(super类) ----------------|Error(错误) ----------------|Exception(异常) ------ ...

  10. C++11遍历map

    2019独角兽企业重金招聘Python工程师标准>>> map的每一个元素都是map<K,V>::value_type,value_type是 typedef std:: ...

最新文章

  1. Codeforces Round #648 (Div. 2)题解 A-D
  2. 你知道select count(*)底层究竟干了啥么?
  3. F4IF_INT_TABLE_VALUE_REQUEST如何返回多于一个列
  4. C++ 获取文件大小
  5. python 文件修改记录_python基础-文件增删改查
  6. 【registries】registrie rest-service idea 无法引入
  7. kettle创建mysql资源库时报错_kettle 创建数据库资源库
  8. ASP.NET的TreeView和Menu控件分别绑定siteMap和xml文件并应用母版
  9. fatfs 文件属性_FATFS文件系统剖析(全).
  10. 2015年 不可不知的五大热点话题
  11. Oracle 常见错误代码处理 1
  12. ES6的新特性(9)——对象的扩展
  13. 平安性格测试题及答案_中国平安EPASS测试题2016年
  14. springmvc 使用Jackson的配置
  15. mysql如何用alter创建索引_MySQL使用ALTER TABLE创建索引
  16. python dll注入监听_DLL注入和API拦截
  17. 计算机青蓝云题库,计算机三级上机题库 计算机三级网络技术上机题库《南开100题》.doc...
  18. linux虚拟机双显卡,Kali Linux 2.0 安装 NVIDIA显卡驱动实现双显卡(联想笔记本)
  19. Get “https://github.com/electron-userland/electron-builder-binaries/releases/download/appimage-12.0.
  20. 腾讯互娱旗下工作室一览

热门文章

  1. 【SCOI 2007】修车
  2. vim内置的搜索命令vimgrep使用
  3. 杰米斯·哈萨比斯:创造性和直觉很重要
  4. deepin因NVIDIA显卡造成开机启动问题:卡在开机logo界面+进入桌面鼠标一直转圈
  5. matlab求解焦点,Matlab焦点光场分布数值计算方法研究+源程序(2)
  6. rk3568 android 11.0 userdata 改为ext4格式
  7. 双系统设置成默认启动Windows的问题
  8. electron结合vue打包后 tray托盘图片路径
  9. Swift5.x - 中文文档
  10. flutter 音乐 录音 播放 audioplayers