集合的概念
集合可以简单理解为一个长度可以改变,可以保存任意数据类型的动态数组。
在 Java中,集合不是由一个类来完成的,而是由一组接口和类构成了一个框架体系。大致可分为3层,最上层是一组接口,继而是接口的实现类,接下来是对集合进行各种操作的工具类
| 接口 | 描述 |
|---|---|
| Collection | 集合框架最基本的接口,一个 Collection存储一组无序、不唯一的对象,一般不直接使用该接口 |
| List | Collection的子接口,存储一组有序、不唯一的对象,常用的接口之一 |
| Set | Collection的子接口,存储一组无序、唯一的对象 |
| Map | 独立于Collection的另外一个接口,存储一组键值对象,提供键到值的映射 |
| Iterator | 输出集合元素的接口,一般适用于无序集合,从前到后单向输出 |
| ListIterator | Iterator的子接口,可以双向输出集合中的元素 |
| Enumeration | 传统的输出接口,已被 Iterator所取代 |
| SortedSet | Set的子接口,可对集合中的元素进行排序 |
| SorteMap | Map的子接口,可对集合中的键值元素进行排序 |
| Queue | 队列接口,此接口的子类可实现队列操作 |
| Map.Entry | Map的内部接口,描述Map中的一个键值对元素 |
Collection 接口
Collection是集合框架中最基础的父接口,可以存储一组无序,不唯一的对象。一般不直接使用该接口,也不能被实例化,只是用来提供规范定义,Collection 接口的定义如下所示。
public interface Collection<E> extends Iterable<E>{}
可以看到 Collection 是 Iterable 的子接口,Collection 和 Iterable 后面的<E>表示它们都使用了泛型的定义,泛型是指在操作集合时需要指定具体的数据类型,这样可以保证数据的安全性。Collection接口常用方法的描述如下:
| 方法 | 描述 |
|---|---|
| int size() | 获取集合长度 |
| boolean isEmpty() | 判断集合是否为空 |
| boolean contains(Object o) | 判断集合中是否存在某个对象 |
| Iterator<E> iterator() | 实例化Iterator接口,遍历集合 |
| Object[] toArray() | 将集合转换为一个object类型的对象数组 |
| 将集合转换为一个指定数据类型的对象数组 | |
| boolean add(E e) | 向集合中添加元素 |
| boolean remove(Object o) | 从集合中移除元素 |
| boolean containsAll(Collection<?> c) | 判断集合中是否存在某个集合的所有元素 |
| boolean addAll(Collection<? extends E> c) | 向集合中添加某个集合的所有元素 |
| boolean removeAll(Collection<?> c) | 从集合中移除某个集合的所有元素 |
| default boolean removeIf(Predicate<? super E> filter) | 从集合中移除满足给定条件的集合的所有元素 |
| boolean retainAll(Collection<?> c) | 对集合进行操作,只保留包含在目标集合中的元素 |
| void clear | 清除集合中的所有元素 |
| boolean equals(Object o) | 比较两个集合是否相等 |
| int hashCode() | 获取集合的散列值 |
| default Spliterator<E> spliterator() | 将集合转化为一个指定数据类型的并行迭代器 |
| default Stream<E> stream() | 将集合转换为一个流 |
| default Stream<E> parallelStream() | 将集合转换为一个可并行的流 |
Collection 的子接口
Collection 作为集合的基本接口,在实际开发中一般不直接使用,而是使用其子接口进行开发,Collection主要的子接口如下:
- List:存放有序,不唯一的元素。
- Set:存放无序,唯一的元素。
- Queue:队列接口
List接口
List 是 Collection 的常用的子接口,可以存储一组有序、不唯一的对象,List接口的定义如下
public interface List<E> extends Collection<E>{ }
List 接口在继承Collection接口的基础上进行了扩展,常用的扩展方法如下
| 方法 | 描述 |
|---|---|
| E get(int index) | 通过下标获取集合中指定位置的元素 |
| E set(int index, E element) | 替换集合中指定位置的元素 |
| void add(int index, E element) | 向集合中的指定位置添加元素 |
| E remove(int index) | 通过下标删除集合中指定位置的元素 |
| int indexOf(Object o) | 查找某个对象在集合中的位置 |
| int lastIndexOf(Object o) | 从后向前查找某个对象在集合中的位置 |
| ListIterator<E> listIterator() | 实例化ListIterator接口 |
| List<E> subList(int fromIndex, int toIndex) | 获取集合中的子集合 |
List接口的实现类
ArrayList 是开发中经常使用到的实现类,实现了长度可变的数组。可以在内存中分配连续的空间,底层是基于索引的数据结构,所以访问元素效率较高。缺点是若添加或者删除元素,需要移动兄弟元素的位置,效率较低。
public class Main {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");
list.add("c++");
list.add("python");
System.out.println("打印list:" + list);
System.out.println("list的长度:" + list.size());
System.out.println("list是否包含java:" + list.contains("java"));
System.out.println("------- 打印list ------");
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.print(iterator.next() + ", ");
}
System.out.println("");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + ", ");
}
System.out.println("");
System.out.println("------- 移除list元素 ------");
list.remove("hello");
list.remove(0);
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + ", ");
}
System.out.println("");
System.out.println("------- list添加元素 ------");
list.add(0, "time");
list.add(1, "sec");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + ", ");
}
System.out.println("");
System.out.println("------- list替换元素 ------");
list.set(0, "one");
list.set(1, "two");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + ", ");
}
System.out.println("");
System.out.println("c++ 在集合中的下标:" + list.indexOf("c++"));
System.out.println("--------------------");
List list1 = list.subList(2,5);
System.out.println(list1);
}
}
// 输出
打印list:[hello, world, java, c++, python]
list的长度:5
list是否包含java:true
------- 打印list ------
hello, world, java, c++, python,
hello, world, java, c++, python,
------- 移除list元素 ------
java, c++, python,
------- list添加元素 ------
time, sec, java, c++, python,
------- list替换元素 ------
one, two, java, c++, python,
c++ 在集合中的下标:3
--------------------
[java, c++, python]
Vector 是一个早期的 List 实现类,用法基本与ArrayList一致。Stack 是 Vector的子类,实现了一个 “后进先出”的栈
public class Main {
public static void main(String[] args) {
Stack stack = new Stack();
stack.push("one");
stack.push("two");
stack.push("three");
for (int i = 0; i < stack.size(); i++) {
System.out.print(stack.get(i) + " ");
}
System.out.println("");
System.out.println("栈顶元素:" + stack.peek());
for (int i = 0; i < stack.size(); i++) {
System.out.print(stack.get(i) + " ");
}
System.out.println("");
System.out.println("栈顶元素:" + stack.pop());
for (int i = 0; i < stack.size(); i++) {
System.out.print(stack.get(i) + " ");
}
System.out.println("");
}
}
// 输出
one two three
栈顶元素:three
one two three
栈底元素:three
one two
ListedList 实现了一个“先进先出”的队列,采用链表的形式存储分散的内存空间。元素和元素之间通过存储彼此的位置信息来形成连接关系,通过位置信息找到前后节点的关系,所以添加和删除元素的效率高;ListedList不但要保存节点数据,还需要保存前后节点的位置信息,所以需要更多的内存空间,查询元素的效率也低。
public class Main {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add("Tom");
linkedList.add("Alice");
linkedList.add("Bob");
linkedList.add("Tim");
for (int i = 0; i < linkedList.size(); i++) {
System.out.print(linkedList.get(i) + " ");
}
System.out.println("");
linkedList.offer("hong");
linkedList.addLast("ming");
System.out.println(linkedList);
linkedList.push("wang");
linkedList.addFirst("zhang");
System.out.println(linkedList);
System.out.println("第一个元素:" + linkedList.peekFirst());
System.out.println(linkedList);
System.out.println("最后一个元素:" + linkedList.peekLast());
System.out.println(linkedList);
System.out.println("取出第一个元素:" + linkedList.pop());
System.out.println(linkedList);
System.out.println("取出最后一个元素:" + linkedList.pollLast());
System.out.println(linkedList);
}
}
// 输出
Tom Alice Bob Tim
[Tom, Alice, Bob, Tim, hong, ming]
[zhang, wang, Tom, Alice, Bob, Tim, hong, ming]
第一个元素:zhang
[zhang, wang, Tom, Alice, Bob, Tim, hong, ming]
最后一个元素:ming
[zhang, wang, Tom, Alice, Bob, Tim, hong, ming]
取出第一个元素:zhang
[wang, Tom, Alice, Bob, Tim, hong, ming]
取出最后一个元素:ming
[wang, Tom, Alice, Bob, Tim, hong]
LinkedList 和 stack 都有 pop()方法,都是取出集合的第一个元素,但是可以看到两者的顺序是相反的,Stack采用的是“后进先出”的方法,是栈的形式;LinkedList 采用的是“先进先出”的方式,是队列的形式
Set接口
Set是Collection的子接口,Set接口以散列的形式存储数据,所以元素没有顺序,可以存储一组无序且唯一的对象。
实际开发中不能直接实例化Set,需要对其实现类进行实例化再完成业务操作。Set的常用实现类主要有 HashSet、LinkedHashSet、TreeSet。
HashSet
HashSet是经常使用的实现类,存储一组无序且唯一的对象。无序是指元素的存储顺序和遍历顺序不一致。
public class Main {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("hello");
hashSet.add("world");
hashSet.add("java");
hashSet.add("c++");
System.out.println(hashSet);
System.out.println("hashSet的长度" + hashSet.size());
Iterator iterator = hashSet.iterator();
while (iterator.hasNext()){
System.out.print(iterator.next() + ", ");
}
System.out.println("");
hashSet.remove("c++");
System.out.println(hashSet);
}
}
// 输出
[c++, world, java, hello]
hashSet的长度4
c++, world, java, hello,
[world, java, hello]
LinkedHashSet
LinkedHashSet 是Set的另外一个子接口,可以存储一组有序且唯一的元素,有序是指元素的存储顺序和遍历顺序是一致的。其用法跟Hashset类似。
对LinkedHashSet集合添加两个“hello”元素,但是只会保存一个,这是因为LinkedHashSet集合的元素是唯一的,即不能出现两个相等的元素。字符串和对象都必须满足这条。
LinkedHashSet 判断两个对象是否相等的过程:首先会比较对象的hashCode,如果不相等,则认为不是同一个对象,可以添加。如果hashCode相等,还不能认为两个对象就是相等的,需要通过equals()方法进一步判断。如果equals()方法为true,则不会重新添加,如果equals()方法为false,则正常添加。先判断hashCode是否相等可以减少equals()方法的调用,提高效率。
TreeSet
TreeSet中保存的元素也是有序的且唯一,但是TreeSet的有序和LinkedHashSet的有序并不一样。
LinkedHashSet的有序是指元素的存储顺序和遍历顺序一样。TreeSet的有序是指集合内部会自动给所有的元素安装升序进行排列,即无论存入元素的顺序是什么,遍历时会按照升序进行输出。
public class Main {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add("1");
treeSet.add("3");
treeSet.add("5");
treeSet.add("4");
treeSet.add("2");
System.out.println(treeSet);
}
}
// 输出
[1, 2, 3, 4, 5]
存入对象并进行排序
public class Num implements Comparable
{
private int num;
public Num(int num){
this.num = num;
}
}
public class Main {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add(new Num(1));
treeSet.add(new Num(3));
treeSet.add(new Num(4));
treeSet.add(new Num(2));
treeSet.add(new Num(5));
System.out.println(treeSet);
}
}
// 输出
Exception in thread "main" java.lang.ClassCastException: com.test.a.Num cannot be cast to java.lang.Comparable
报错原因是Num对象不具备排序功能,解决方法是实现Comparable接口。
public class Num implements Comparable
{
private int num;
public Num(int num){
this.num = num;
}
@Override
public int compareTo(Object o) {
Num n = (Num) o;
if (this.num > n.num){
return 1;
}else if (this.num == n.num){
return 0;
}else {
return -1;
}
}
@Override
public String toString() {
return "Num{" +"num=" + num +'}';
}
}
public class Main {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add(new Num(1));
treeSet.add(new Num(3));
treeSet.add(new Num(4));
treeSet.add(new Num(2));
treeSet.add(new Num(5));
System.out.println(treeSet);
}
}
// 输出
[Num{num=1}, Num{num=2}, Num{num=3}, Num{num=4}, Num{num=5}]
Map接口
Map中的元素都是以key-value的键值对映射形式存储的。Map接口定义时使用了泛型,并且定义了两个泛型K和V,K表示key,规定了键元素的数据类型,V表示value,规定了值元素的数据类型。Map接口的常用方法如下:
| 方法 | 描述 |
|---|---|
| int size() | 获取集合长度 |
| boolean isEmpty() | 判断集合是否为空 |
| boolean containsKey(Object key) | 判断集合中是否存在某个key值 |
| boolcontainsValue(Object value) | 判断集合中是否存在某个value值 |
| V get(Object key) | 取出集合中key对应的value值 |
| V put(K key, V value) | 向集合中存入一组 key-value的元素 |
| V remove(Object key) | 删除集合中key对应的value值 |
| void pullAll(Map<? extends K,? extends V> m) | 向集合中添加另外一个map集合 |
| void clear() | 清除集合中的所有元素 |
| Set<K> keySet() | 取出集合中所有的key,返回一个Set集合 |
| Collection<V> values() | 取出集合中的所有value,返回一个Collection集合 |
| Set<Map.Entry<K,V>> entrySet() | 将Map对象转换为Set对象 |
| int hashCode() | 获取集合的散列值 |
| boolean equals(Object o) | 比较两个集合是否相等 |
Map接口的实现类
Map接口常用的实现类如下:
- HashMap:存储一组无序,key不可重复,但value可重复的元素
- Hashtable:存储一组无序,key不可重复,但value可重复的元素
- TreeMap:存储一组有序,key不可重复,但value可重复的元素,可以按照key来排序。
HashMap 的使用方法
public class Main {
public static void main(String[] args) {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("a","hello");
hashMap.put("s","world");
hashMap.put("v","java");
hashMap.put("w","c++");
System.out.println(hashMap);
hashMap.remove("a");
System.out.println("删除后:" + hashMap);
hashMap.put("g","google");
System.out.println("添加后:" + hashMap);
if (hashMap.containsKey("s")){
System.out.println("map集合中存在值为s的key");
}else {
System.out.println("map集合中不存在值为s的key");
}
if (hashMap.containsValue("java1")){
System.out.println("map集合中存在值为java的value");
}else {
System.out.println("map集合中不存在值为java的value");
}
Set keys = hashMap.keySet();
System.out.println(keys);
Collection<String> values = hashMap.values();
System.out.println(values);
}
}
// 输出
{a=hello, s=world, v=java, w=c++}
删除后:{s=world, v=java, w=c++}
添加后:{s=world, v=java, w=c++, g=google}
map集合中存在值为s的key
map集合中不存在值为java的value
[s, v, w, g]
[world, java, c++, google]
Hashtable 和 HashMap 的用法基本一样,与 HashMap相比:Hashtable是线程安全的,但是性能较低。HashMap是非线程安全的,但是性能较高。实际开发中,HashMap的使用频率更高。
TreeMap主要功能就是按照key对集合中的数据进行排序。
public class Main {
public static void main(String[] args) {
TreeMap<Integer, String> treeMap = new TreeMap<>();
treeMap.put(3,"java");
treeMap.put(5,"css");
treeMap.put(1,"c++");
treeMap.put(4,"nodejs");
treeMap.put(2,"python");
System.out.println(treeMap);
}
}
// 输出
{1=c++, 2=python, 3=java, 4=nodejs, 5=css}
Collections工具类
集合除了可以存储数据,也提供了很多方法来对数据进行操作,但是这些方法都有局限性,实际操作也不方便。JDK提供了工具类Collections,专门用来操作集合,例如添加元素,对元素进行排序,替换元素等。Collections和Arrays很类似,Arrays是针对数组的工具类,Collections是针对集合的工具类。
public class Main {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("hello");
arrayList.add("world");
System.out.println("添加之前的集合:" + arrayList);
Collections.addAll(arrayList,"java", "python", "php");
System.out.println("添加之后的集合:" + arrayList);
Collections.reverse(arrayList);
System.out.println("反转之后的集合:" + arrayList);
Collections.swap(arrayList, 0, 2);
System.out.println("交换之后的集合:" + arrayList);
int index = Collections.binarySearch(arrayList, "php");
System.out.println("php在集合中的小标:" + index);
Collections.replaceAll(arrayList, "world" , "js");
System.out.println("替换之后的集合:" + arrayList);
}
}
// 输出
添加之前的集合:[hello, world]
添加之后的集合:[hello, world, java, python, php]
反转之后的集合:[php, python, java, world, hello]
交换之后的集合:[java, python, php, world, hello]
php在集合中的小标:2
替换之后的集合:[java, python, php, js, hello]
泛型
泛型是指在类定义时不指定类中信息的具体数据类型,而是用一个标识符来代替,当外部实例化对象来指定具体的数据类型。
使用泛型可以避免数据不安全的隐患,接口是支持泛型的,所有在实例化ArrayList对象的时候就指定泛型为Integer,限制存入集合的数据,除了Integer类型以外的数据无法存入集合,当然指定数据类型的子类是可以存入的,这样就保证了集合中数据类型的统一性。在取数据的时候不会抛出数据转换类型失败的异常,同时在指定泛型后,集合内部就会以指定的数据类型来保存所有数据,取数据时就省去了强制类型转换的步骤。
泛型通配符
定义一个参数为ArrayList类型的方法时,希望该方法既可以接收泛型为String的集合参数,也可以接收泛型为Integer的集合参数。解决方法是使用ArrayList<?>,它可以使用任意的泛型类型对象。
public class Main {
public static void main(String[] args) {
ArrayList<String> strings = new ArrayList<>();
ArrayList<Integer> integers = new ArrayList<>();
test(strings);
test(integers);
}
public static void test(ArrayList<?> list){
System.out.println(list);
}
}
泛型上限和下限
使用泛型时,往往数据类型会有限制,只能使用一种具体的数据类型,如果希望在此基础上进行适量扩容,可以通过泛型上限和下限来完成。
泛型上限表示实例化时的具体数据类型,可以是上限类型的子类或者是上限类型本身,用extends关键字来修饰。
泛型下限表示实例化时的具体数据类型,可以时下限类型的父类或者是下限类型本身,用super关键字修饰
- 泛型上限:类名<泛型标识 extends 上限类名>
- 泛型下限:类名<泛型标识 super 下限类名>
泛型接口
在定义类时可以添加泛型,在定义接口时也可以添加泛型。声明泛型接口的语法和声明泛型类很相似,在接口名后加上<T>即可,基本语法:访问权限修饰符 interface 接口名<泛型标识>
实现泛型接口有两种方式,一种是实现类在定义时继续使用泛型标识,另一种是实现类在定义时直接给出具体的数据类型.
两种不同实现类的实例化方式也不同,一种需要在实例化时指定具体的数据类型,另外一种在实例化不需要指定具体的数据类型。
public class MyInterface<T> implements Interface<T>{}
MyInterface<String> myInterface = new MyInterface<String>("接口");
public class MyInterface implements Interface<String>{}
MyInterface myInterface = new MyInterface("接口");