JavaSE阶段学习03:List、Set、数据结构、Collections
泛型概述
泛型方法概述:在定义方法同时定义了泛型变量
泛型方法定义格式:修饰符 <T> 返回值类型 方法名(参数列表){}
泛型方法的注意事项:泛型方法定义的泛型变量的具体数据类型是在调用方法时传参决定。
泛型类概述:在定义类时定义了泛型变量
泛型类定义格式:class 类名<T>{}
泛型类的注意事项
泛型类的泛型变量的具体数据类型是在创建该类对象时由使用者指定,如果没有指定,默认是Object
静态方法不能使用类上定义的泛型变量,如果该静态方法要使用泛型变量,则需要将该方法定义为泛型方法。
public class MyArrays<T>{
public static <T> void reverse(T[] t){
}
}
泛型接口概述:在定义接口的同时定义了泛型变量
泛型接口的定义格式:interface 接口<T>{}
泛型接口的实现方式
实现接口同时指定泛型变量的具体数据类型(不推荐使用)
实现接口时不指定泛型变量的具体数据类型,将实现类定义为泛型类,由使用者创建实现类对象时指定泛型变量的具体数据类型。(推荐使用)
泛型上下限
? 通配符,可以匹配任意类型的数据
? extends Student 泛型上限,可以接受Student或Student的子类类型的数据
? super String 泛型下限, 可以接受String或String的父类类型的数据。
主要内容
- Collection:所有单列集合的父类
- List集合(有序,可重复,有索引)
- ArrayList (实现类,有序,可重复,有索引)
- LinkedList (实现类,有序,可重复,有索引)
- Set集合 (无序,不重复的)
- HashSet (实现类,无序,不重复的)
- LinkedHashSet (实现类,有序,不重复的)
- List集合(有序,可重复,有索引)
- 要明确清楚每一个集合的特点。
- 要清楚每种数据结构的特点
教学目标
- [ ] 能够说出List集合特点
- [ ] 能够说出常见的数据结构
- [ ] 能够说出数组结构特点
- [ ] 能够说出栈结构特点
- [ ] 能够说出队列结构特点
- [ ] 能够说出单向链表结构特点
- [ ] 能够说出Set集合的特点
- [ ] 能够说出哈希表的特点
- [ ] 使用HashSet集合存储自定义元素
- [ ] 能够说出可变参数的格式(形参个数可变的方法)
- [ ] 能够使用集合工具类(对集合元素进行大小排序)
- [ ] 能够使用Comparator比较器对集合排序(自定义比较规则)
第一章 数据结构
1. Java常用的数据结构
栈:先进后出(后进先出) First In Last Out(FILO)
队列:先进先出 First In First Out(FIFO)
数组:
增删慢:每次增删元素时需要创建新的数组,需要拷贝大量的数组元素。
查询快:可以直接根据索引获得对应的元素。
链表
增删快:每次增删元素不需要移动元素位置,只需要修改上一个元素记住下一个元素的地址值。
查询慢:每次查询元素都需要从链表头或链表尾部开始遍历查询。
* 索引决定了是从链表头还是链表尾部开始查询
如果索引大于等于元素个数的一半,从链表尾部开始查询,否则从链表头开始查询。
红黑树(了解) ==> 树 ==> 二叉树
2. 红黑树(了解)
- 二叉树:binary tree ,是每个结点不超过2的有序树(tree) 。
简单的理解,就是一种类似于我们生活中树的结构,只不过每个结点上都最多只能有两个子结点。
二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作**“左子树”和“右子树”。**
如图:
我们要说的是二叉树的一种比较有意思的叫做红黑树,红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。
第二章 List集合
2.1 List接口介绍
List集合的特点
有序(存取顺序一致),有索引,元素可重复,基于数组结构
List集合常用子类
ArrayList
LinkedList
2.2 List接口中常用方法
public void add(int index, E element): 将指定的元素添加到指定的位置
public E get(int index): 根据索引获得指定位置的元素
public E remove(int index): 删除指定位置的元素,返回被删除的元素
boolean remove(Object o): 删除指定的元素,删除成功返回true,否则false
public E set(int index, E element): 将指定位置的元素修改为指定的值element
- 示例代码
public class ListDemo02 {
public static void main(String[] args){
// 创建集合对象
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// 将元素4添加到索引1位置
list.add(1,4);
// 将索引0的元素修改为6
list.set(0,6);
System.out.println(list); // [6,4,2,3]
// 要注意索引越界问题
// list.add(5,6);
// 根据索引获得元素
System.out.println(list.get(2)); // 2
// 删除指定位置的元素
// System.out.println(list.remove(1)); // 4
// 删除元素1
System.out.println(list.remove(Integer.valueOf(5))); // false
System.out.println(list);
}
}
2.3 List接口常用子类
ArrayList
LinkedList
2.3.1 ArrayList集合
/**
ArrayList集合的特点
* 有序(存取顺序一致),有索引,元素可重复
* 底层是数组结构:增删慢,查询快,线程不安全的,效率高
*/
public class ArrayListDemo {
public static void main(String[] args){
// 创建ArrayList集合对象
ArrayList<String> list = new ArrayList<>();
list.add("a") ;
list.add("b") ;
list.add("c") ;
System.out.println(list);
}
}
2.3.2 LinkedList集合
LinkedList集合特点
* 实现了List接口
* 有序(存取顺序一致),有索引,元素可重复
* 底层是双链表结构,查询慢,增删快,线程不安全的,效率高
LinkedList集合特有方法方法
public void addFirst(E e): 将元素添加到链表头
public void addLast(E e): 将元素添加到链表尾部
public E getFirst(): 获得链表头元素
public E getLast(): 获得链表尾部元素
public E removeFirst(): 删除链表头元素
public E removeLast(): 删除链表尾部元素
- 示例代码
public class LinkedListDemo {
public static void main(String[] args){
// 创建LinkedList集合对象
LinkedList<String> list = new LinkedList<>();
list.add("a");
list.add(0,"b");
// 将元素添加到链表头
list.addFirst("c");
// 将元素添加到链表尾部
list.addLast("d");
list.add("e");
// 获得链表头元素
System.out.println(list.getFirst()); // c
// 获得链表尾部元素
System.out.println(list.getLast()); // e
System.out.println(list);
System.out.println(list.get(3)); //
// 删除链表头元素
System.out.println(list.removeFirst()); // c
// 删除链表尾部元素
System.out.println(list.removeLast()); // e
}
}
2.4 ArrayList和LinkedList的选择
如果需要执行大量的增删操作,则选择LinkedList
如果不需要执行增删操作,则选择ArrayList
第三章 Set集合
3.1 Set接口介绍
Set集合的特点
* 无序(存取顺序不一致),无索引,元素不可重复
Set集合常用子类
* HashSet
* LinkedHashSet
3.2 HashSet集合
HashSet集合特点
HashSet集合的特点
* 无序(存取顺序不一致),无索引,元素不可重复
* 底层结构是哈希表
什么是哈希表
* JDK1.8之前,是数组+链表
* JDK1.8之后,是数组+链表+红黑树
HashSet集合基本使用
public class HashSetDemo02 {
public static void main(String[] args){
// 创建Set集合对象
HashSet<String> set = new HashSet<>();
set.add("aa"); // "aa".hashCode() ==> 12345 % 16 = 3
set.add("bb"); // "bb".hashCode() ==> 56654 % 16 = 3
set.add("cc"); // "cc".hashCode() ==> 56776 % 16 = 3
System.out.println(set);
}
}
3.3 对象的哈希值概述
* 对象的哈希值就是一个十进制的整数。
* 哈希值是通过调用Object类的hashCode方法获得,如果子类没有重写该方法,则返回值默认是地址值。
* 哈希值是对象存储到哈希表的重要依据。
- 示例代码
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student() {
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public class HashSetDemo01 {
public static void main(String[] args){
// 定义一个字符串
String str1 = "abc";
String str2 = "abc";
System.out.println(str1.hashCode()); // 96354
System.out.println(str2.hashCode()); // 96354
System.out.println("Aa".hashCode()); // 2112
System.out.println("BB".hashCode()); // 2112
// 创建学生对象
Student stu1 = new Student();
Student stu2 = new Student();
System.out.println(stu1.hashCode()); // 1971489295
System.out.println(stu2.hashCode()); // 985655350
}
}
3.4 HashSet存储结构之哈希表
什么是哈希表呢?是Set集合无序的根本原因!
在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。
看到这张图就有人要问了,这个是怎么存储的呢?
为了方便大家的理解我们结合一个存储流程图来说明一下:
总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。
3.5 HashSet存储自定义对象
需求如下
- 定义一个学生类,包含姓名和年龄两个成员变量。
- 在测试类中创建多个学生对象存储到HashSet集合中
- 要求相同姓名和年龄的学生对象只存储一个。
代码实现如下
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student() {
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
// 重写的hashCode方法计算哈希值是通过计算成员变量的哈希值
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
/**
哈希表的存储自定义对象
* 要求姓名和年龄相同的学生只存储一个
哈希表存储元素底层是依赖于hashCode和equals方法
* 自定义对象要重写hashCode和equals方法
*/
public class HashSetDemo03 {
public static void main(String[] args){
// 创建Set集合对象
HashSet<Student> set = new HashSet<>();
// 添加学生
set.add(new Student("jack",20)); // 34562345 % 16 = 1
set.add(new Student("jack",20)); // 34562345 % 16 = 1
set.add(new Student("rose",23)); // 87678987 % 16 = 1
set.add(new Student("lily",18)); // 3456765 % 16 = 2
for(Student stu:set){
System.out.println(stu);
}
}
}
3.6 LinkedHashSet集合
LinkedHashSet集合的特点
* 继承HashSet,能够保证存取顺序一致。
* 底层结构是哈希表+链表
LinkedHashSet集合基本使用
public class LinkedHashSetDemo01 {
public static void main(String[] args){
// 创建Set集合对象
LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("xfa");
set.add("324y");
set.add("fdsafa");
set.add("hghs");
set.add("hghs");
set.add("hghs");
System.out.println(set);
}
}
3.7 单列集合Collection小结
单列集合小结
* List:有序,有索引,元素可重复
* ArrayList:底层是数组,查询快,增删慢,线程不安全,效率高。
* LinkedList:底层是链表,查询慢,增删快, 线程不安全,效率高。
* Set:无序,无索引,元素不可重复
* HashSet:底层是哈希表(JDK1.8之后:数组+链表+红黑树,JDK1.8之前:数组+链表)
增删和查询都比较快。线程不安全,效率高。
* LinkedHashSet:继承HashSet,底层是哈希表+链表,能够保证存取顺序一致。
如何选择集合
* 判断是否要求要存储重复元素
* 如果要求元素可重复,则在List集合下选择。
* 如果需要执行大量的增删操作,则选择LinkedList
* 如果只需要执行查询操作,则选择ArrayList
* 如果要求不可重复,则在Set集合下选择。
* 如果要求存取顺序一致,则选择LinkedHashSet,否则可以随便选择。
第四章 可变参数
可变参数的概述
* JDK1.5新特性,参数个数可以是任意个
* 格式:数据类型...变量名
* 本质:数组
可变参数的注意事项
* 一个方法参数列表中只能有一个可变参数且只能出现在参数列表的最后一个
- 示例代码
public class Demo01 {
public static void main(String[] args){
System.out.println(sum(1,2));
System.out.println(sum(1,2,3));
System.out.println(sum(1));
System.out.println(sum(1,3,123,13,123,13,1,31,3,13,13,13,1,3));
}
public static int sum(int...arr){
// 定义求变量
int result = 0;
for(int num:arr ){
result += num;
}
return result;
}
/*
求两个数之和
*/
/* public static int sum(int a,int b) {
return a + b;
}
*//*
求三个数之和
*//*
public static int sum(int a,int b,int c) {
return a + b + c;
}*/
}
第五章 Collections工具类
5.1 常用方法
* public static <T> boolean addAll(Collection<T> c, T... elements)
* 将数组中的所有元素添加到指定的集合中。
* public static void shuffle(List<?> list)
* 将集合中的元素乱序。
* public static <T> void sort(List<T> list)
* 将集合中的元素排序,默认是升序排序
5.2 代码演示
public class CollectionsDemo01 {
public static void main(String[] args){
// 创建集合对象
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1,122,13,13,13,13,1,31,31,31,3);
System.out.println(list);
// 将集合元素乱序
Collections.shuffle(list);
System.out.println(list);
// 对集合元素进行排序:默认是升序
Collections.sort(list);
System.out.println(list);
}
}
5.3 自定义比较器
public static <T> void sort(List<T> list,Comparator<? super T> c)
* 使用比较器对集合元素进行排序
- 示例代码
/**
自定义比较器比较自定义对象
方式1:自定义对象需要实现Comparable接口,重写compareTo方法。
方式2:使用自定义比较器排序,重写compare方法
自定义比机器的步骤
* 定义一个类型实现Comparator接口
* 重写接口中的compare方法,在该方法中定义好排序规则
* 创建自定义比较器对象调用Collections工具类sort方法,传递比较器器对象即可。
*/
public class CollectionsDemo04 {
public static void main(String[] args){
// 创建集合对象
ArrayList<Student> list = new ArrayList<>();
list.add(new Student("jack",20));
list.add(new Student("rose",25));
list.add(new Student("lucy",18));
list.add(new Student("lily",30));
for(Student stu:list){
System.out.println(stu);
}
System.out.println("-----------");
// 对象学生排序
// public static <T extends Comparable> void sort(List<T> list)
// 方式1:自定义对象需要实现Comparable接口,重写compareTo方法。
// Collections.sort(list);
// 方式2:使用自定义比较器排序
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
});
for(Student stu:list){
System.out.println(stu);
}
}
}
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student() {
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
/*
compareTo方法返回值的作用
* 正数:o1大于o2
* 0 : 表示o1和o2相等
* 负数:o1小于o2
*/
public int compareTo(Student o) {
// System.out.println(this.name+"="+o.name);
// this.name.compareTo(o.name);
return o.age - this.age;
}
}