集合
- 一个长度可变的容器
学习目标
- 集合容器的创建
- 集合中数据的赠删改查
- 集合的遍历操作
Collection的使用
// 以多态的形式创建集合对象
Collection<String> c = new ArrayList<>();
c.add("张三");
c.add("李四");
c.add("王五");
// 判断集合中是否包含李四
System.out.println(c.contains("李四"));
// 根据元素做删除
boolean result = c.remove("李四");
System.out.println(result);
System.out.println(c.contains("李四"));
System.out.println(c);
System.out.println(c.isEmpty());
System.out.println(c.size());
// 清空集合所有元素
c.clear();
System.out.println(c);
System.out.println(c.isEmpty());
System.out.println(c.size());
注意事项
remove(), contains()
底层依赖对象的equals
方法
集合的通用遍历方式
迭代器
/*
集合通用遍历方式 - 迭代器遍历
*/
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("张三");
c.add("李四");
c.add("王五");
// 1. 获取迭代器
Iterator<String> it = c.iterator(); // new Itr();
// 2. 循环判断, 是否有元素可以取出
while (it.hasNext()) {
// 3. 使用迭代器获取元素
String s = it.next();
System.out.println(s);
}
}
迭代器源码解析
hashNext()
: 判断集合中是否还有元素
next()
: 去除集合元素, 并将指针向后移动
注意:
- 在循环过程中
next
方法最好只调用一次
增强for
循环
- 简化迭代器的代码书写
- 它是
JDK5
之后出现的,其内部原理就是一个lterator
迭代器 - 增强
for
循环是迭代器的语法糖
格式
for (元素的数据类型 变量名 : 数组或者集合) {
}
for (String s : list) {
System.out.println(s);
}
int[] arr = {11, 22, 33};
for (int num : arr) {
System.out.println(num);
}
forEach
方法
c.forEach(new Consumer<Student>() {
@Override
public void accept(Student stu) {
System.out.println(stu);
}
});
c.forEach(stu -> System.out.println(stu));
List
接口
List
因为支持索引, 所以多了很多索引操作的独特API
List
的独特API
private static void method() {
// 以多态形式创建对象
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
System.out.println(list);
list.remove(1);
System.out.println(list);
list.set(0, "张三丰");
System.out.println(list);
}
注意
List
存入Integer
, 可以用索引删除, 也可以用元素删除, 但用元素删除时需要用Integer
的手动装箱
private static void test() {
List<Integer> list = new ArrayList<>();
list.add(100);
list.add(200);
list.add(300);
list.add(400);
list.remove(Integer.valueOf(100));
System.out.println(list);
}
List
集合的遍历方式:
- 迭代器
- 增强
for
forEach
方法- 普通
for
循环, 因为有索引 - 使用
List
集合特有迭代器,ListIterator
/*
List集合的遍历方式:
1. 迭代器
2. 增强for
3. foreach方法
4. 普通for循环, 因为有索引
5. 使用List集合特有迭代器, ListIterator
*/
public static void main(String[] args) {
List<Student> list = new ArrayList<>();
list.add(new Student("张三", 23));
list.add(new Student("李四", 24));
list.add(new Student("王五", 25));
// 1. 迭代器
Iterator<Student> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
System.out.println("---------------------");
// 2. 增强for
for (Student student : list) {
System.out.println(student);
}
System.out.println("---------------------");
// 3. foreach方法
list.forEach(s -> System.out.println(s));
System.out.println("---------------------");
// 4. 普通for
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("---------------------");
// 5. 使用List集合特有迭代器, ListIterator
ListIterator<Student> listIt = list.listIterator();
while (listIt.hasNext()) {
System.out.println(listIt.next());
}
System.out.println("----------------------");
// 判断是否有前一个元素
while (listIt.hasPrevious()) {
// 取出前一个元素
Student stu = listIt.previous();
System.out.println(stu);
}
}
并发修改异常
ConcurrentModificationException
public class ConcurrentModificationException extends RuntimeException
- 当不允许这样修改时,检测到对象的并发修改的方法可能抛出此异常
- 使用迭代器通历集合的过程中,调用了集合对象的添加,删除方法,就会出现此异常
- 删除倒数第二个元素时不会报错
解决方案
- 使用迭代器对象的添加\删除方法
- 迭代过程中做删除:使用
iterator
自带的remove
方法 - 迭代过程中做添加:使用
listIterator
自带的add
方法
- 迭代过程中做删除:使用
数据结构介绍
- 数据结构是计算机底层存储,组织数据的方式,是指数据相互之间是以什么方式排列在一起的
- 通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率
常见的数据结构
- 栈
- 队列
- 数组
- 链表
- 二叉树
- 二叉查找树
- 平衡二叉树
- 红黑树
- 哈希表
数据结构 (数组)
- 查询速度快: 查询数据通过地址值和索引定位,查询任意数据耗时相同
- 增,删效率低: 新增或删除数据的时候,都有可能大批量的移动数组中其他的元素
数据结构(链表)
- 链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址。
- 链表查询慢,无论查询哪个数据都要从头开始找。
- 链表增删相对快 (相对数组而言)
各种数据结构的特点和作用是什么样的
- **栈:**后进先出,先进后出。
- **队列:**先进先出,后进后出。
- **数组:**内存连续区域,查询快,增删慢。
- **链表:**元素是游离的,查询慢,首尾操作极快。
ArrayList
类
ArrayList
底层是基于数组实现的,根据查询元素快,增删相对慢
public class ArrayListDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("张三");
}
}
ArrayList
长度可变原理
ArrayList源码.png
ArrayList
源码解析
- 使用空参构造器创建的集合,在底层创建一个默认长度为0的数组
- 添加第一个元素时,底层会创建一个新的长度为10的数组
- 存满时,会扩容1.5倍
LinkedList
类
LinkedList
底层基于双链表实现的,查询元素慢,增删首尾元素是非常快的
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("a");
list.add("b");
list.add("c");
System.out.println(list.get(1));
}
private static void method(LinkedList<String> list) {
list.addFirst("a");
list.addFirst("b");
list.addFirst("c");
list.addFirst("d");
list.addLast("e");
System.out.println(list);
System.out.println(list.getFirst());
System.out.println(list.getLast());
list.removeFirst();
list.removeLast();
System.out.println(list);
}
}
一个问题
LinkedList
也有get
方法,表面看起来是根据索引获取元素,实际上是?
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
输入的index小于size的1/2时, LinkedList从头开始遍历查找, 大于size的1/2时, LinkedList从尾开始遍历查找
泛型
JDK5
引入的,可以在编译阶段约束操作的数据类型,并进行检查
泛型的好处
- 统一数据类型
- 将运行期的错误提升到了编译期
**细节: **如果没有指定泛型, E 默认就是Object
/*
泛型的好处:
1. 统一数据类型
2. 将运行期的错误提升到了编译期.
细节: 如果没有指定泛型, E 默认就是Object
*/
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("张三");
list.add("李四");
list.add("王五");
list.add(new Random());
Iterator it = list.iterator();
while(it.hasNext()){
Object o = it.next();
String s = (String) o;
System.out.println(s.length());
}
}
注意事项
- 泛型中只能便携引用数据类型
泛型的常见标识符
- E : element 元素
- T : Type 类型
- K : Key 键
- V : value 值
泛型类
- 在创建对象的时候, 确定到具体的数据类型.
public class Demo2 {
/*
泛型的常见标识符 :
E : element 元素
T : Type 类型
K : Key 键
V : value 值
泛型类: 在创建对象的时候, 确定到具体的数据类型.
*/
public static void main(String[] args) {
Student<Integer> stu = new Student<>();
}
}
class Student<E> {
private E e;
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
}
泛型方法
- 非静态: 方法中的泛型, 跟着类的泛型去匹配.
-
静态: 调用方法, 传入实际参数的时候
- 注意: 静态方法中的泛型, 必须声明出自己独立的泛型
/*
泛型方法:
1. 非静态: 方法中的泛型, 跟着类的泛型去匹配.
2. 静态: 调用方法, 传入实际参数的时候
- 注意: 静态方法中的泛型, 必须声明出自己独立的泛型
*/
public static void main(String[] args) {
Integer[] arr1 = {11, 22, 33, 44, 55};
Double[] arr2 = {11.1, 22.2, 33.3};
String[] arr3 = {"张三", "李四", "王五"};
printArray(arr1);
printArray(arr2);
printArray(arr3);
}
public static <T> void printArray(T[] arr) {
System.out.print("[");
for (int i = 0; i < arr.length - 1; i++) {
System.out.print(arr[i] + ", ");
}
System.out.println(arr[arr.length - 1] + "]");
}
泛型接口
- 类实现接口的时候,如果接口带有泛型,有两种操作方式
1. 类实现接口的时候,直接确定类型
2. 延续接口的泛型,等创建对象的时候再确定
public static void main(String[] args) {
}
}
interface Inter<E> {
void add(E e);
}
class InterAImpl implements Inter<String> {
@Override
public void add(String s) {
}
}
class InterBImpl<E> implements Inter<E> {
@Override
public void add(E e) {
}
}
泛型通配符 (泛型限定)
?
: 任意类型? extends E
: 可以传入的是E, 或者是E的子类? super E
: 可以传入的是E, 或者是E的父类
public class Demo5 {
/*
泛型通配符
? : 任意类型
? extends E : 可以传入的是E, 或者是E的子类
? super E : 可以传入的是E, 或者是E的父类
*/
public static void main(String[] args) {
ArrayList<Coder> list1 = new ArrayList<>();
list1.add(new Coder());
ArrayList<Manager> list2 = new ArrayList<>();
list2.add(new Manager());
method(list1);
method(list2);
}
public static void method(ArrayList<? extends Employee> list) {
for (Employee employee : list) {
employee.work();
}
}
}
abstract class Employee {
private String name;
private double salary;
public Employee() {
}
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public abstract void work();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String toString() {
return "Employee{name = " + name + ", salary = " + salary + "}";
}
}
class Coder extends Employee {
@Override
public void work() {
System.out.println("程序员写代码...");
}
}
class Manager extends Employee {
@Override
public void work() {
System.out.println("项目经理分配任务...");
}
}