Java集合框架
集合
集合:就是用来
存放数据
的一个容器
Java提供的集合类
- 长度可以改变
- 能存储任意的对象
- 长度是随着你元素的个数增加而增加
🐤数组和集合的区别
- 数组能存
基本数据类型
,和引用数据类型
- 集合当中只能存放
引用数据类型
,直接放基本数据类型,也会自动帮你装箱(把基本数据类型转成对象)集合当中只能存放对象
也就是引用数据类型- 数组的长度是固定的,定义好就不能再去增长,在程序跑起来的时候是不能修改的,还没有跑起来是可以修改的,相当于还在定义
- 集合长度是可以改变的,根据元素的个数增长而增加,在程序跑起来了,元素突然增加的集合的长度也会改变,而数组就不能
- 什么时候使用数组,什么时候使用集合
- 如果元素个数是
固定
的,推荐使用数组
- 如果元素个数
不是固定
的,推荐使用集合
集合继承结构图
Collection
是一个接口,真正使用的是它的实现类
集合通用方法
添加元素
- 创建
Student.java
/**
* @author BNTang
**/
public class Student {
public String name;
public int age;
}
- 测试类代码如下:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
Collection c = new ArrayList();
boolean b1 = c.add("abc");
// 会自动装箱(把基本数据类型转成对象)
boolean b2 = c.add(10);
boolean b3 = c.add(true);
Student s = new Student();
s.name = "BNTang";
s.age = 23;
boolean b4 = c.add(s);
System.out.println(c);
}
}
删除元素
- 测试代码如下:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
Collection c2 = new ArrayList();
c2.add("a");
c2.add("b");
c2.add("c");
System.out.println(c2);
// 可以从集合当中移除指定元素
c2.remove("a");
System.out.println(c2);
}
}
判断一个集合是否为空
- 测试代码如下:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
Collection c2 = new ArrayList();
c2.add("a");
c2.add("b");
c2.add("c");
// 判断一个集合是否为空,为空返回 true,不为空返回 false
System.out.println(c2.isEmpty());
}
}
获取集合当中的长度
- 测试代码如下:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
Collection c2 = new ArrayList();
c2.add("a");
c2.add("b");
c2.add("c");
// 获取集合当中的长度(也就是有几个元素)
System.out.println(c2.size());
}
}
清空集合当中所有的内容
- 测试代码如下:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
Collection c2 = new ArrayList();
c2.add("a");
c2.add("b");
c2.add("c");
c2.clear();
System.out.println(c2);
System.out.println(c2.size());
}
}
把c2当中的所有元素合并到c1当中
- 测试代码如下:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
Collection c1 = new ArrayList();
c1.add("a");
c1.add("b");
c1.add("c");
c1.add("d");
Collection c2 = new ArrayList();
c2.add("a");
c2.add("b");
c2.add("c");
c2.add("d");
c2.add("e");
System.out.println(c1);
c1.addAll(c2);
System.out.println(c1);
}
}
删除c1两个集合中的交集元素
- 例如 c1 调用删除的方式 c2 作为参数传入, c2 当中的元素在 c1 当中存在了,就会删除该元素
- 测试代码如下,结果自行运行:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
Collection c1 = new ArrayList();
c1.add("a");
c1.add("b");
c1.add("c");
c1.add("d");
Collection c2 = new ArrayList();
c2.add("a");
c2.add("b");
c2.add("e");
c1.removeAll(c2);
System.out.println(c1);
}
}
判断调用的集合是否包含(全部包含)传入集合
- 意思是判断传入的集合当中所有的值是否和当前集合中的内容匹配
- 注意是传入的集当中的值全部进行判断,其中一个在当前集合中不包含就为
false
,全部存在就为true
- 代码如下:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
Collection c1 = new ArrayList();
c1.add("a");
c1.add("b");
c1.add("c");
c1.add("d");
Collection c2 = new ArrayList();
c2.add("a");
c2.add("b");
c2.add("e");
boolean res = c1.containsAll(c2);
System.out.println(res);
}
}
取交集把交集的结果赋值给调用者
- 代码如下:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
Collection c1 = new ArrayList();
c1.add("a");
c1.add("b");
c1.add("c");
c1.add("d");
Collection c2 = new ArrayList();
c2.add("a");
c2.add("b");
boolean b = c1.retainAll(c2);
System.out.println(c1);
System.out.println(b);
}
}
集合的遍历
集合遍历
- 代码如下:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
Collection c1 = new ArrayList();
c1.add("a");
c1.add("b");
c1.add("c");
c1.add("d");
// 会出现类型转换异常
// c1.add(10);
Object[] arr = c1.toArray();
for (Object o : arr) {
System.out.println(o);
}
}
}
迭代器遍历
- 代码如下:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
Collection arrayList = new ArrayList();
arrayList.add("a");
arrayList.add(1);
arrayList.add("c");
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
迭代器源码
- 有一个
iterator
方法返回了一个Iterator
类型的结果,内部又定义了一个内部类实现了Iterator
接- 由于不同的集合数据结构都不一样所以都有自己不一样的实现
- 首先定义 3 个变量分别记录不同的结果,当前角标,和最后一个元素两个
- 每次调用
next
方法都会把当前角标赋值一份给i
,如果i
大于了当前集合的个数,则会抛出异常,没有元素异常- 不大于则会取出当前集合元素用一个变量接收,在判断一下当前
i
,是否大于当前变量接收的角标如果大于的话则会抛出一个并发修改异常
,如果不大于,等于则会进行取元素,会把当前i
赋值给lastRet
变量
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
// prevent creating a synthetic constructor
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
- 并发修改异常,这个问题是调用了集合自身的
remove
方法,导致了底层在判断的时候数据不一致抛出的,自己看一下集合remove
源码就知道为啥会出现这个问题了
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
- 所以在移除元素的时候尽可能的调用迭代器的
remove
方法,迭代器的remove
方法,查看源码就知道为什么了
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
List集合
ArrayList
添加元素
- 代码如下:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) throws Exception {
List<String> person = new ArrayList<>();
person.add("jackie"); //索引为0
person.add("peter"); //索引为1
person.add("annie"); //索引为2
person.add("martin"); //索引为3
person.add("BNTang"); //索引为4
System.out.println(person);
}
}
获取元素
- 代码如下:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) throws Exception {
List<String> person = new ArrayList<>();
person.add("jackie"); //索引为0
person.add("peter"); //索引为1
person.add("annie"); //索引为2
person.add("martin"); //索引为3
person.add("BNTang"); //索引为4
System.out.println(person.get(4));
}
}
删除元素
- 代码如下:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) throws Exception {
List<String> person = new ArrayList<>();
person.add("jackie"); //索引为0
person.add("peter"); //索引为1
person.add("annie"); //索引为2
person.add("martin"); //索引为3
person.add("BNTang"); //索引为4
person.remove(0);
System.out.println(person);
}
}
遍历集合元素
- 上面我写了一种方式
- 这种是传统的 for 循环的方式根据长度遍历
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) throws Exception {
List<String> person = new ArrayList<>();
person.add("jackie"); //索引为0
person.add("peter"); //索引为1
person.add("annie"); //索引为2
person.add("martin"); //索引为3
person.add("BNTang"); //索引为4
for (int i = 0; i < person.size(); i++) {
System.out.println(person.get(i));
}
}
}
迭代器遍历
- 代码如下:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) throws Exception {
List list = new ArrayList();
list.add("jackie"); //索引为0
list.add("peter"); //索引为1
list.add("annie"); //索引为2
list.add("martin"); //索引为3
list.add("BNTang"); //索引为4
Iterator it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
🐤并发修改异常
🐸并发修改异常原因
🦄解决并发修改异常
ListIterator迭代器
ArrayList数据结构
去除List集合中重复的元素
如果比较的是自定义对象需要自己覆盖
equals
方法,他默认比较的是内存地址
,而我们想要达到对象里面的属性值比较的话需要自己覆盖equals
当然可以使用编辑器生成例如:intellij IDEA
LinkedList
遍历元素
特有方法
LinkedList数据结构
Vector
泛型
- List 中添加元素存在的问题
- 往集合当中存储元素,可以存任何类型元素
- 使用某个类型当中特有的方法,必须得要转回来才可以进行使用
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) throws Exception {
List list = new ArrayList();
list.add(1);
list.add("abc");
Object obj = list.get(1);
String str = (String) obj;
str.substring(0, 1);
}
}
- 没有泛型存在的问题
- 什么是泛型:广泛通用的类型
- 一开始还不确定是什么类型,在使用的时候,才能确定是什么类型
- 在开始定义类的时候,留一个插口
- 在创建对象的时候,再去插入对应的类型
🐤泛型注意点
- 泛型前后类型必须得要保持一致
- 从 Java7 开始,后面的类型可以不写:new ArrayList<>();
菱形语法
- 泛型是没有继承的
- 泛型其实是一个
语法糖
(本质还是 Object,内部其实还是要做强转)
- 没有使用泛型的 List
泛型类
- 在类上面定义的泛型,在创建对象的时候,要指明泛型的类型
- 泛型类当中定义的泛型只能用在普通方法上面
- 不能使用在静态方法上面
- 静态方法是直接使用类名调用,泛型是在创建对象的时候,才去指定类型,所以就不能使用了
泛型方法
- 就是在方法上面添加了泛型
- 单独对一个方法上面声明泛型,方法当中定义的泛型
自定义泛型方法
通配符
- 不知道使用什么类型来接收的时候,可以使用
?
来表示未知- 通配符只是用来做接收使用
- 不能做添加操作
泛型上限
- 泛型的上限:用来限定元素的类型必须得是指定类型(Number)的子类<或者是指定类型或者是指定类的子类>
泛型下限
- 用来限定元素的类型必须得是指定类型(Number)的父类(或者是指定的类)
泛型擦除
- 把一个有泛型集合赋值给了一个没有泛型的集合最后就没有泛型了 → 也就是泛型擦除(把泛型给去掉了)
数组与集合的转换
把数组转成集合
引用数据类型的数组转集合
集合转数组
- list3 是上图中的集合连着的,阅读的时候需要注意一下
Set
- Set 当中存的元素是
无序
的,里面没有重复
的元素
HashSet
- HashSet 自定义对象的比较方法,需要重写
hashcode
和equals
,他底层其实是用hashCode
来比较的
- 获取1到20之间随机数
- 自己可以抽出一个方法来
- 传的数字是几就生成几个不重复的随机数
LinkedHashSet
- LinkedHashSet 它是 HashSet 的子类
- 它底层是使用
链表
实现,是 Set 集合当中唯一的一个保证元素是怎么存怎么取的- HashSet 能够保证元素的唯一
TreeSet
Map
- 映射关系
- A集合、B集合可能是(ArrayList LinkedList Vector HashSet LinkedHashSet TreeSet)这其中的一种集合类型
- A集合当中的每一元素,都可以在B集合当中找到一个唯一的一个值与之对应
- A集合当中的元素不能是重复的(Set)
- A集合当中的每一个元素称它是一个
key
(键)- B集合当中的每一个元素称它是一个
value
(值)
添加功能
删除功能
获取长度
遍历Map
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new HashMap<>();
map.put("张三", 20);
map.put("李四", 21);
map.put("王五", 22);
Set<String> allKeys = map.keySet();
Iterator<String> it = allKeys.iterator();
while (it.hasNext()) {
String key = it.next();
Object val = map.get(key);
System.out.println(key + "=" + val);
}
System.out.println("------------");
for (String key : map.keySet()) {
System.out.println(key + "=" + map.get(key));
}
}
}
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new HashMap<>();
map.put("张三", 20);
map.put("李四", 21);
map.put("王五", 22);
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
Iterator<Map.Entry<String, Integer>> it = entrySet.iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> en = it.next();
String key = en.getKey();
Integer value = en.getValue();
System.out.println(key + " = " + value);
}
System.out.println("--------------");
for (Map.Entry<String, Integer> entry : entrySet) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
TreeMap
- 如果是自定义对象,想要按照自己的方式进行排序,你就可以参考我上面介绍的
TreeSet
的方式进行实现一个接口,然后覆盖里面的compareTo
方法
HashTable
LinkedHashMap
- 使用
HashMap
它的key
是没有顺序的- LinkedHashMap 添加的元素是有顺序(你怎么放的,打印时就是什么顺序的)
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new HashMap<>();
map.put("张三", 20);
map.put("李四", 21);
map.put("王五", 22);
System.out.println(map);
HashMap<String, Integer> hm = new HashMap<>();
hm.put("张三", 20);
hm.put("李四", 20);
hm.put("王五", 20);
System.out.println(hm);
LinkedHashMap<String, Integer> hm2 = new LinkedHashMap<>();
hm2.put("张三", 20);
hm2.put("李四", 20);
hm2.put("王五", 20);
System.out.println(hm2);
}
}
异常
- Java 程序在运行过程当中出现的
错误
异常继承体系
- Throwable
- 🐸Throwable继承体系
异常处理方式
- 自己处理,然后继续运行
- 自己没有办法处理,交给
JVM
来处理
JVM
有一个默认的异常处理机制处理异常,并将该异常的名称,异常信息,异常出现的位置给打印在控制台上,同时停止程序的运行
10/0;
违背了算法运算法则,抛出异常
抛出的异常是一个对象
New ArithmeticException(“/by zero”),把这个异常对象返回
此时想把异常对象赋值给 a
a 是一个 int 类型不能接收,main方法解决不了
交给 JVM 来处理,JVM 就将该异常在控制台打印程序终止
自己处理异常
- 基本格式
try...catch...finally
try{
可能出现异常的代码
}catch(异常类型){
}finally{
处理完异常最后做的处理
}
- try:用来检测可能出现异常的代码
catch
:用来捕获出现的异常finally
:一般用来释放资源
10/0;会创建一个异常对象
New ArithmeticException(“/by zero”)
赋值给a,接收不了,此时就把异常对象传给catch当中的参数a
能够接收,就会执行catch当中的内容,程序也没有终止
- 只有在
try
中的代码出了问题的时候才会执行catch
当中的内容- 出问题时
catch
处理完毕之后,后续的代码继续执行try…catch
可以同时处理多个异常- 在
try
当中可能会出现不同的异常类型此时可以使用多个catch
来进行处理
Exception e
不能写在最上面,写在上面会报错,因为下面的永远不会执行到- 因为
Exception
是所有异常的父类,所以就不会再一一往下进行判断了
- 🐤JDK7处理多个异常
常用方法
获取异常信息
获取异常的类名和异常信息
获取异常类名和异常信息,及异常出现的位置
throws方式处理异常
- 🐤抛出运行时异常
- 抛出运行时异常,不需要处理这个抛出的异常
- 🐸抛出编译时异常
- 抛出了一个编译时异常,必须得要有人处理如果不处理,必须继续往上抛,抛给方法调用者
- 此时由于
setAge
内部抛出了一个异常,在调用该方法时必须得要处理抛出的异常- 处理异常
- 可以选择继续往上抛
throw与throws的区别
- throw
- 用在方法体内,跟的是异常对象名称,也就是异常对象实例
- 只能抛出一个异常对象
- 表示抛出异常
- throws
- 用在方法声明后面,跟的是异常类名
- 可以跟多个异常类名,中间使用逗号隔开
- 表示抛出异常,由方法调用者来处理
编译时异常与运行时异常
- 运行时异常:所有的
RuntimeException
类及其子类称为运行时异常,在编译时不会报错,在运行过程中程序会被终止运行- 编译时异常:程序员必须显示的处理了这个异常,否则程序就会发生错误无法通过编译,就跑不起来
- 编译时异常发生的情况
在编译时就必须处理这个异常
这个代码是读取一个文件,在读取时,它也不确定你这个文件是否存在
如果不存在,你要怎么处理,所以在编译时就要确定好
如果文件不存在该怎么样处理