Java第二部分:集合
本博客用以记录我在学习过程中的笔记,也可以分享给有需要的人。
学习内容和时间:JavaSE课程(视频来自于b站Idea黑马)、2020年10月。
1、Collection常用功能
集合是Java中提供的一种容器,可以用来存储多个数据。
集合和数组的区别:
- 数组长度固定,集合长度可变;
- 数组存储同一个类型的元素,既可以存储基本类型的数据,可以存储对象;集合只能用来存储对象,而且对象的类型可以不一致。
注意事项: Collection集合中没有定义带索引的方法。
-
List集合:特点:有索引的、可以存储重复元素、存储有序;
1、ArrayList集合【重点】:底层是数组实现的,查询快、增删慢;
2、LinkedList集合【次重点】:底层是链表实现的,查询慢、增删快;
3、Vector集合【了解】 -
Set集合:特点:没有索引、不可以存储重复元素、存储无序;
1、HashSet集合【重点】:底层是哈希表+(红黑树)实现的;
2、LinkedHashSet集合【次重点】:底层是哈希表+链表实现的,可以保证存储有序
3、TreeSet结集合【了解】:底层是二叉树实现的,一般用于排序 -
Java的常用功能:
public boolean add(E e):把给定的对象添加到当前的集合中
public void clear() :清空集合中的所有元素
public boolean remove(E e):把给定的对象在当前的集合中删除(返回值是boolean型,代表删除是否成功)
public boolean contains(E e):判断当前的集合中是否包含给定的对象
public boolean isEmpty():判断当前的集合是否为空
public int size():返回集合中元素的个数
public Object[] toArray():把集合中的元素,存储到数组中
public Object[] toArray(T[] a):把集合中的元素,存储到数组中。返回的数组的类型是指定数组的类型
注意事项: 使用集合的特有方法时,不能使用多态。
2、List集合
特点:有索引的、可以存储重复元素、存储有序;
2.1 ArrayList集合【重点】
特点:
- ArrayList底层是一个数组,所以 【查询快,增删慢】;
- 此实现不是同步的:可以多线程访问,效率较高,速度较快。
常用方法:
- 声明一个ArrayList
ArrayList<String> list = new ArrayList<>();//泛型只能是引用类型,不能是基本类型!
//ArrayList<int> list = new ArrayList<>()错误!
//(因为ArrayList里面保存的地址值,而基本类型的数据没有地址值,应使用基本数据类型对应的包装类)
基本类型 包装类(引用型,包装类都位于java.lang包下)
byte Byte
short Short
int Integer 【特殊】
long Long
float Float
double Double
char Character 【特殊】
boolean Boolean
- 添加数据(add)
list.add("yy");
list.add("ni");
list.add("zui");
list.add("shuai!");
//向集合当中添加数据,要用到add,返回值一定是true(因为添加一定成功)
System.out.println(list);//yynizuishuai!
//注意事项:对于ArrayList集合来说,直接打印不再输出地址值,而是内容(重写了toString方法)。
- 获取数据(get)
String list1 = list.get(0);//从集合中获取元素,参数是索引编号,返回值就是这个位置的元素
System.out.println(list1);//yy
- 删除数据(remove)
String list2 = list.remove(0);//从集合中删除元素,参数是索引编号,返回值就是这个被删除的元素
System.out.println(list2);//yy
System.out.println(list);//nizuishuai!
- 获取集合长度
int listSize = list.size();//获取集合的尺寸长度,返回值是这个集合中的元素个数
System.out.println(listSize);//3
2.2 LinkedList集合【次重点】
特点:
- LinkedList集合是一个双向链表,所以【查询慢,增删快】,很方便找到头和尾;
- 此实现不是同步的:可以多线程访问,效率较高,速度较快。
常用方法:
- 添加数据
public void addFirst(E e): 将指定元素插入此列表的开头
public void addLast(E e):将指定元素插入此列表的结尾
public void push(E e):将元素推入此列表所表示的堆栈(等效于addFirst)
- 获取数据
public E getFirst():返回此列表的第一个元素
public E getLast():返回此列表的最后一个元素
- 删除数据
public E removeFirst():移除此列表的第一个元素,并返回被移除的元素
public E removeLast():移除此列表的最后一个元素,并返回被移除的元素
public E pop():从此列表所表示的堆栈处弹出一个元素(等效于removeFirst)
2.3 Vector集合【了解】
特点:
- Vector底层是一个数组,所以 【查询快,增删慢】;
- 此实现是同步的:所以被ArrayList取代了。
3、Set集合
特点:没有索引、不可以存储重复元素、存储无序;
3.1 HashSet集合【重点】
特点:
- HashSet集合底层是一个哈希表,查询的速度非常快;
- 因为没有索引,所以不能使用普通for循环遍历,只能使用迭代器循环或者增强for循环遍历集合。
哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,不是物理地址)。
int hashCode():返回该对象的哈希值
使用方法:对象名.hashCode();返回的就是该对象的哈希值
哈希表:对元素进行分组,拥有相同哈希值的元素会被分在同一组;每组中,用链表将相同哈希值的元素连接再一起,若链表的长度超过了8位,那么就会把链表转换为红黑树(提高查询的速度)。
Set集合不存储重复集合的原理:Set集合在调用add方法时,会调用元素的hashCode方法和equals方法,首先判断哈希值是否相同;若相同,使用equals方法判断哈希值相同的元素是否重复。
Set不存储重复元素的前提:存储的元素必须重写hachCode方法和equals方法。
所以在HashSet中存放自定义类型的元素时,需要【重写对象中的hachCode方法和equals方法】,建立自己的比较方式,才能保证集合中的对象是唯一的。
3.2 LinkedHashSet集合【次重点】
HashSet集合中的元素是唯一的,但是不保证元素有序。
在HashSet集合下面有一个子类叫做java.util.LinkedHashSet,它是链表和哈希表(数组+链表/红黑树)组合的一个数据存储结构:多了一条链表(记录元素的存储顺序),保证元素【存储有序】
4、Collections集合帮助类
java.util.Collections是集合工具类,提供了一系列静态方法,用来对集合进行操作。
public static <T> boolean addAll(Collection<T> c,T... elements):往集合中添加【一些】元素
public static void shuffle(List<?> list):打乱集合的顺序
public static <T> void sort(List<T> list):将集合中的元素按照默认规则排序(默认是升序)
public static <T> void sort(List<T> list,Comparator<? super T>):将集合中的元素按照指定规则排序
常用方法:
- 添加【一些】元素
/*
public static <T> boolean addAll(Collection<T> c,T... elements):往集合中添加一些元素
*/
Collections.addAll(list,"a","b","c");//return true
System.out.println(list);//[a, b, c]
- 打乱集合顺序
/*
public static void shuffle(List<?> list):打乱集合的顺序
*/
Collections.shuffle(list);
System.out.println(list);//[b, c, a]
- 默认规则排序
/*
public static <T> void sort(List<T> list):将集合中的元素按照默认规则排序
*/
Collections.sort(list);
System.out.println(list);//[a, b, c],默认是升序
//注意事项:sort方法的使用前提:被排序的集合里边存储的集合,必须实现Comparable接口,重写接口中的方法compareTo定义排序的规则
@Override
public int compareTo(Person o) {
// return 0;//认为元素都是相同的
// return this.getAge()-o.getAge();//按照年龄升序【记住升序降序的规律】
return o.getAge()-this.getAge();//按照年龄降序
}
- 指定规则排序
/*
public static <T> void sort(List<T> list,Comparator<? super T>):将集合中的元素按照指定规则排序
Comparator和Comparable的区别:
Comparable:自己(this)和别人(参数)进行比较,自己需要实现Comparable接口,重写比较规则compareTo方法
Comparator:参数和参数之间进行比较
*/
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,2,1,3,4,5);
Collections.sort(list, new Comparator<Integer>() {//使用到了匿名内部类
@Override
public int compare(Integer o1, Integer o2) {
// return o1-o2;//【升序】
// return o2-o1;//【降序】
return o2.compareTo(o1);//【降序】
}
// public int compare(String o1, String o2) {
// return o1.charAt(0)-o2.charAt(0);//按照String类型的首字母升序
// }
});
System.out.println(list);//[5, 4, 3, 2, 1]
5、Map集合
Map集合是双列集合;
特点:通过【键】可以找到值【值】、键和值是一一映射的关系、键是唯一的、值可以重复。
5.1 HashMap
java.util.HashMap<k,v>集合 implements Map<k,v>接口
特点:
-
HashMap集合底层是哈希表:查询速度非常快;
JDK1.8之前:数组+单向链表
JDK1.8之后:数组+单向链表\红黑树(链表的长度超过8):提高查询的速度 -
HashMap集合是一个无序的集合,存储元素和取出元素的顺序可能不一致。
5.2 LinkedHashMap
java.util.LinkedHashMap<k,v>集合 extends HashMap<k,v>集合
特点:
- LinkedHashMap集合底层是哈希表+链表(保证迭代的顺序)
- LinkedHashMap集合是一个有序的集合,存储元素和取出元素的顺序一致
HashMap和LinkedHashMap的存储无序与有序的区别如下:
Map<String,String> map1 = new HashMap<>();
map1.put("a","a");
map1.put("c","c");
map1.put("b","b");
map1.put("d","d");
System.out.println(map1);//{a=a, b=b, c=c, d=d},而存储数据的顺序是“acbd”,存储无序
Map<String,String> map2 = new LinkedHashMap<>();
map2.put("a","a");
map2.put("c","c");
map2.put("b","b");
map2.put("d","d");
System.out.println(map2);//{a=a, c=c, b=b, d=d},存储有序
5.3 Map的常用方法
public V put(K key,V value):把指定的键和值添加到Map集合中
public V remove(Object key):把指定的键 所对应的键值对 在集合中删除,并且返回被删除的值
public V get(Object key):根据指定的键 返回集合中对应的值
boolean containsKey(Object key):判断集合中是否包含所对应的键
public Set<K> keySet():获取Map集合中所有的键,存储到Set集合中
public Set<Map.Entry<K,V>> entrySet():获取Map集合中国所有的键值对对象,存储到Set集合中
default V getOrDefault(Object key, V defaultValue):返回key键所对应的值,如果找不到该键则返回默认值defaultValue
- 添加键值对
/*
public V put(K key,V value):把指定的键和值添加到Map集合中
返回值:
存储键值对时,key之前不存在,返回值V是null
key之前存在,则发生重复,会使用新的value值替换map集合中的value值,并返回被替换的value值
*/
Map<String, String> map = new HashMap<>();
String value = map.put("YY", "LJL");
System.out.println(map);//{YY=LJL}
System.out.println(value);//null
String value1 = map.put("YY", "LJL");
System.out.println(map);//{YY=LJL}
System.out.println(value1);//LJL
- 删除键值对
/*
public V remove(Object key):把指定的键 所对应的键值对 在集合中删除,并且返回被删除的值
返回值:V
key存在,v返回被删除的值
key不存在,v返回null
*/
String v1 = map.remove("LJL");//没有键“LJL”
System.out.println(v1);//返回null
String v2 = map.remove("YY");//有键“YY”
System.out.println(v2);//返回被删除的键所对应的值,LJL
- 根据键,返回值
/*
public V get(Object key):根据指定的键 返回集合中对应的值
返回值:
key存在,返回对应的value值
key不存在,返回null
*/
- 判断是否存在键
/*
boolean containsKey(Object key):判断集合中是否包含所对应的键
返回值:
key存在,返回true
key不存在,返回值false
*/
- 遍历所有的键值对的方式1
/*
public Set<K> keySet():获取Map集合中所有的键,存储到Set集合中
返回值:
返回此映射中包含的键的Set视图
Map集合的第一种遍历方式:通过键找值
1、使用Map集合中的keySet()方法,把Map集合中的所有key取出来,存储到一个Set集合中
2、遍历Set集合,获取Map集合中的每一个key
3、通过Map集合中的get(key)方法,通过key找到value
*/
//创建Map
Map<String,Integer> map = new HashMap<>();
map.put("123",123);
map.put("234",234);
map.put("567",567);
//1、使用Map集合中的keySet()方法,把Map集合中的所有key取出来,存储到一个Set集合中
Set<String> set = map.keySet();
//System.out.println(set);//[123, 234, 567]
//2、遍历Set集合,获取Map集合中的每一个key
//使用迭代器遍历
Iterator<String> it = set.iterator();
while(it.hasNext()){
String key = it.next();
//3、通过Map集合中的get(key)方法,通过key找到value
Integer value = map.get(key);
System.out.println(key+"="+value);//123=123 234=234 567=567
}
//使用增强for循环遍历
for(String key:map.keySet()){
//3、通过Map集合中的get(key)方法,通过key找到value
Integer value = map.get(key);
System.out.println(key+"="+value);//123=123 234=234 567=567
}
- 遍历所有的键值对的方式2
/*
public Set<Map.Entry<K,V>> entrySet()//:
返回值:
Map集合中所有的键值对对象Entry,存储到Set集合中(Set可以遍历。获取每个entry)
Entry对象的方法:
public K getKey():获取key
public V getValue():获取value
Map集合的第二种遍历方式:通过Entry对象获取键和值
1、使用Map集合中的entrySet()方法,把Map集合中的多个Entry对象取出来,存储到一个Set集合中
2、遍历Set集合,获取每一个Entry对象
3、使用Entry对象中的方法getKey()和getValue()获取键与值
*/
//1、使用Map集合中的entrySet()方法,把Map集合中的多个Entry对象取出来,存储到一个Set集合中
Set<Map.Entry<String, Integer>> set = map.entrySet();
//2、遍历Set集合,获取每一个Entry对象
//迭代器遍历Set
Iterator<Map.Entry<String, Integer>> it = set.iterator();
while(it.hasNext()){
Map.Entry<String, Integer> next = it.next();
//3、使用Entry对象中的方法getKey()和getValue()获取键与值
System.out.println(next.getKey()+"="+next.getValue());//123=123 234=234 567=567
}
//使用增强for循环遍历Set
for (Map.Entry<String, Integer> entry : set) {
//3、使用Entry对象中的方法getKey()和getValue()获取键与值
System.out.println(entry.getKey()+"="+entry.getValue());//123=123 234=234 567=567
}
5.4 Map集合存储自定义类型的键值
注意事项: Map集合需要保证key是唯一的:作为key的元素,需要【重写hashCode方法和equals方法,以保证key唯一】。
例如:
Map<Student, String> map = new HashMap<>();//Student作为键,必须重写hashCode和equals方法
5.5 HashTable集合
java.util.HashTable<K,V>集合 implements Map<K,V>接口;
区别:
HashMap集合底层也是一个哈希表,是一个线程不安全的集合,是多线程集合,速度快;
HashMap集合(之前学的所有集合):可以存储null值和null键
HashTable集合底层是一个哈希表,是一个线程安全的集合,是单线程集合,速度慢;
HashTable集合:不可以存储null值和null键;
HashTable和Vector集合一样,在jdk1.2版本之后被更先进的集合(HashMap、ArrayList)所取代;但是HashTable的子类Properties集合,是一个唯一和IO流相结合的集合,。
5.6 List、Set、Map接口添加元素的新特性
JDK9新特性:
- List接口、Set接口、Map接口:添加了一个新的静态方法of,可以给集合一次性添加多个元素。
static <E> List<E> of (E... elements)
- 使用前提: 集合中存储的元素个数已经确定。
注意事项:
- of方法只适用于List接口、Set接口、Map接口,不适用于接口的实现类(如ArrayList,HashSet,HashMap等等);
- of方法的返回值是一个不能改变的集合,集合不能使用add、put方法添加元素,否则会抛出异常;
- Set接口和Map接口在调用of接口的时候,不能有重复的元素,否则会抛出异常;
6、泛型
创建集合对象时,如果不使用泛型。那么默认的类型就是Object类型,可以存储任意类型的数据,看似非常美好。但是这引发了一个弊端:因为一个集合中存储了任意类型的数据,很容易引发异常,不安全。
所以在创建集合对象是,往往使用泛型,这有如下两点好处:
- 避免了类型转换的麻烦,存储什么类型,取出什么类型
- 把运行期异常提升到了编译期异常
但是也有坏处:泛型是什么类型,只能存储什么类型的数据,有了一定的局限性。
6.1 定义和使用含有泛型的类
泛型是一个未知的数据类型,当我们不确定使用什么数据类型时,就可以使用泛型;
泛型可以接收任意的数据类型,如Integer、String等等;
在创建对象的时候,再确定泛型的具体数据类型。
使用方法:
/*
格式:
public class 类名称<E>{
//...
}
*/
public class Demo01GenericClass<E> {
private E name;
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
}
6.2 定义和使用含有泛型的方法
定义含有泛型的方法:泛型定义在方法的【修饰符和返回值类型之间】
使用格式:
/*
修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)){
//...
}
*/
//定义一个含有泛型的方法
public <M> void Method01(M m){
System.out.println(m);
}
//定义一个含有泛型的静态方法
public static <M> void Method02(M m){
System.out.println(m);
}
6.3 定义和使用含有泛型的接口
定义一个含有泛型的接口:
/*
格式:
public interface 接口名称<E>{
//抽象方法(参数列表为泛型)
}
*/
public interface Demo01GenericInterface<E> {
void method(E e);
}
实现 含有泛型的接口 的实现类之一:
/*
第一种方式:在定义实现类的时候,指定接口的泛型的类型。
格式:
public class 实现类名称 implements 接口名称<泛型的类型>{
//...
}
*/
public class Demo01GenericInterfaceImpl01 implements Demo01GenericInterface<String>{
@Override
public void method(String s) {
System.out.println(s);
}
}
//调用这种实现类:不需要再指定泛型了
Demo01GenericInterfaceImpl01 dgi1 = new Demo01GenericInterfaceImpl01();
dgi1.method("yy");//yy
实现 含有泛型的接口 的实现类之二:
/*
第二种方式:在定义实现类的时候,不指定泛型的类型
格式:
public class 实现类名称<泛型> implements 接口名称<泛型>{
//...
}
*/
public class Demo01GenericInterfaceImpl02<E> implements Demo01GenericInterface<E>{
@Override
public void method(E e) {
System.out.println(e);
}
}
//调用这种实现类,需要指定泛型的具体数据类型,如ArrayList等等
Demo01GenericInterfaceImpl02<Integer> dgi2 = new Demo01GenericInterfaceImpl02<>();
dgi2.method(22);//22
6.4 泛型通配符
当使用 有泛型的类或者接口 时,传递的数据的类型不确定,可以使用通配符<?>表示。
但是一旦使用了泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
6.5 泛型通配符的高级使用:受限泛型
- 泛型的上限限定:<? extends E>:表示使用的泛型只能是E类型的子类/本身
- 泛型的下限限定:<? super E>:表示使用的泛型只能是E类型的父类/本身