08-集合1-集合类体系结构、Collection、List、泛型
原创
©著作权归作者所有:来自51CTO博客作者武大保安的原创作品,请联系作者获取转载授权,否则将追究法律责任
文章目录
1.Collection集合
1.1数组和集合的区别【理解】
- 数组的长度是不可变的,集合的长度是可变的
- 数组可以存基本数据类型和引用数据类型
集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类
public static void main(String[] args) {
int[] arr1={1,2,3};
String[] arr2={"a","b","c"};
System.out.println(Arrays.toString(arr1));
System.out.println(Arrays.toString(arr2));
System.out.println("----------------------");
ArrayList<String> list1 = new ArrayList<>();
list1.add("a");list1.add("b");list1.add("c");
System.out.println(list1);
//ArrayList<int> list2 = new ArrayList<>();//报错 只能存储引用数据类型 不能存储基本数据类型
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(1);list2.add(2);list2.add(3);
System.out.println(list2);
}
1.2集合类体系结构【理解】
蓝色外框都是接口、红色外框都是具体实现类
1.3Collection 集合概述和使用【应用】
- 是单列集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
- JDK 不提供此接口的任何直接实现.它提供更具体的子接口(如Set和List)实现
Collection集合常用方法(所有单列集合:list和set都 继承了的(共有)的方法)
方法名
| 说明
|
boolean add(E e)
| 添加元素
|
boolean remove(Object o)
| 从集合中移除指定的元素(第一次出现的那一个 且 成功返回true 失败返回false) |
boolean removeIf(Object o)
| 根据条件进行移除
|
void clear()
| 清空集合中的元素
|
boolean contains(Object o)
| 判断集合中是否存在指定的元素
|
boolean isEmpty()
| 判断集合是否为空
|
int size()
| 集合的长度,也就是集合中元素的个数
|
代码1:add&remove
public static void main(String[] args) {
Collection<String> c =new ArrayList<>();//只能new实现类 不能new接口
// boolean add(E e) | 添加元素
c.add("aaa");c.add("bbb");c.add("ccc");
System.out.println(c);//[aaa, bbb, ccc]
//boolean remove(Object o) | 从集合中移除指定的元素(成功返回true 失败返回false)
print("remove");
System.out.println(c.remove("aaa"));//true
System.out.println(c.remove("ddd"));//false
System.out.println(c);//[bbb, ccc]
}
public static void print(String s){
System.out.println("-----------"+s+"-----------");
}
代码2:removeIf
public static void main(String[] args) {
Collection<String> c =new ArrayList<>();//只能new实现类 不能new接口
c.add("aaa");c.add("bbb");c.add("ccc");c.add("dddd");
//boolean removeIf(Object o) | 根据条件进行删除
/*removeIf底层会遍历集合,得到集合中的每一个元素
s依次表示集合中的每一个元素
就会把这每一个元素都到lambda表达式中去判断一下
如果返回的是true,则删除
如果返回的是false,则保留不删除.*/
c.removeIf( (String s)->{return s.length()==3;}/*删除长度为3的*/ );//传入lambda表达式 条件删除
System.out.println(c);//[dddd]
}
代码3:清空集合中的元素
public static void main(String[] args) {
Collection<String> c =new ArrayList<>();//只能new实现类 不能new接口
c.add("aaa");c.add("bbb");c.add("ccc");c.add("dddd");
// void clear() | 清空集合中的元素
System.out.println(c);//[aaa, bbb, ccc, dddd]
c.clear();
System.out.println(c);//[]
}
代码4:判断集合中是否存在指定元素
public static void main(String[] args) {
Collection<String> c =new ArrayList<>();//只能new实现类 不能new接口
c.add("aaa");c.add("bbb");c.add("ccc");c.add("dddd");
//boolean contains(Object o) | 判断集合中是否存在指定的元素
System.out.println(c.contains("aaa"));//true
System.out.println(c.contains("cccc"));//false
}
代码5:集合判空、集合长度
public static void main(String[] args) {
Collection<String> c =new ArrayList<>();//只能new实现类 不能new接口
c.add("aaa");c.add("bbb");c.add("ccc");c.add("dddd");
// boolean isEmpty() | 判断集合是否为空
System.out.println(c.isEmpty());//false
// int size() | 集合的长度,也就是集合中元素的个数
System.out.println(c.size());//4
}
1.4Collection集合的遍历【应用】
- 迭代器,集合的专用遍历方式
- Iterator iterator(): 返回此集合中元素的迭代器,通过集合对象的iterator()方法得到
- Iterator中的常用方法
boolean hasNext(): 判断当前位置是否有元素可以被取出
E next(): 获取当前位置的元素,将迭代器对象移向下一个索引位置 - Collection集合的遍历
public static void main(String[] args) {
Collection<String> list=new ArrayList<>();
list.add("a");list.add("b");list.add("c");list.add("d");
//1.获取迭代器对象
//迭代器对象一旦被创建出来,默认指向集合的0索引处
Iterator<String> it = list.iterator();
while (it.hasNext()){//当前位置是否有元素可以被取出
System.out.println(it.next());//取出当前位置的元素 + 将迭代器往后移动一个索引的位置
}
}
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<>();
list.add("a");list.add("b");list.add("b");list.add("c");list.add("d");
Iterator<String> it = list.iterator();
while (it.hasNext()){
String s = it.next();
if("b".equals(s)){
it.remove();
//迭代器不会因为删除而导致指针混乱(应该自动帮你实现了it--啥的 或者it一起左移了)
}
}
System.out.println(list);//[a, c, d]
}
普通根据下标删除的弊端
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<>();
list.add("a");list.add("b");list.add("b");list.add("c");list.add("d");
for (int i = 0; i < list.size(); i++) {
if("b".equals(list.get(i))){//ArrayList 才有get方法 Collection没有
list.remove(i);
i--;//必须i-- 否则bug list.remove(i);会自动将后面元素整体后移动(标准顺序表)然后表长-1
//System.out.println(list.size());
}
}
System.out.println(list);//[a, c, d]
//remove后不加 i--; 输出[a, b, c, d] 原因,每次remove后集合元素会整体前移,然后i++,正好错过了相邻的b
list.add("e");list.add("e");
System.out.println(list);
while (list.contains("e")) //结合api删除 多快 不过可能时间效率太慢了 不提倡
list.remove("e");//一次remove只能删除一个
System.out.println(list);
}
1.5增强for循环【应用】
- 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
- 实现Iterable接口的类才可以使用迭代器和增强for
- 简化数组和Collection集合的遍历 (
Map接口没有继承Iterable接口 不能直接用
- 格式
for(集合/数组中元素的数据类型 变量名 : 集合/数组名) {
// 已经将当前遍历到的元素封装到变量中了,直接使用变量即可
} - 代码
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<>();
list.add("a");list.add("b");list.add("b");list.add("c");list.add("d");
//只能是数组或者Collection(单列集合) Map不能用(需要自己实现Iterable接口后才可以用)
for (String s : list) {
System.out.print(s+" ");//a b b c d
}
}
注意:增强for无法修改集合
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<>();
list.add("a");list.add("b");list.add("b");list.add("c");list.add("d");
//只能是数组或者Collection(单列集合) Map不能用(需要自己实现Iterable接口后才可以用)
for (String s : list) {
s="q";//s只是list元素(新地址下)的一个副本 不要妄想通过增强for修改集合
}
System.out.println(list);//[a, b, b, c, d]
}
三种循环使用场景
2.List集合
2.1List集合的概述和特点【记忆】
- 有序集合,这里的有序指的是存取顺序
- 用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引访问元素,并搜索列表中的元素
- 与Set集合不同,列表通常允许重复的元素
2.2List集合的特有方法【应用】
List接口就有的
方法名
| 描述
|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
public static void main(String[] args) {
List<String> list = new ArrayList<>();//类型采用List接口类型
list.add("aaa");list.add("bbb");list.add("bbb");
Iterator<String> it = list.iterator();
while (it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("---------------");
for (String s : list) {
System.out.println(s);
}
}
- 代码2:List.add(index,element) 指定位置插入
public static void main(String[] args) {
List<String> list = new ArrayList<>();//类型采用List接口类型
list.add("aaa");list.add("bbb");list.add("ccc");
// void add(int index,E element) 在此集合中的指定位置插入指定的元素
// 原来位置上的元素往后挪一个索引.
list.add(0,"qqq");
System.out.println(list);//[qqq, aaa, bbb, ccc]
}
- 代码3:List.remove(0) 删除指定位置处的元素
public static void main(String[] args) {
List<String> list = new ArrayList<>();//类型采用List接口类型
list.add("aaa");list.add("bbb");list.add("ccc");
// E remove(int index) 删除指定索引处的元素,返回被删除的元素
//在List集合中有两个删除的方法
//第一个 删除指定的元素,返回值表示当前元素是否删除成功
//第二个 删除指定索引的元素,返回值表示实际删除的元素
String s = list.remove(0);
System.out.println(s);//aaa
System.out.println(list);//[bbb, ccc]
/* boolean remove(Object o) //Collection就有,继承下来的
从此列表中移除第一次出现的指定元素(如果存在)(可选操作)。 */
}
- 代码4.1:List.set(index,元素) 修改指定索引处的元素 (被替换的那个元素,在集合中就不存在了)
- 代码4.2:List.get(int index) 返回指定索引处的元素
public static void main(String[] args) {
List<String> list = new ArrayList<>();//类型采用List接口类型
list.add("aaa");list.add("bbb");list.add("ccc");
// E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
//被替换的那个元素,在集合中就不存在了.
String s = list.set(0, "qqq");
System.out.println(s);//aaa (返回被修改消失的元素)
System.out.println(list);//[qqq, bbb, ccc]
// E get(int index) 返回指定索引处的元素
String s1 = list.get(0);
System.out.println(s1);//qqq
}
3.数据结构
3.1数据结构之栈和队列【记忆】
3.2数据结构之数组和链表【记忆】
4.List集合的实现类
4.1List集合子类的特点【记忆】
- ArrayList集合
底层是数组结构实现,查询快、增删慢 (add、remove、get…算法完全就是顺序表的那些操作,需要知道)
[ArrayList.get(i) 数组当然可以随机访问(查看源码也确实有个数组)]
ArrayList.java源码里面一句话: private static final int DEFAULT_CAPACITY = 10;
则:初试数组容量0,第一次申请数组容量10 (以后再满了应该是1.5倍地增长 还带有原来数组元素的复制)
- LinkedList集合
底层是链表结构实现,查询慢、增删快(add、remove、get…算法完全就是链表的那些操作,需要知道)
LinkedList也有get(i)方法,只不过效率很低罢了 [List接口继承下来的嘛]
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("aaa");list.add("bbb");list.add("ccc");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));//链表的get(i)方法,效率很低
}
System.out.println("----------------------------");
Iterator<String> it = list.iterator();
while (it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("----------------------------");
for (String s : list) {
System.out.println(s);
}
}
4.2LinkedList集合的特有功能【应用】
方法名
| 说明
|
public void addFirst(E e)
| 在该列表开头插入指定的元素
|
public void addLast(E e)
| 将指定的元素追加到此列表的末尾
|
public E getFirst()
| 返回此列表中的第一个元素
|
public E getLast()
| 返回此列表中的最后一个元素
|
public E removeFirst()
| 从此列表中删除并返回第一个元素
|
public E removeLast()
| 从此列表中删除并返回最后一个元素
|
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("aaa");list.add("bbb");list.add("ccc");
// public void addFirst(E e) | 在该列表开头插入指定的元素 |
list.addFirst("qqq");
System.out.println(list);//[qqq, aaa, bbb, ccc]
// public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
list.addLast("www");
System.out.println(list);//[qqq, aaa, bbb, ccc, www]
// public E removeFirst() | 从此列表中删除并返回第一个元素 |
String s = list.removeFirst();
System.out.println(s);//qqq (返回被删元素(也就是第一个元素))
System.out.println(list);//[aaa, bbb, ccc, www]
// public E removeLast() | 从此列表中删除并返回最后一个元素 |
String s1 = list.removeLast();
System.out.println(s1);//www
System.out.println(list);//[aaa, bbb, ccc]
// public E getFirst() | 返回此列表中的第一个元素 |
String first = list.getFirst();
System.out.println(first);//aaa
// public E getLast() | 返回此列表中的最后一个元素 |
String last = list.getLast();
System.out.println(last);//ccc
}
查LinkedList.java源码 发现里面有个内部类 (是双向链表的结构啊)
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
还维护了首尾指针
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
get(i)方法查找,i<size/2从头查 i>=size/2 从后查 时间复杂度O(size/2)
因为是双向链表,且维护了首尾指针,因此可以做这种优化
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {//i<size/2
Node<E> x = first;
for (int i = 0; i < index; i++)//从前往后查找
x = x.next;
return x;//此时下标i==index
} else { //i>=size/2
Node<E> x = last;
for (int i = size - 1; i > index; i--)//从后往前查找
x = x.prev;
return x;//此时下标i==index
}
}
add方法
5.泛型
5.1泛型概述【理解】
- 泛型的介绍
泛型是JDK5中引入的特性,它提供了编译时类型安全检测机制 - 泛型的好处
- 把运行时期的问题提前到了编译期间
- 避免了强制类型转换
public static void main(String[] args) {
ArrayList list= new ArrayList();
list.add("aaa");list.add("bbb");list.add("ccc");
list.add(123);//123也是Object 此时下面就没办法强转类型了 所以泛型的作用不可缺少
Iterator it = list.iterator();
while (it.hasNext()){
Object next = it.next();//默认类型Object 则该类型的特有方法失去了
//next.length();该类型的特有方法失去了
String s=(String)next;//加上泛型就不存在需要强转的问题了
System.out.println(s.length());//只能强转后才能用
//System.out.print(next+" ");//aaa bbb ccc 只是遍历不影响
}
}
5.2泛型类【应用】
如果一个类的后面有 < E >,表示这个类是一个泛型类 【E可以是任意字母,eg:Q,T,M…】
创建泛型类的对象时,必须要给这个泛型确定具体的数据类型
- <类型>: 指定一种类型的格式.尖括号里面可以任意书写,一般只写一个字母.例如: < E > < T>
- <类型1,类型2…>: 指定多种类型的格式,多种类型之间用逗号隔开.例如: <E,T> <K,V>
public class Box<E> {
private E element;
public E getElement() {
return element;
}
public void setElement(E element) {
this.element = element;
}
}
public static void main(String[] args) {
Box<String> box=new Box<>();
box.setElement("abc");
String s = box.getElement();
System.out.println(s);//abc
//box.setElement(123);//直接编译报错 多好
Box<Integer> box1 = new Box<>();
box1.setElement(123);
Integer i = box1.getElement();
System.out.println(i);//123
}
5.3泛型方法【应用】
5.3.1 泛型方法使用
调用格式
| 方法说明
| 备注
|
Object[] toArray()
| 按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组
| 直接返回Object[] 使用不方便
|
T[] toArray(T[] a)
| 按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组;返回传入的参数指定的数组类型。
| 通过泛型,可以指定返回类型。 将list内容传到T[] a数组中,并返回该数组
|
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aaa");list.add("bbb");list.add("ccc");
Object[] objects = list.toArray();//list转数组
System.out.println(Arrays.toString(objects));//[aaa, bbb, ccc]
//list.toArray()方法的弊端,返回值Object[] 需要强转,非常不便,存在问题
//参数给他一个数组,会将list内容放到参数数组中,同时返回该数组
String[] strings = list.toArray(new String[list.size()]);//类型正确了 ★
System.out.println(Arrays.toString(strings));//[aaa, bbb, ccc]
}
5.3.2 自定义泛型方法
修饰符 <类型> 返回值类型 方法名(类型 变量名) { }
< T>表示这是在定义一个泛型T,告诉编译器本没有T这个类型的
泛型只有在被调用时才有具体类型
public static void main(String[] args) {
//好方便实用的一个方法啊 (不用一个一个慢慢add了 还通用)
ArrayList<String> list = addElement(new ArrayList<String>(), "a", "b", "c", "d");
System.out.println(list);//[a, b, c, d]
ArrayList<Integer> list1 = addElement(new ArrayList<Integer>(), 1, 2, 3, 4);
System.out.println(list1);//[1, 2, 3, 4]
}
//注意定义泛型 <T> 和方法返回值是两个不同的东西
public static <T> ArrayList<T> addElement(ArrayList<T> list,T t1,T t2,T t3,T t4){//后期有技术来实现不定长参数
list.add(t1);list.add(t2);list.add(t3);list.add(t4);
return list;
}
5.4泛型接口【应用】 (和泛型类差不多 接口看成类即可)
修饰符 interface 接口名<类型> { }
注意自定义泛型接口 好多细节 哪里需要泛型定义< E> 哪里又不需要
public class GenericityInterface {
public static void main(String[] args) {
GenericityImpl<String> genericity = new GenericityImpl<>();
genericity.method("abc");//"abc"
//此时这个实现类只是一个普通类
GenericityImpl1 genericity1 = new GenericityImpl1();
genericity1.method(123);//"123"
}
}
interface Genericity<E>{
public abstract void method(E e);//接口定义处已经定义了泛型 此处的泛型方法定义时就不需要再定义泛型<E>了
}
//不指定接口泛型的具体类型 实现类还要有泛型定义<E>
class GenericityImpl<E> implements Genericity<E>{
@Override
public void method(E e) {
System.out.println(e);
}
}
//给出了具体类型后 实现类就不用有泛型定义<E>了
class GenericityImpl1 implements Genericity<Integer>{
@Override
public void method(Integer integer) {
System.out.println(integer);
}
}
5.5类型通配符
- ArrayList<?>: 表示元素类型未知的ArrayList,它的元素可以匹配任何的类型
- 但是并不能把元素添加到ArrayList中了,获取出来的也是父类类型
- ArrayListList <? extends Number>: 它表示的类型是Number或者其子类型
- ArrayListList <? super Number>: 它表示的类型是Number或者其父类型
- 泛型通配符的使用
import java.util.ArrayList;
public class GenericityGlobbing1 {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<Number> list2 = new ArrayList<>();
ArrayList<Object> list3 = new ArrayList<>();
printList(list1);
printList(list2);
method1(list1);
method1(list2);
//method1(list3);//报错了 Number的父类类型不行了(extends修饰的)
//method2(list1);//报错了 Number的子类类型不行了(super修饰的)
method2(list2);
method2(list3);
}
// 泛型通配符: 此时的泛型?,可以是任意类型
private static void printList(ArrayList<?> list){ }//比<E>简单 因为返回值前还要写<E>
//extends 表示传递进来集合的类型,可以是Number类型,也可以是Number所有的子类类型
private static void method1(ArrayList<? extends Number> list){ }
//super 表示传递进来集合的类型,可以是Number类型,也可以是Number所有的父类类型
private static void method2(ArrayList<? super Number> list){ }
}