java记忆手册(2)
文章目录
- 异常
- 泛型
- 认识泛型
- 泛型类
- 泛型接口
- 泛型方法、通配符、上下限
- 泛型支持的类型
- 集合框架
- Collection
- List
- Set
- Map集合
- 三种遍历方式
- map底层原理
异常
-
什么是异常?
异常代表程序出现的问题 -
Java的异常体系
这也是java帮我们写好的我们直接调用就好。
Java.lang.Throwable 这是所有异常的父级
- Error:代表的系统级别错误(属于严重问题),也就是说系统一旦出现问题,sun公司会把这些问题封装成Error对象给出来
( 说白了,Error是给sun公司自己用的,不是给我们程序员用的,因此我们开发人员不用管它) - Exception:叫异常,它代表的才是我们程序可能出现的问题,所以,我们程序员通常会用Exception以及它的孩子来封装程序出现的问题。
- 运行时异常:RuntimeException及其子类,编译阶段不会出现错误提醒,运行时出现的异常(如:数组索引越界异常)
- 编译时异常:编译阶段就会出现错误提醒的。(如:日期解析异常,这里是提示你这里的程序很容易出错。就算你写的是对的也会提示你。)
- 异常的基本处理
两种方式
- 抛出异常(throws)
- 捕获异常(try…catch)
快捷键
Alt+Enter:编译时异常调用 throws或者try…catch
Ctrl+Alt+T : throws / try…catch / if / for …
- 异常的作用?
-
异常是用来定位程序bug的关键信息。
-
可以作为方法内部的一种特殊返回值,以便通知上层调用者,方法的执行问题。
-
什么是自定义异常,自定义异常有几种
- Java无法为这个世界上全部的问题都提供异常类来代表, 如果企业自己的某种问题,想通过异常来表示,以便用异常来管理该问题,那就需要自己来定义异常类了。
- 我们可以使用编译时异常也可以使用运行时异常。开发时常用运行时异常。
-
自定义运行时异常
- 定义一个异常类继承RuntimeException.
- 重写构造器。
- 通过throw new 异常类(xxx)来创建异常对象并抛出。
特点:编译阶段不报错,运行时才可能出现!提醒不属于激进型。
-
自定义编译时异常
- 定义一个异常类继承Exception.
- 重写构造器。
- 通过throw new 异常类(xxx) 创建异常对象并抛出。
特点:编译阶段就报错,提醒比较激进
-
异常的处理方案有哪些?
方案1- 1、底层异常层层往上抛出,最外层捕获异常,记录下异常信息,并响应适合用户观看的信息进行提示(最常见)
- 方案2
2、最外层捕获异常后,尝试重新修复
抛出异常(throws)
在方法上使用throws关键字,可以将方法内部出现的异常抛出去给调用者处理。
方法 throws 异常1 ,异常2 ,异常3 ..{…
}
// 推荐方式
方法 throws Exception{
}
// Exception代表可以捕获一切异常
捕获异常(try…catch)
直接捕获程序出现的异常。
try{
// 监视可能出现异常的代码!
}catch(异常类型1 变量){
// 处理异常
}catch(异常类型2 变量){// 处理异常}...// 推荐方式
try{// 可能出现异常的代码!
}catch (Exception e){e.printStackTrace(); // 直接打印异常对象的信息
}
// Exception代表可以捕获一切异常
泛型
认识泛型
定义类、接口、方法时,同时声明了一个或者多个类型变量(如:)
称为泛型类、泛型接口,泛型方法、它们统称为泛型。
public class ArrayList<E>{. . .
}
ArrayList<String> list = new ArrayList<String>();
MyArrayList<String> mlist = new MyArrayList<>(); // JDK 7开始支持的后面类型可以不写
作用:泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力!这样可以避免强制类型转换,及其可能出现的异常。
泛型的本质:把具体的数据类型作为参数传给类型变量。
泛型类
类名后面跟
// 修饰符 class 类名<类型变量,类型变量,…> { }
public class ArrayList<E>{ ... }
// 注意:类型变量建议用大写的英文字母,常用的有:E、T、K、V 等
泛型接口
// 修饰符 interface 接口名<类型变量,类型变量,…> { . .. }
public interface A<E>{ ... }
注意:类型变量建议用大写的英文字母,常用的有:E、T、K、V 等
泛型方法、通配符、上下限
// 修饰符 <类型变量,类型变量,…> 返回值类型 方法名(形参列表) { }
// 正确写法
public static <T> void test(T t){
}
// 错误写法
public E get(int index){return (E) arr[index];
}
// 这才上面的正确写法
public <T> T get(int index) {return (T) arr[index];
}
通配符
就是 “?” ,可以在“使用泛型”的时候代表一切类型; E T K V 是在定义泛型的时候使用。
泛型的上下限:
- 泛型上限: ? extends Car: ? 能接收的必须是Car或者其子类 。
- 泛型下限: ? super Car : ? 能接收的必须是Car或者其父类。
泛型支持的类型
为什么要引入包装类
答:泛型擦除:泛型工作在编译阶段,等编译后泛型就没用了,所以泛型在编译后都会被擦除。所有类型会恢复成Object类型,于是有了 Object o = 12
。 但是12并不是一个对象,为了实现万物皆对象,于是有了基本数据类型的包装类。
包装类有哪些
基本数据类型 | 对应的包装类(引用数据类型) |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
基本类型的数据包装成对象的方案 |
---|
public Integer(int value):已过时 |
public static Integer valueOf(int i) |
为什么第一种方法过时了。
答:缓存了好多已经封装好的对象,最低的-128最高的127。valueof方法已经提前在数组里封装好了-128-127的所有对象,这些对象已经new好了。因为-128-127在编程中出现的比较多。他并不希望每次使用都去创建新的对象。创建新的对象占内存。
自动装箱:基本数据类型可以自动转换为包装类型。Integer it11 = 130; int i = it11;
自动拆箱:包装类型可以自动转换为基本数据类型。ArrayList<Integer> list = new ArrayList<>(); list.add(130); // 自动装箱
包装类具备的其他功能
- 可以把基本类型的数据转换成字符串类型。
Integer.toString(1);
相当于String a=1+"";
public static String toString(double d) |
---|
public String toString() |
- 可以把字符串类型的数值转换成数值本身对应的真实数据类型。
Double.valueOf(;"98.8");Integer.valueOf(1);
public static int parseInt(String s) |
---|
public static Integer valueOf(String s) |
集合框架
1 认识集合
集合是一种容器,用来装数据的,类似于数组,但集合的大小可变,开发中也非常常用。
2 常用集合有哪些?各自有啥特点?
- Collection(接口):单列集合:每个元素(数据)只包含一个值。
- List(接口):添加的元素是有序、可重复、有索引。
- ArrayList:有序、可重复、有索引。
- LinkedList:有序、可重复、有索引。
- Set(接口):无序、不重复、无索引
- HashSet:无序、不重复、无索引
- LinkedHashSet:有序、不重复、无索引
- TreeSet:按照大小默认升序排序、不重复、无索引
- HashSet:无序、不重复、无索引
- List(接口):添加的元素是有序、可重复、有索引。
- Map(接口):双列集合:每个元素包含两个值(键值对)。
- HashMap:
- LinkedHashMap
- TreeMap
- HashMap:
Collection
为啥要先学Collection的常用方法?
Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的。
方法名 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数。 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
三种遍历方式
- 迭代器
Iterator<String> it = names.iterator(); while (it.hasNext()) {String name = it.next();System.out.println(name); } //超出范围会有 NoSuchElementException异常
- 增强for循环: 遍历集合或者数组。本质就是迭代器遍历集合的简化写法
// for (元素的数据类型 变量名 : 数组或者集合) { } Collection<String> c = new ArrayList<>(); for(String s : c) { System.out.println(s);}
- forEach的Lambda表达式
Collection<String> lists = new ArrayList<>(); lists.forEach(new Consumer<String>() {@Overridepublic void accept(String s) {System.out.println(s);} });lists.forEach(s -> { System.out.println(s); }); // lists.forEach(s -> System.out.println(s)); names.forEach(System.out::println);
什么是并发修改异常?
遍历集合的同时又存在增删集合元素的行为时可能出现业务异常,这种现象被称之为并发修改异常问题。
如何修改解决并发修改异常?
- 如果集合支持索引,可以使用for循环遍历,每删除数据后做i–;或者可以倒着遍历
- 可以使用迭代器遍历,并用迭代器提供的删除方法删除数据。
- 使用forEach方法的Lambda写法:无法解决,直接报错
注意:增强for循环/Lambda遍历均不能解决并发修改异常问题,因此增它们只适合做数据的遍历,不适合同时做增删操作。
List
ArrayList和LinkedList底层实现是什么,有什么特点,适合的场景不同。
- Collection(接口):单列集合:每个元素(数据)只包含一个值。
- List(接口):添加的元素是有序、可重复、有索引。
- ArrayList:有序、可重复、有索引。基于数组存储数据的,索引查询快,增删慢。第一次扩容长度是10。
- LinkedList:有序、可重复、有索引。基于双链表存储数据的,索引查询慢,增删快,对首尾增删改查极快。
- Set(接口):无序、不重复、无索引
- HashSet:无序、不重复、无索引。第一次扩容长度是16。
- LinkedHashSet:有序、不重复、无索引
- TreeSet:按照大小默认升序排序、不重复、无索引
- HashSet:无序、不重复、无索引。第一次扩容长度是16。
- List(接口):添加的元素是有序、可重复、有索引。
- Map(接口):双列集合:每个元素包含两个值(键值对)。
- HashMap:
- LinkedHashMap
- TreeMap
- HashMap:
List集合因为支持索引,所以多了很多与索引相关的方法,当然,Collection的功能List也都继承了。
方法名称 | 说明 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
ArrayList的遍历方式
for循环(因为List集合有索引)
迭代器
增强for循环
Lambda表达式
LinkedList新增了:很多首尾操作的特有方法。
方法名称 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
LinkedList的应用场景:
- 可以用来设计队列: 先进先出,后进后出。我们只需要收尾删除元素即可。
- 设计栈:后进先出,先进后出。push()和pop()方法
Set
- Collection(接口):单列集合:每个元素(数据)只包含一个值。
- List(接口):添加的元素是有序、可重复、有索引。
- ArrayList:有序、可重复、有索引。基于数组存储数据的,索引查询快,增删慢。第一次扩容长度是10。
- LinkedList:有序、可重复、有索引。基于双链表存储数据的,索引查询慢,增删快,对首尾增删改查极快。
- Set(接口):不重复、无索引,常用方法是Collection提供的,几乎没有新增的常用功能。
- HashSet:无序、不重复、无索引。第一次扩容长度是16。哈希表(哈希表=数组+链表+红黑树)。
- LinkedHashSet:有序、不重复、无索引。哈希表(数组+链表+红黑树)。但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置。即双链表+哈希表(哈希表=数组+链表+红黑树)。
- TreeSet:按照大小默认升序排序、不重复、无索引。红黑树+哈希表(哈希表=数组+链表+红黑树)。对于自定义类型如Student对象,TreeSet默认是无法直接排序的会报错。想要排序需要重写Comparator对象去指定比较规则。
- HashSet:无序、不重复、无索引。第一次扩容长度是16。哈希表(哈希表=数组+链表+红黑树)。
- List(接口):添加的元素是有序、可重复、有索引。
- Map(接口):双列集合:每个元素包含两个值(键值对)。
- HashMap:
- LinkedHashMap
- TreeMap
- HashMap:
1 常用set方法
Set要用到的常用方法,基本上就是Collection提供的。自己几乎没有额外新增一些常用功能!
2 什么是hash值
哈希值:就是一个int类型的随机值,Java中每个对象都有一个哈希值。
Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值。
// public int hashCode(); //返回对象的哈希码值
String s1 = "acd";
String s2 = "abc";
System.out.println(s1.hashCode());// 96386
System.out.println(s1.hashCode());// 96386
System.out.println(s2.hashCode());// 96354
System.out.println(s2.hashCode());// 96354
3 对象哈希值的特点:
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的。
- 不同的对象,它们的哈希值大概率不相等,但也有可能会相等(哈希碰撞)。
4 为什么大概率不相等?
因为int取值范围 (-21亿多 ~ 21亿多)一共45亿个对象。当我们有48亿个对象,就会有三亿个对象重复。
5 红黑树:就是可以自平衡的二叉树:让红黑数量相等。
6 HashSet底层原理
哈希表存储数据的详细流程
- 创建一个默认长度16,默认加载因为0.75的数组,数组名table
- 根据元素的哈希值跟数组的长度计算出应存入的位置
- 判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素, 则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入链表。
- 当数组存满到16*0.75=12时,就自动扩容,每次扩容成原先的两倍
- 当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树
7 HashSet集合元素的对象去重操作
在类中重写hashCode()和equals()
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Objects;@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {private String name;private int age;private String address;private String phone;// 只要两个对象的内容一样结果一定是true.// s3.equals(s1)@Overridepublic boolean equals(Object o) {// 1、如果是自己和自己比直接返回trueif (this == o) return true;// 2、如果o为空或者o不是Student类型,直接返回falseif (o == null || this.getClass() != o.getClass()) return false;// 3、比较两个对象的内容是否一样Student student = (Student) o;return this.age == student.age && Objects.equals(name, student.name) && Objects.equals(address, student.address) && Objects.equals(phone, student.phone);}@Overridepublic int hashCode() {// 不同学生对象,如果内容一样返回的哈希值一定是一样的,return Objects.hash(name, age, address, phone);}
}
// main
import java.util.HashSet;
import java.util.Set;
public class SetDemo2 {public static void main(String[] args) {// 目标:掌握HashSet集合去重操作。Student s1 = new Student("张三", 18, "北京", "123456");Student s2 = new Student("李四", 19, "上海", "989876");Student s3 = new Student("张三", 18, "北京", "123456");Student s4 = new Student("李四", 19, "上海", "989876");Set<Student> set = new HashSet<>(); // 不重复的!set.add(s1);set.add(s2);set.add(s3);set.add(s4);System.out.println(set);// 如果在Student类中不重写hashCode()和equals()方法。这里hashCode()相当于不同地址。虽然内容一样。// [Student(name=张三, age=18, address=北京, phone=123456),// Student(name=李四, age=19, address=上海, phone=989876)]}
}
8 LinkedHashSet集合的底层原理
依然是基于哈希表(数组、链表、红黑树)实现的。
但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置。
9 TreeSet集合底层原理
底层是基于红黑树实现的排序。
10 TreeSet集合排序规则
- 对于数值类型:Integer , Double,默认按照数值本身的大小进行升序排序。
- 对于字符串类型:默认按照首字符的编号升序排序。
- 对于自定义类型如Student对象,TreeSet默认是无法直接排序的。
11 TreeSet集合存储自定义类型的对象时,必须指定排序规则,支持如下两种方式来指定比较规则。
- 让自定义的类(如学生类)实现Comparable接口,重写里面的compareTo方法来指定比较规则。
public int compareTo(Teacher o) {return this.getAge() - o.getAge(); // 升序 }
- 通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则。
Set<Teacher> teachers = new TreeSet<>((o1, o2) -> Double.compare(o1.getSalary(), o2.getSalary()));
public TreeSet(Comparator<? super E> comparator)
两种方式中,关于返回值的规则:
如果认为第一个元素 > 第二个元素 返回正整数即可。
如果认为第一个元素 < 第二个元素返回负整数即可。
如果认为第一个元素 = 第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
注意:如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序。
- 如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据?
用ArrayList集合(有序、可重复、有索引),底层基于数组的。(常用) - 如果希望记住元素的添加顺序,且增删首尾数据的情况较多?
用LinkedList集合(有序、可重复、有索引),底层基于双链表实现的。 - 如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快?
用HashSet集合(无序,不重复,无索引),底层基于哈希表实现的。 (常用) - 如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快?
用LinkedHashSet集合(有序,不重复,无索引), 底层基于哈希表和双链表。 - 如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快?
用TreeSet集合,基于红黑树实现。
Map集合
- Collection(接口):单列集合:每个元素(数据)只包含一个值。
- List(接口):添加的元素是有序、可重复、有索引。
- ArrayList:有序、可重复、有索引。基于数组存储数据的,索引查询快,增删慢。第一次扩容长度是10。
- LinkedList:有序、可重复、有索引。基于双链表存储数据的,索引查询慢,增删快,对首尾增删改查极快。
- Set(接口):不重复、无索引,常用方法是Collection提供的,几乎没有新增的常用功能。
- HashSet:无序、不重复、无索引。第一次扩容长度是16。哈希表(哈希表=数组+链表+红黑树)。
- LinkedHashSet:有序、不重复、无索引。哈希表(数组+链表+红黑树)。但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置。即双链表+哈希表(哈希表=数组+链表+红黑树)。
- TreeSet:按照大小默认升序排序、不重复、无索引。红黑树+哈希表(哈希表=数组+链表+红黑树)。对于自定义类型如Student对象,TreeSet默认是无法直接排序的会报错。想要排序需要重写Comparator对象去指定比较规则,或者自定义类实现Comparable接口,重写里面的compareTo方法来指定比较规则。
- HashSet:无序、不重复、无索引。第一次扩容长度是16。哈希表(哈希表=数组+链表+红黑树)。
- List(接口):添加的元素是有序、可重复、有索引。
- Map(接口):双列集合:每个元素包含两个值(键,值),键不重复、无索引
- HashMap:无序、键不重复、无索引。哈希表(哈希表=数组+链表+红黑树)
- LinkedHashMap:有序、键不重复、无索引。双链表+哈希表(哈希表=数组+链表+红黑树)
- TreeMap:按照大小默认升序排序、键不重复、无索引。红黑树+哈希表(哈希表=数组+链表+红黑树),想要排序需要重写Comparator对象去指定比较规则,或者自定义类实现Comparable接口,重写里面的compareTo方法来指定比较规则。
- HashMap:无序、键不重复、无索引。哈希表(哈希表=数组+链表+红黑树)
Map集合也被叫做“键值对集合”,格式:{key1=value1 , key2=value2 , key3=value3 , …}
Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值
Map集合在什么业务场景下使用。
需要存储一一对应的数据时,就可以考虑使用Map集合来做。
{商品1=2 , 商品2=3 , 商品3 = 2 , 商品4= 3}
注意:Map系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的
HashMap(由键决定特点): 无序、不重复、无索引; (用的最多)
LinkedHashMap (由键决定特点):由键决定的特点:有序、不重复、无索引。
TreeMap (由键决定特点):按照大小默认升序排序、不重复、无索引。
为什么要先学习Map的常用方法 ?
Map是双列集合的祖宗,它的功能是全部双列集合都可以继承过来使用的。
方法名称 | 说明 |
---|---|
public V put(K key,V value) | 添加元素 |
public int size() | 获取集合的大小 |
public void clear() | 清空集合 |
public boolean isEmpty() | 判断集合是否为空,为空返回true , 反之 |
public V get(Object key) | 根据键获取对应值 |
public V remove(Object key) | 根据键删除整个元素 |
public boolean containsKey(Object key) | 判断是否包含某个键 |
public boolean containsValue(Object value) | 判断是否包含某个值 |
public Set<K> keySet() | 获取全部键的集合 |
public Collection<V> values() | 获取Map集合的全部值 |
三种遍历方式
- 键找值:先获取Map集合全部的键,再通过遍历键来找值
- 键值对:把“键值对“看成一个整体进行遍历(难度较大)
- Lambda:JDK 1.8开始之后的新技术(非常的简单)
map底层原理
HashMap跟HashSet的底层原理是一模一样的,都是基于哈希表实现的。
实际上:原来学的Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
下面是 HashSet源码。我们发现set就是给Map套了一个壳子。
public HashSet() { map = new HashMap<>();}