1.集合有两种:
2.集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型
3.集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象(基本数据类型用对应的包装类)
Collection<Double> list = new ArrayList<>();
list1.add(23.3);//将23.3自动装箱为Double类型后作为参数传给add方法
list1.add(23.0);//整数默认是int,是不能自动装箱为Double类型的,两种解决办法:23d或23.0
list1.add(23d);
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() | 把集合中的元素存储到数组中 |
public boolean addAll(Collection extend E>) | 向集合中加入另一个集合的所有元素 |
关于public boolean addAll(Collection extend E>),public boolean remove(E e)和public Object[] toArray():
addAll方法有一种用法是向集合中加入另一个集合的所有元素:
Collection<String> c1 = new ArrayList<>();
c1.add("java1");
Collection<String> c2 = new ArrayList<>();
c2.add("赵敏");
c2.add("殷素素");
c1.addAll(c2);
为什么remove方法不支持索引删除呢:
Collection<String> c1 = new ArrayList<>();
c1.add("java1");
c1.remove("yyds")
public Object[] toArray()为什么返回值类型是Object[]呢:
Collection<String> c1 = new ArrayList<>();
1.迭代器遍历概述
2.怎么使用迭代器遍历:
首先需要Collection集合获取迭代器:
方法名称 | 说明 |
---|---|
Iterator iterator() | 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的-1的位置 |
Iterator中的常用方法:
方法名称 | 说明 |
---|---|
boolean hasNext() | 询问当前位置的下一个位置是否有元素存在,存在返回true,反之false |
E next() | 获取当前位置的下一个位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界 |
public class CollectionDemo01 {
public static void main(String[] args) {
ArrayList<String> lists = new ArrayList<>();
lists.add("赵敏");
lists.add("小昭");
Iterator<String> it = lists.iterator();
while (it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
}
}
既可以遍历集合也可以遍历数组
它是JDK5之后出现的,其内部原理是一个Iterator迭代器,遍历集合相当于是迭代器的简化写法
实现Iterator接口的类才可以使用迭代器和增强for,Collection接口已经继承了Iterator接口
public interface Collection<E> extends Iterable<E>
格式:
for(元素数据类型 变量名 : 数组或者Collection集合) {
//在此处使用变量即可,该变量就是元素
}
public class CollectionDemo02 {
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("赵敏");
lists.add("小昭");
System.out.println(lists);
for (String ele : lists) {
System.out.println(ele);
}
double[] scores = {100, 99.5 , 59.5};
for (double score : scores) {
System.out.println(score);
// if(score == 59.5){
// score = 100.0; // 修改无意义,不会影响数组的元素值。
// }
}
}
}
注意:使用迭代器或者增强for进行遍历修改值无效,因为这就相当于将实参copy一份交给形参,形参修改的值当然不会影响到实参
得益于JDK8开始的新技术Lambda表达式,提供了一种更简单,更直接的遍历集合的方式
Collection结合Lambda遍历的API:forEach方法
方法名称 | 说明 |
---|---|
default void forEach(Consumer super T> action) | 结合lambda遍历集合 |
public class CollectionDemo03 {
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("赵敏");
lists.add("小昭");
lists.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
System.out.println("=======Lambda简化版========");
lists.forEach(s ->System.out.println(s));
}
}
现在分析一下这段代码:
lists.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
看一下forEach源码:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
栈数据结构的执行特点:后进先出,先进后出
队列数据结构的执行特点:先进先出,后进后出
红黑树增删改查的性能都很好
List集合因为支持索引,所以多了很多索引操作的独特API,其他Collection的功能List也继承了
方法名称 | 说明 |
---|---|
void add(int index, E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index, E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
遍历方式:
为什么默认长度是10呢:
1.先分析add方法源码:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
根据第3行elementData[size++] = e;可知只有向elementData数组添加元素时size值才会增加1,所以此时size为默认值0,也就是说,第一次向集合添加元素时会先调用ensureCapacityInternal(1);注意这里的实参为size + 1 = 0 + 1 = 0
2.看一下ensureCapacityInternal方法的源码:
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
源码中在定义ArrayList类时已经定义了两个空数组和一个常量:
transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private static final int DEFAULT_CAPACITY = 10;
那么我们回到ensureCapacityInternal方法看elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA发现判断结果为真,进入分支语句,然后分支语句中的Math.max(DEFAULT_CAPACITY, minCapacity);第一个参数是10,第二个参数是调用ensureCapacityInternal方法时传入的参数1,显然,这里将10赋值给minCapacity变量.接着调用ensureExplicitCapacity方法并传入参数10
3.看一下ensureExplicitCapacity方法的源码:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
minCapacity我们已经知道是10,elementData在前面也已经说过了是一个空数组,所以其数组长度是0,那么if条件判断为真,调用grow方法进行扩容,并将容量扩容为10.这一系列都做完后回到add方法:
elementData[size++] = e;
将元素赋值给数组中索引为0的位置.至此,解释完了为什么第一次向集合中添加元素时默认数组长度为10
我在分析这个源码的时候发现了ArrayList集合有一个构造器可以手动设置第一次向集合中添加元素时数组长度,现在把这个流程再过一边:
1.看一下这个构造器的源码:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
假如我传入的是15,那么此时会执行this.elementData = new Object[initialCapacity];创建一个长度为15的数组并将其地址赋值给elementData变量(上面我们说过这个变量默认是一个空数组,而此时它将是一个长度为15的数组)
2.add方法逻辑不变,调用ensureCapacityInternal方法并传参1
3.看ensureCapacityInternal方法的源码
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
可知执行minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);时会把较大的15赋值给minCapacity,然后调用ensureExplicitCapacity并传参15
4.看ensureExplicitCapacity方法的源码:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
此时minCapacity和elementData都为15,if条件判断为假,不执行grow方法扩容
如果这个数组长度为15且准备向集合中添加第16个元素,底层怎么执行扩容的呢:
1.看add方法源码:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
调用ensureCapacityInternal方法,此时参数为15 + 1 = 16
2.看ensureCapacityInternal方法的源码:
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
此时if条件判断为假,所以直接调用ensureExplicitCapacity方法并传参16
3.看ensureExplicitCapacity方法的源码:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
先把minCapacity - elementData.length > 0转换为minCapacity > elementData.length这样比较好理解.
minCapacity是容纳所有元素(包括我此时准备向集合中添加的这一个元素)所需的最少数组长度,elementData.length是实际数组长度
minCapacity为16,elementData.length为15,很显眼,此时容纳所有元素所需的最少数组长度>实际数组长度,判断成立,执行grow方法进行扩容
方法调来调去的看着好麻烦,我简单总结了一下这几个方法之间的关系(我自己看的,所以写的很简略,看不懂的话我建议自己也总结一个类似小流程,真的好用!!!超级便于记忆)
- 当第一次向集合添加元素时:
- 1.add方法把集合中有几个元素进行+1操作后传下去(传下去的实际上就是容纳所有元素所需的最小数组长度,后面我都称为最小容量)注意这里不能是把数组元素个数进行+1返回,因为数组长度可能不等于集合中元素个数
- 2.进行判断,如果最小容量<10则将其改为10并传下去
- 3.把最小容量与数组长度比较,如果前者>后者,就把最小容量传下去进行扩容
- 非第一次向集合添加元素时:
- 1.add方法把集合中有几个元素进行+1操作后传下去
- 2.直接传下去
- 把最小容量与数组长度比较,如果前者>后者,就把最小容量传下去进行扩容
方法名称 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
public class ListDemo03 {
public static void main(String[] args) {
// LinkedList可以完成队列结构,和栈结构 (双链表)
// 1、做一个队列:
LinkedList<String> queue = new LinkedList<>();
// 入队
queue.addLast("1号");
queue.addLast("2号");
System.out.println(queue);
// 出队
System.out.println(queue.removeFirst());
// 2、做一个栈
LinkedList<String> stack = new LinkedList<>();
// 入栈 压栈 (push)
stack.addFirst("第1颗子弹");
stack.push("第2颗子弹");//点进push方法发现其实就是addFirst方法
System.out.println(stack);
// 出栈 弹栈 pop
System.out.println(stack.removeFirst());
System.out.println(stack.pop());//点进pop方法发现其实就是removeFirst方法
}
}
当我们从集合中找出某个元素并删除的时候可能会出现一种并发修改异常问题,比如:
1.解决迭代器遍历集合并删除元素:用迭代器自己的删除方法操作即可解决
public class Test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("情话");
list.add("Java");
list.add("Java");
list.add("赵敏");
//迭代器遍历删除
Iterator<String> it = list.iterator();
while (it.hasNext()){
String ele = it.next();
if("Java".equals(ele)){
// list.remove(ele); //错误示范
it.remove(); //用迭代器自己的删除方法
}
}
System.out.println(list);
}
}
2.for循环遍历集合并删除元素错误示范:
public class Test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("情话");
list.add("Java");
list.add("Java");
list.add("赵敏");
for (int i = 0; i < list.size(); i++) {
String ele = list.get(i);
if("Java".equals(ele)){
list.remove(ele);
}
}
System.out.println(list);
}
}
解决方法一:在if中让i自减
public class Test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("情话");
list.add("Java");
list.add("Java");
list.add("赵敏");
for (int i = 0; i < list.size(); i++) {
String ele = list.get(i);
if("Java".equals(ele)){
list.remove(ele);
i--;//将i进行自减操作
}
}
System.out.println(list);
}
}
解决方法二:遍历集合时倒着遍历
public class Test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("情话");
list.add("Java");
list.add("Java");
list.add("赵敏");
for (int i = list.size() - 1; i >= 0 ; i--) {
String ele = list.get(i);
if("Java".equals(ele)){
list.remove(ele);
}
}
System.out.println(list);
}
}
1.泛型概述:
2.泛型的好处:
统一数据类型
把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来了
List<Object> list1 = new ArrayList();
list1.add("Java");
list1.add(23.3);
list1.add(false);
for (Object o : list1) {
String ele = (String) o;
System.out.println(ele);
}
上述代码会报ClassCastException异常
因为在这个案例中for循环中变量o指向的真实对象是String,Double,Boolean类型,而Double和Boolean类型的对象不能强制转换为String类型
3.泛型可以在很多地方进行定义:
定义类的同时定义了泛型的类就是泛型类
泛型类的格式:修饰符 class 类名<泛型变量> {}
范例:public class MyArrayList {}
此处泛型变量可以随机写为任意标识,常见的如E,T,K,V等
泛型类的作用:编译阶段可以指定数据类型,类似于集合的使用
泛型方法的原理:把出现泛型变量的地方全部替换成传输的真实数据类型
案例实现:模拟ArrayList集合自定义一个集合MyArrayList集合,完成添加和删除功能的泛型设计即可
public class Test {
public static void main(String[] args) {
// 需求:模拟ArrayList定义一个MyArrayList ,关注泛型设计
MyArrayList<String> list = new MyArrayList<>();
list.add("Java");
list.add("MySQL");
System.out.println(list);
}
}
class MyArrayList<E> {
//换壳思想:我不会你这个操作,那我让表面上是我的,实际实现还是用你的操作实现
private ArrayList lists = new ArrayList();
public void add(E e){
lists.add(e);
}
public void remove(E e){
lists.remove(e);
}
//不真实的换壳
// @Override
// public String toString() {
// return "MyArrayList{" +
// "lists=" + lists +
// '}';
// }
//真实的换壳
@Override
public String toString() {
return lists.toString();//注意这里重写的toString方法,只有这样重写才能换壳真实
}
}
案例实现:给你任何一个类型的数组,都能返回它的内容,也就是实现Arrays.toString(数组)的功能
public class GenericDemo {
public static void main(String[] args) {
String[] names = {"小璐", "蓉容", "小何"};
printArray(names);
Integer[] ages = {10, 20, 30};
printArray(ages);
Integer[] ages2 = getArr(ages);//再理解一下泛型方法的核心思想:ages是Integer
//类型的数组,进入getArr方法后把出现泛型变量的地方全部替换成传输的真实数据类型,
//所以方法返回的也是Integer类型的数组,将该数组赋值给ages2时不需要进行强转
String[] names2 = getArr(names);//同上
}
public static <T> T[] getArr(T[] arr){
return arr;
}
public static <T> void printArray(T[] arr){
if(arr != null){
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < arr.length; i++) {
sb.append(arr[i]).append(i == arr.length - 1 ? "" : ", ");
}
sb.append("]");
System.out.println(sb);
}else {
System.out.println(arr);
}
}
}
案例实现:教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作
public interface Data<E> {
void add(E e);
void delete(int id);
E queryById(int id);
}
public class StudentData implements Data<Student>{
@Override
public void add(Student student) {
}
@Override
public void delete(int id) {
}
@Override
public Student queryById(int id) {
return null;
}
}
public class TeacherData implements Data<Teacher>{
@Override
public void add(Teacher teacher) {
}
@Override
public void delete(int id) {
}
@Override
public Teacher queryById(int id) {
return null;
}
}
通配符:?可以在使用的时候代表一切类型,注意和ETKV的区别:后者是在定义泛型的时候使用的
泛型的上下限:
案例实现:开发一个极品飞车的游戏,所有的汽车都能一起参与比赛
public class GenericDemo {
public static void main(String[] args) {
ArrayList<BMW> bmws = new ArrayList<>();
bmws.add(new BMW());
bmws.add(new BMW());
bmws.add(new BMW());
go(bmws);
ArrayList<BENZ> benzs = new ArrayList<>();
benzs.add(new BENZ());
benzs.add(new BENZ());
benzs.add(new BENZ());
go(benzs);
ArrayList<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
dogs.add(new Dog());
// go(dogs);
}
public static void go(ArrayList<? extends Car> cars){
}
}
class Dog{
}
class Car{
}
class BENZ extends Car{
}
class BMW extends Car{
}
现在解释一下ArrayList extends Car> cars怎么来的: