集合进阶
集合体系结构
Collection(单列集合,为接口)
- List(接口)
- ArrayList(实现类)
- LinkedList(实现类)
- Vector(实现类)
- Set(接口)
- HashSet(实现类):无序、不重复、无索引
- LinkedHashSet(实现类):有序、不重复、无索引
- TreeSet(实现类):可排序、不重复、无索引
- HashSet(实现类):无序、不重复、无索引
- List系列集合:添加的元素是有序(存取)、有索引、可重复的。
- Set系列集合:添加的元素是无序(存取有可能不一样)、无索引、不可重复的,Set接口中的方法基本上与Collection的API一致。
Map(双列集合)
Collection集合
-
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 class CollectionDemo01 { public static void main(String[] args) { /* public boolean add(E e) 把给定的对象添加到当前集合中 public void clear() 清空集合中所有的元素 public boolean remove(E e) 把给定的对象在当前集合中删除 public boolean contains(Object obj) 判断当前集合中是否包含给定的对象 public boolean isEmpty() 判断当前集合是否为空 public int size() 返回集合中元素的个数/集合的长度 注意点: Collection是一个接口,我们不能直接创建它的对象 所以,现在我们学习它的方法时,只能创建它实现类的对象 实现类:ArrayList */ //目的:为了学习Collection接口里的方法 //自己在做一些练习的时候,还是按照之前的方法去创建对象 Collection<String> coll = new ArrayList<>(); //1.添加元素 //细节1:如果我们要往List系列集合中添加数据,那么永远返回true,因为List系列是允许元素重复的 //细节2:如果我们要往Set系列集合中添加数据,如果当前要添加的元素不存在,方法返回true,表示添加成功 // 如果当前要添加的元素存在,方法返回false,表示添加失败 // 因为Set系列的集合不允许重复。 coll.add("aaa"); coll.add("bbb"); coll.add("ccc"); System.out.println(coll); //2.清空 /*coll.clear(); System.out.println(coll);*/ //3.删除 //细节1:因为Colection里面定义的是共性的方法,所以此时不能通过索引进行删除,只能通过元素的对象进行删除 //细节2:方法会有一个布尔类型的返回值,删除成功返回true,删除失败返回false //如果要删除的元素不存在,就会删除失败 coll.remove("aaa"); System.out.println(coll); //4.contains判断元素是否被包含 //细节:底层是依赖equals方法进行判断是否存在的 //所以,如果集合中存储的是自定义对象,也想通过contains方法来判断是否包含,那么在javabean类中,一定要重写equals方法 boolean result = coll.contains("aaa"); System.out.println(result); //5.判断集合是否为空 boolean result2 = coll.isEmpty(); System.out.println(result2);//false //6.获取集合长度 int size = coll.size(); System.out.println(size);//2 } }
public class CollectionDemo02 { public static void main(String[] args) { //1.创建集合的对象 Collection<Student> coll = new ArrayList<>(); //2.创建三个学生对象 Student s1 = new Student("zhangsan", 23); Student s2 = new Student("lisi", 24); Student s3 = new Student("wangwu", 25); //3.把学生对象添加到集合中 coll.add(s1); coll.add(s2); coll.add(s3); //4.判断集合中某一个学生对象是否包含 Student s4 = new Student("zhangsan", 23); //因为contains方法在底层依赖equals方法判断对象是否一致 //如果存的是自定义对象,没有重写equals方法,那么默认使用Object类中的equals方法进行判断,而Object类中的equals方法,依赖地址值进行判断 //若果姓名和年龄都相同,就认为是同一个学生 //所以,需要在自定义的Javabean类中,重写equals方法 System.out.println(coll.contains(s4)); } }
public class Student { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } /** * 获取 * @return name */ public String getName() { return name; } /** * 设置 * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * @return age */ public int getAge() { return age; } /** * 设置 * @param age */ public void setAge(int age) { this.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); } public String toString() { return "Student{name = " + name + ", age = " + age + "}"; } }
-
Collection的遍历方式
-
迭代器遍历
-
迭代器不依赖索引
-
迭代器在Java中的类是Iterator,迭代器是集合专用的遍历方式。
-
Collection集合获取迭代器
方法名称 说明 Iterator iterator 返回迭代器对象,默认指向当前集合的0索引 -
Iterator中的常用方法
方法名称 说明 boolean hasNext() 判断当前位置是否有元素,有元素返回true,没有元素返回false E next() 获取当前位置的元素,并将迭代器对象移向下一个位置 public class CollectionDemo03 { public static void main(String[] args) { /* Collection系列集合三种通用的遍历方式 1.迭代器遍历 2.增强for循环 3.lambda表达式遍历 迭代器遍历相关的三个方法: Iterator<E> iterator(): 获取一个迭代器对象 boolean hasNext(): 判断当前指向的位置是否元素 E next(): 获取当前指向的元素并移动指针 */ //1.创建集合并添加元素 Collection<String> coll = new ArrayList<>(); coll.add("aaa"); coll.add("bbb"); coll.add("ccc"); coll.add("ddd"); //2.获取迭代器对象 //迭代器就好比是一个箭头,默认指向集合的0索引处 Iterator<String> it = coll.iterator(); //3,利用循环不断地去获取集合中的每一个元素 while(it.hasNext()) { //4.next方法的两件事情:获取元素并移动指针 String str = it.next(); System.out.println(str); } } }
-
细节注意点:
(1)报错NoSuchElementException
(2)迭代器遍历完毕,指针不会复位
(3)循环中只能用一次next方法
(4)迭代器遍历时,不能用集合的方法进行增加或者删除
public class CollectionDemo04 { public static void main(String[] args) { /* 迭代器的细节注意点: 1.报错NoSuchElementException 2.迭代器遍历完毕,指针不会复位 3.循环中只能用一次next方法 4.迭代器遍历时,不能用集合的方法进行增加或者删除 */ //1.创建集合并添加元素 Collection<String> coll = new ArrayList<>(); coll.add("aaa"); coll.add("bbb"); coll.add("ccc"); coll.add("ddd"); //2.获取迭代器对象 //迭代器就好比是一个箭头,默认指向集合的0索引处 Iterator<String> it = coll.iterator(); //3,利用循环不断地去获取集合中的每一个元素 while(it.hasNext()) { //4.next方法的两件事情:获取元素并移动指针 String str = it.next(); System.out.println(str); } //当上面的循环结束之后,迭代器的指针已经指向了最后没有这个元素的位置 //System.out.println(it.next());//NoSuchElementException //迭代器遍历完毕,指针不会复位 System.out.println(it.hasNext());//false //如果要第二次继续遍历集合,只能再次获取一个新的迭代器对象 Iterator<String> it2 = coll.iterator(); while (it2.hasNext()) { String str = it2.next(); System.out.println(str); } } }
public class CollectionDemo05 { public static void main(String[] args) { /* 迭代器的细节注意点: 1.报错NoSuchElementException 2.迭代器遍历完毕,指针不会复位 3.循环中只能用一次next方法 4.迭代器遍历时,不能用集合的方法进行增加或者删除 如果实在要删除,那么可以用迭代器提供的remove方法进行删除 如果要添加,暂时没有办法 */ //1.创建集合并添加元素 Collection<String> coll = new ArrayList<>(); coll.add("aaa"); coll.add("bbb"); coll.add("ccc"); coll.add("ddd"); coll.add("eee"); //2.获取迭代器对象 //迭代器就好比是一个箭头,默认指向集合的0索引处 Iterator<String> it = coll.iterator(); //3,利用循环不断地去获取集合中的每一个元素 while(it.hasNext()) { //4.next方法的两件事情:获取元素并移动指针 String str = it.next(); if("bbb".equals(str)) { //coll.remove("bbb"); it.remove(); } } //System.out.println(coll);//ConcurrentModificationException System.out.println(coll);//[aaa, ccc, ddd, eee] } }
-
-
增强for遍历
-
增强for的底层就是迭代器,为了简化迭代器的代码书写的
-
它是JDK5之后出现的,其内部原理就是一个Iterator迭代器
-
所有的单列集合和数组才能用增强for进行遍历
-
格式
for(元素的数据类型 变量名:数组或集合) { // }
例如:
for(String s : list) { System.out.println(s); }
public class CollectionDemo06 { public static void main(String[] args) { /* Collection系列集合三种通用的遍历方式 1.迭代器遍历 2.增强for循环 3.lambda表达式遍历 增强for格式: for(数据类型 变量名 : 集合/数组) { // } 快速生成方式: 集合的名字+for回车 */ //1.创建集合并添加元素 Collection<String> coll = new ArrayList<>(); coll.add("zhangsan"); coll.add("lisi"); coll.add("wangwu"); //2.利用增强for循环进行遍历 //注意点: //s其实就是一个第三方变量,在循环的过程中依次表示集合中的每一个数据 for(String s : coll) { System.out.println(s); } } }
-
增强for的细节:
(1)修改增强for中的变量,不会改变按集合中原本的数据
for(String s : list) { s = "q"; }
-
-
Lambda表达式遍历
得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。
-
方法名称 说明 default void forEach(Consumer<? super T> action): 结合lambda遍历集合 public class CollectionDemo07 { public static void main(String[] args) { /* Collection系列集合三种通用的遍历方式 1.迭代器遍历 2.增强for循环 3.lambda表达式遍历 lambda表达式遍历 default void forEach(Consumer<? super T> action): */ //1.创建集合并添加元素 Collection<String> coll = new ArrayList<>(); coll.add("zhangsan"); coll.add("lisi"); coll.add("wangwu"); //2.利用匿名内部类的形式 //底层原理: //其实也会自己遍历集合,依次得到每一个元素 //把得到的每一个元素,传递给下面的accept方法 //s依次表示集合中的每一个数据 /*coll.forEach(new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } });*/ //lambda表达式 //()->{} coll.forEach(s -> System.out.println(s)); } }
-
-
List集合
-
Lits集合的特有方法
-
Collection的方法List都继承了
-
List集合因为有索引,所以多了很多索引操作的方法
-
常用方法
方法名称 说明 void add(int index,E element) 在此集合中的指定位置插入指定的元素 E remove(int index) 删除指定索引处的元素,返回被删除的元素 E set(int index,E element) 修改指定索引处的元素,返回被修改的元素 E get(int index) 返回指定索引处的元素 public class ListDemo01 { public static void main(String[] args) { /* List系列集合独有的方法: void add(int index,E element) 在此集合中的指定位置插入指定的元素 E remove(int index) 删除指定索引处的元素,返回被删除的元素 E set(int index,E element) 修改指定索引处的元素,返回被修改的元素 E get(int index) 返回指定索引处的元素 */ //1.创建一个集合 List<String> list = new ArrayList<>(); //2.添加元素 list.add("aaa"); list.add("bbb"); list.add("ccc"); //void add(int index,E element) 在此集合中的指定位置插入指定的元素 //细节:原来索引的元素会依次往后移 //list.add(1,"QQQ"); //E remove(int index) 删除指定索引处的元素,返回被删除的元素 //String remove = list.remove(0); //System.out.println(remove); //E set(int index,E element) 修改指定索引处的元素,返回被修改的元素 //String result = list.set(0, "QQQ"); //System.out.println(result); //E get(int index) 返回指定索引处的元素 String s = list.get(0); System.out.println(s); //3.打印集合 System.out.println(list); } }
public class ListDemo02 { public static void main(String[] args) { //List系列集合中的两个删除的方法 //1.直接删除元素 //2.通过索引进行删除 //1.创建集合并添加元素 List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); //2.删除元素 //请问:此时删除的是1这个元素,还是1索引上的元素?(index) //因为在调用方法的时候,如果方法出现了重载现象 //优先调用,实参跟形参类型一致的那个方法 //list.remove(1); //手动装箱,手动把基本数据类型的1,变成Integer类型 Integer i = Integer.valueOf(1); list.remove(i); System.out.println(list); } }
-
-
List集合的遍历方式
- 迭代器遍历
- 列表迭代器遍历(独有)
- 增强for遍历
- Lambda表达式遍历
- 普通for循环(因为Lits集合存在索引)
public class ListDemo03 { public static void main(String[] args) { /* List系列集合的五种遍历方式: 1.迭代器遍历 2.列表迭代器遍历(独有) 3.增强for遍历 4.Lambda表达式遍历 5.普通for循环(因为Lits集合存在索引) */ //创建集合并添加元素 List<String> list = new ArrayList<>(); list.add("aaa"); list.add("bbb"); list.add("ccc"); //1.迭代器 /*Iterator<String> it = list.iterator(); while(it.hasNext()) { String str = it.next(); System.out.println(str); }*/ //2.增强for //下面的变量s,其实就是一个第三方的变量而已 //在循环的过程中,依次表示集合中的每一个元素 /*for (String s : list) { System.out.println(s); }*/ //3.lambda表达式 //forEach方法的底层其实就是一个循环遍历,依次得到集合中的每一个元素 //并把每一个元素传递给下面的accept方法 //accept方法的形参s,依次表示集合中的每一个元素 //list.forEach(s -> System.out.println(s)); //4.普通for循环 //size方法跟get方法还有循环结合的方式,利用索引获取到集合中的每一个元素 /*for (int i = 0; i < list.size(); i++) { //i:依次表示集合中的每一个索引 String s = list.get(i); System.out.println(s); }*/ //5.列表迭代器 //获取一个列表迭代器的对象,里面的指针默认也是指向0索引的 //额外添加了一个方法:在遍历的过程中,可以添加元素 ListIterator<String> it = list.listIterator(); while(it.hasNext()) { String str = it.next(); if("bbb".equals(str)) { //qqq it.add("qqq"); } } System.out.println(list); } }
-
五种遍历方式对比
- 迭代器遍历:在遍历的过程中需要删除元素
- 列表迭代器:在遍历的过程中需要添加元素
- 增强for遍历+Lambda表达式:仅仅想遍历
- 普通for:如果遍历的时候想操作索引,可以用普通for
数据结构
-
什么是数据结构呢?
答:
计算机底层存储、组织数据的方式,是指数据相互之间以什么方式排列在一起的。
数据结构是为了更加方便的管理和使用数据,需要结合具体的业务场景来进行选择。
一般情况下,精心选择的数据结构可以带来更高的运行或者存储效率。
(1,每种数据结构长什么样子?2,如何添加数据?3,如何删除数据?)
-
常见的数据结构
- 栈
- 队列
- 数组
- 链表
- 二叉树
- 二叉查找树
- 平衡二叉树
- 红黑树
ArrayList集合
ArrayList集合底层原理
- 利用空参创建的集合,在底层创建一个默认长度为0的数组
- 添加第一个元素时,底层会创建一个新的长度为10的数组
- 存满时,会扩容1.5倍
- 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
LinkedList集合
-
底层数据结构是双向链表,查询慢,增删快,但是如果操作的是首尾元素,速度也是极快的。
-
LinkedList本身多了很多直接操作首尾元素的特有API。
-
特有方法
特有方法 说明 public void addFirst(E e) 在该列表开头插入指定的元素 public void addLast(E e) 将指定的元素追加到此列表的末尾 public E getFirst() 返回此列表中的第一个元素 public E getLast() 返回此列表中的最后一个元素 public E removeFirst() 从此列表中删除并返回第一个元素 public E removeLast() 从此列表中删除并返回最后一个元素
泛型深入
-
泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
-
泛型的格式:<数据类型>
-
注意:泛型只能支持引用数据类型。
-
泛型的好处:
- 统一数据类型
- 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来。
-
扩展知识点:Java中的泛型是伪泛型
-
泛型的细节:
- 泛型中不能写基本数据类型
- 指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型
- 如果不写泛型,类型默认是Object
public class GenericsDemo01 { public static void main(String[] args) { //没有泛型的时候,集合如何存储数据 //结论: //如果没有给集合指定类型,默认所有的数据类型都是Object类型 //此时可以往集合添加任意的数据类型 //带来一个坏处:在获取数据的时候,无法使用它的特有行为 //此时推出了泛型,可以在添加数据的时候就把类型进行统一 //而且在获取数据的时候,也省得强转了,非常方便 //1.创建集合的对象 ArrayList list = new ArrayList(); //2.添加数据 list.add(123); list.add("abc"); list.add(new Student("zhangsan",123)); //3.遍历集合获取里面的每一个数据 Iterator it = list.iterator(); while(it.hasNext()) { Object obj = it.next(); //多态的弊端是不能访问子类的特有功能 //obj.length(); System.out.println(obj); } } }
-
泛型可以在很多地方进行定义
(写在)类后面——泛型类
(写在)方法上面——泛型方法
(写在)接口后面——泛型接口
-
泛型类
使用场景:当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类
//格式: 修饰符 class 类名<类型> { // } //举例: public class ArrayList<E> { //创建该类对象时,E就确定类型 } //此处E可以理解为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成:T、E、K、V等
/** * 当编写一个类时,如果不确定类型,那么这个类就可以定义为泛型类 */ public class MyArrayList<E> { Object[] obj = new Object[10]; int size; /** * E:表示是不确定的类型,该类型在类名后面已经定义过了 * e:形参的名字,变量名 */ public boolean add(E e) { obj[size] = e; size++; return true; } public E get(int index) { return (E)obj[index]; } @Override public String toString() { return Arrays.toString(obj); } }
public class GenericsDemo02 { public static void main(String[] args) { /*MyArrayList<String> list = new MyArrayList<>(); list.add("aaa"); list.add("bbb"); list.add("ccc"); System.out.println(list);*/ MyArrayList<Integer> list = new MyArrayList<>(); list.add(123); list.add(456); list.add(789); int i = list.get(0); System.out.println(i); System.out.println(list); } }
-
泛型方法
方法中形参类型不确定时,可以使用类名后面定义的泛型
public class MyArrayList { public <E> boolean add(E e) { obj[size] = e; size++; return true; } }
//格式 修饰符 <类型> 返回值类型 方法名(类型 变量名) { // } //举例 public <T> void show(T t) { //此处T可以理解为变量,但是不是用来记录数据的,而是记录类型的,可以写成:T、E、K、V等 }
泛型方法的练习
定义一个工具类:ListUtil
类中定义一个静态方法addAll,用来添加多个集合的元素。
public class ListUtil { private ListUtil() {} //类中定义一个静态方法addAll,用来添加多个集合的元素 /** * 参数一:集合 * 参数二~最后:要添加的元素 */ public static<E> void addAll(ArrayList<E> list,E e1,E e2,E e3,E e4) { list.add(e1); list.add(e2); list.add(e3); list.add(e4); } public static<E> void addAll(ArrayList<E> list,E...e) { for (E e1 : e) { list.add(e1); } } public void show() { System.out.println("谢谢你"); } }
/** * 定义一个工具类:ListUtil * 类中定义一个静态方法addAll,用来添加多个集合的元素。 */ public class GenericsDemo03 { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); ListUtil.addAll(list,"aaa","bbb","ccc","ddd"); System.out.println(list); ArrayList<Integer> list2 = new ArrayList<>(); ListUtil.addAll(list2,1,2); System.out.println(list2); } }
-
泛型接口
//格式 修饰符 interface 接口名<类型> { // } //举例: public interface List<E> { // }
重点:如何使用一个带泛型的接口
方式1:实现类给出具体类型
方式2:实现类延续泛型,创建对象时再确定
public class GenericsDemo04 { public static void main(String[] args) { /* 泛型接口的两种使用方式: 方式1:实现类给出具体类型 方式2:实现类延续泛型,创建实现类对象时再确定 */ //方式1:实现类给出具体类型 /*MyArrayList2 list = new MyArrayList2(); list.add("Hello");*/ MyArrayList3<String> list = new MyArrayList3<>(); list.add("World"); } }
public class MyArrayList2 implements List<String> {//实现类给出具体类型 @Override public int size() { return 0; } @Override public boolean isEmpty() { return false; } @Override public boolean contains(Object o) { return false; } @Override public Iterator<String> iterator() { return null; } @Override public Object[] toArray() { return new Object[0]; } @Override public <T> T[] toArray(T[] a) { return null; } @Override public boolean add(String s) { return false; } @Override public boolean remove(Object o) { return false; } @Override public boolean containsAll(Collection<?> c) { return false; } @Override public boolean addAll(Collection<? extends String> c) { return false; } @Override public boolean addAll(int index, Collection<? extends String> c) { return false; } @Override public boolean removeAll(Collection<?> c) { return false; } @Override public boolean retainAll(Collection<?> c) { return false; } @Override public void clear() { } @Override public String get(int index) { return null; } @Override public String set(int index, String element) { return null; } @Override public void add(int index, String element) { } @Override public String remove(int index) { return null; } @Override public int indexOf(Object o) { return 0; } @Override public int lastIndexOf(Object o) { return 0; } @Override public ListIterator<String> listIterator() { return null; } @Override public ListIterator<String> listIterator(int index) { return null; } @Override public List<String> subList(int fromIndex, int toIndex) { return null; } }
public class MyArrayList3<E> implements List<E> { @Override public int size() { return 0; } @Override public boolean isEmpty() { return false; } @Override public boolean contains(Object o) { return false; } @Override public Iterator<E> iterator() { return null; } @Override public Object[] toArray() { return new Object[0]; } @Override public <T> T[] toArray(T[] a) { return null; } @Override public boolean add(E e) { return false; } @Override public boolean remove(Object o) { return false; } @Override public boolean containsAll(Collection<?> c) { return false; } @Override public boolean addAll(Collection<? extends E> c) { return false; } @Override public boolean addAll(int index, Collection<? extends E> c) { return false; } @Override public boolean removeAll(Collection<?> c) { return false; } @Override public boolean retainAll(Collection<?> c) { return false; } @Override public void clear() { } @Override public E get(int index) { return null; } @Override public E set(int index, E element) { return null; } @Override public void add(int index, E element) { } @Override public E remove(int index) { return null; } @Override public int indexOf(Object o) { return 0; } @Override public int lastIndexOf(Object o) { return 0; } @Override public ListIterator<E> listIterator() { return null; } @Override public ListIterator<E> listIterator(int index) { return null; } @Override public List<E> subList(int fromIndex, int toIndex) { return null; }//实现类延续泛型,创建实现类对象时再确定 }
-
泛型的继承和通配符
- 泛型不具备继承性,但是数据具备继承性
public class GenericsDemo05 { public static void main(String[] args) { /* 泛型不具备继承性,但是数据具备继承性 */ //创建集合对象 ArrayList<Ye> list1 = new ArrayList<>(); ArrayList<Fu> list2 = new ArrayList<>(); ArrayList<Zi> list3 = new ArrayList<>(); //调用method方法 method(list1); //method(list2);//报错 //method(list3);//报错 //数据具备继承性 list1.add(new Ye()); list1.add(new Fu()); list1.add(new Zi()); } /** * 此时,泛型里面写的是什么类型,那么只能传递什么类型的数据 * @param list */ public static void method(ArrayList<Ye> list) { } } class Ye{} class Fu extends Ye{} class Zi extends Fu{}
public class GenericsDemo06 { public static void main(String[] args) { /** * 需求: * 定义一个方法,形参是一个集合,但是集合中的数据类型不确定 */ //创建集合对象 ArrayList<Ye> list1 = new ArrayList<>(); ArrayList<Fu> list2 = new ArrayList<>(); ArrayList<Zi> list3 = new ArrayList<>(); //调用method方法 method(list1); method(list2); method(list3); } /** * 此时,泛型里面写的是什么类型,那么只能传递什么类型的数据 * 弊端: * 利用泛型方法有一个小弊端,此时它可以接受任意的数据类型。 * Ye Fu Zi Student * * 希望:本方法虽然不确定类型,但是以后希望只能传递Ye Fu Zi * * 此时我们就可以使用泛型的通配符: * ?也表示不确定的类型 * 它可以进行类型的限定 * ? extends E:表示可以传递E或者E所有的子类类型 * ? super E:表示可以传递E或者E所有的父类类型 * * 应用场景: * 1.如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口。 * 2.如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以使用泛型的通配符 * 泛型的通配符: * 关键点:可以限定类型的范围 */ public static<E> void method(ArrayList<? super Zi> list) { } }
-
练习
public abstract class Animal { private String name; private int age; public abstract void eat(); public Animal() { } public Animal(String name, int age) { this.name = name; this.age = age; } /** * 获取 * @return name */ public String getName() { return name; } /** * 设置 * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * @return age */ public int getAge() { return age; } /** * 设置 * @param age */ public void setAge(int age) { this.age = age; } public String toString() { return "Animal{name = " + name + ", age = " + age + "}"; } }
public abstract class Cat extends Animal{ public Cat(String name, int age) { super(name, age); } //1.继承抽象类,重写里面所有的抽象类 //2.本身Cat也是一个抽象的,让Cat的子类再重写方法 //此时采取第二种处理方案 //因为猫的两个子类eat的方法体还是不一样的 }
public abstract class Dog extends Animal{ public Dog(String name, int age) { super(name, age); } }
//波斯猫 public class PersianCat extends Cat{ public PersianCat(String name, int age) { super(name, age); } @Override public void eat() { System.out.println("一只叫作" + this.getName() + "的," + this.getAge() + "岁的波斯猫,正在吃小饼干"); } }
//狸花猫 public class DragonLi extends Cat{ public DragonLi(String name, int age) { super(name, age); } @Override public void eat() { System.out.println("一只叫作" + this.getName() + "的," + this.getAge() + "岁的狸花猫,正在吃小鱼干"); } }
//泰迪 public class Teddy extends Dog{ public Teddy(String name, int age) { super(name, age); } @Override public void eat() { System.out.println("一只叫作" + this.getName() + "的," + this.getAge() + "岁的泰迪,正在吃骨头,边吃边蹭"); } }
//哈士奇 public class SiberianHusky extends Dog{ public SiberianHusky(String name, int age) { super(name, age); } @Override public void eat() { System.out.println("一只叫作" + this.getName() + "的," + this.getAge() + "岁的哈士奇,正在吃骨头,边吃边拆家"); } }
public class GenericsDemoTest { public static void main(String[] args) { /* 需求: 定义一个继承结构: 动物 | | 猫 狗 | | | | 波斯猫 狸花猫 泰迪 哈士奇 属性:名字、年龄 行为:吃东西 方法体打印:一只叫作xxx的,x岁的波斯猫,正在吃小饼干 方法体打印:一只叫作xxx的,x岁的狸花猫,正在吃小鱼干 方法体打印:一只叫作xxx的,x岁的泰迪,正在吃骨头,边吃边蹭 方法体打印:一只叫作xxx的,x岁的哈士奇,正在吃骨头,边吃边拆家 测试类中定义一个方法用于饲养动物 public static void keepPet(ArrayList<???> list) { //遍历集合,调用动物的eat方法 } 需求1:该方法能养所有品种的猫,但是不能养狗 需求2:该方法能养所有品种的狗,但是不能养猫 需求3:该方法能养所有的动物,但是不能传递其他类型 */ //创建集合添加对象 ArrayList<PersianCat> cat1 = new ArrayList<>(); ArrayList<DragonLi> cat2 = new ArrayList<>(); ArrayList<Teddy> dog1 = new ArrayList<>(); ArrayList<SiberianHusky> dog2 = new ArrayList<>(); cat1.add(new PersianCat("球球", 1)); cat2.add(new DragonLi("圆圆", 2)); dog1.add(new Teddy("来福",4)); dog2.add(new SiberianHusky("旺财",3)); keepPet(cat1); keepPet(cat2); keepPet(dog1); keepPet(dog2); } //需求1:该方法能养所有品种的猫,但是不能养狗 /*public static void keepPet(ArrayList<? extends Cat> List) { //遍历集合,调用动物的eat方法 }*/ //需求2:该方法能养所有品种的狗,但是不能养猫 /*public static void keepPet(ArrayList<? extends Dog> List) { //遍历集合,调用动物的eat方法 }*/ //需求3:该方法能养所有的动物,但是不能传递其他类型 public static void keepPet(ArrayList<? extends Animal> List) { //遍历集合,调用动物的eat方法 for (Animal animal : List) { animal.eat(); } } }
Set集合
练习:存储字符串并遍历
利用Set系列的集合,添加字符串,并使用多种方式遍历
(1)迭代器
(2)增强for
(3)Lambda表达式
public class SetDemo01 {
public static void main(String[] args) {
/*
利用Set系列的集合,添加字符串,并使用多种方式遍历
(1)迭代器
(2)增强for
(3)Lambda表达式
*/
//1.创建一个Set集合的对象
Set<String> s = new HashSet<>();
//2.添加元素
//如果当前元素是第一次添加,那么可以添加成功,返回true
//如果当前元素是第二次添加,那么添加失败,返回false
boolean r1 = s.add("zhangsan");
boolean r2 = s.add("zhangsan");
s.add("lisi");
s.add("wangwu");
System.out.println(r1);//true
System.out.println(r2);//false
//3.打印集合
//无序
//System.out.println(s);//[zhangsan]
//System.out.println(s);//[lisi, zhangsan, wangwu]
//迭代器遍历
/*Iterator<String> it = s.iterator();
while(it.hasNext()) {
String str = it.next();
System.out.println(str);
}*/
//增强for
/*for (String s1 : s) {
System.out.println(s1);
}*/
//lambda表达式
s.forEach(str -> System.out.println(str));
}
}
HashSet
-
HashSet底层原理
- HashSet集合底层采取哈希表存储数据
- 哈希表是一种对于增删改查数据性能都较好的数据
-
哈希表组成
- JDK8之前:数组+链表
- JDK8之后:数组+链表+红黑树
-
哈希值
对象的整数表现形式。
- 根据hashCode方法算出来的int类型的整数
- 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
- 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值
-
对象的哈希值特点
- 如果没有重写hashCode方法,不同对象计算出来的哈希值是不同的。
- 如果已经重写hashCode方法,不同的对象只要属性值相同,计算出来的哈希值就是一样的。
- 在小部分情况下,不同的属性值或者不同的地址计算出来的哈希值也有可能是一样的。(哈希碰撞)
public class HashSetDemo01 { public static void main(String[] args) { /* 哈希值: 对象的整数表现形式 1.如果没有重写hashCode方法,不同对象计算出来的哈希值是不同的。 2.如果已经重写hashCode方法,不同的对象只要属性值相同,计算出来的哈希值就是一样的。 3.在小部分情况下,不同的属性值或者不同的地址计算出来的哈希值也有可能是一样的。(哈希碰撞) */ //1.创建对象 Student s1 = new Student("zhangsan", 23); Student s2 = new Student("zhangsan", 23); //2.如果没有重写hashCode方法,不同对象计算出来的哈希值是不同的。 //如果已经重写hashCode方法,不同的对象只要属性值相同,计算出来的哈希值就是一样的。 //System.out.println(s1.hashCode());//460141958(重写之前) //System.out.println(s2.hashCode());//1163157884(重写之前) System.out.println(s1.hashCode());//-1461067292(重写之后) System.out.println(s2.hashCode());//-1461067292(重写之后) //在小部分情况下,不同的属性值或者不同的地址计算出来的哈希值也有可能是一样的。(哈希碰撞) System.out.println("abc".hashCode());//96354 System.out.println("acD".hashCode());//96354 } }
-
HashSet JDK8以前底层原理
-
创建一个默认长度16,默认加载因子为0.75的数组,数组名table
HashSet<String> hm = new HashSet<>();
-
根据元素的哈希值跟数组的长度计算出应存入的位置
int index = (数组长度 - 1) & 哈希值
-
判断当前位置是否是null,如果是null直接存入
-
如果位置不为null,表示有元素,则调用equals方法比较属性值
-
属性值一样——不存/属性值不一样——存入数组,形成链表
JDK8以前:新元素存入数组,老元素挂在新元素下面
JDK8以后:新元素直接挂在老元素下面
-
JDK8以后,当链表长度超过8,而且数组长度大于等于64时,自动转换为红黑树
-
如果集合中存储的是自定义对象,必须要重写hashCode和equals方法
-
-
HashSet的三个问题
- HashSet为什么存和取的顺序不一样?
- HashSet为什么没有索引?
- HashSet是利用什么机制保证数据去重的?(HashCode和equals方法)
-
案例:利用HashSet集合去除重复元素
需求:创建一个存储学生对象的集合,存储多个学生对象。
使用程序实现在控制台遍历该集合。
要求:学生对象的成员变量值相同,我们就认为是同一个对象
public class Student { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } /** * 获取 * @return name */ public String getName() { return name; } /** * 设置 * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * @return age */ public int getAge() { return age; } /** * 设置 * @param age */ public void setAge(int age) { this.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 String toString() { return "Student{name = " + name + ", age = " + age + "}"; } }
public class HashSetDemo02 { public static void main(String[] args) { /* 需求:创建一个存储学生对象的集合,存储多个学生对象。 使用程序实现在控制台遍历该集合。 要求:学生对象的成员变量值相同,我们就认为是同一个对象 */ //1.创建四个学生对象 Student s1 = new Student("zhangsan", 21); Student s2 = new Student("lisi", 23); Student s3 = new Student("wangwu", 22); Student s4 = new Student("zhangsan", 21); //2.创建集合用来添加学生 HashSet<Student> hs = new HashSet<>(); //3.添加元素 System.out.println(hs.add(s1)); System.out.println(hs.add(s2)); System.out.println(hs.add(s3)); System.out.println(hs.add(s4)); //4.打印集合 System.out.println(hs); } }
LinkedHashSet
-
LinkedHashSet底层原理
- 有序、不重复、无索引
- 这里的有序指的是保证存储和取出的顺序一致
- 原理:底层数据结构依然是哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序
public class LinkedHashSetDemo { public static void main(String[] args) { //1.创建四个学生对象 Student s1 = new Student("zhangsan", 21); Student s2 = new Student("lisi", 23); Student s3 = new Student("wangwu", 22); Student s4 = new Student("zhangsan", 21); //2.创建集合用来添加学生 LinkedHashSet<Student> lhs = new LinkedHashSet<>(); //3.添加元素 System.out.println(lhs.add(s3)); System.out.println(lhs.add(s2)); System.out.println(lhs.add(s1)); System.out.println(lhs.add(s4)); //4.打印集合 System.out.println(lhs); } }
TreeSet
-
TreeSet的特点
- 不重复、无索引、可排序
- 可排序:按照元素的默认规则(由小到大)排序
- TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
-
练习:TreeSet对象排序练习题
需求:利用TreeSet存储整数并进行排序
public class TreeSetDemo01 { public static void main(String[] args) { /* 利用TreeSet存储整数并进行排序 */ //1.创建TreeSet集合对象 TreeSet<Integer> ts = new TreeSet<>(); //2.添加元素 ts.add(4); ts.add(5); ts.add(1); ts.add(3); ts.add(2); //3.打印集合 //System.out.println(ts);//[1, 2, 3, 4, 5] //4.遍历集合(三种遍历) //迭代器 /*Iterator<Integer> it = ts.iterator(); while(it.hasNext()) { Integer i = it.next(); System.out.println(i); }*/ //增强for /*for (Integer t : ts) { System.out.println(t); }*/ //lambda表达式 ts.forEach(integer -> System.out.println(integer)); } }
-
TreeSet集合默认的规则
- 对于数值类型:Integer,Double,默认按照从小到大的顺序进行排序
- 对于字符、字符串类型:按照字符在ASCII码表中的数字升序进行排序
-
TreeSet对象排序练习题
需求:创建TreeSet集合,并添加3个学生对象
学生对象属性:姓名、年龄
要求按照学生的年龄进行排序
同年龄按照姓名字母排列(按不考虑中文)
同姓名,同年龄认为是同一个人
public class Student implements Comparable<Student>{ private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } /** * 获取 * @return name */ public String getName() { return name; } /** * 设置 * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * @return age */ public int getAge() { return age; } /** * 设置 * @param age */ public void setAge(int age) { this.age = age; } public String toString() { return "Student{name = " + name + ", age = " + age + "}"; } @Override //this:表示当前要添加的元素 //o:表示已经在红黑树存在的元素 //返回值: //负数:表示当前要添加的元素是小的,存左边 //正数:表示当前要添加的元素是大的,存右边 //0:表示当前要添加的元素已存在,舍弃 public int compareTo(Student o) { System.out.println("-------------"); System.out.println("this:" + this); System.out.println("o:" + o); //指定排序规则 //只看年龄,想要按照年龄的升序进行排序 return this.getAge() - o.getAge(); } }
public class TreeSetDemo02 { public static void main(String[] args) { /* 需求:创建TreeSet集合,并添加3个学生对象 学生对象属性:姓名、年龄 要求按照学生的年龄进行排序 同年龄按照姓名字母排列(按不考虑中文) 同姓名,同年龄认为是同一个人 方式一: 默认排序/自然排序 Student实现Comparable接口,重写里面的抽象方法,再指定比较规则 */ //1.创建三个学生对象 Student s1 = new Student("zhangsan", 23); Student s2 = new Student("lisi", 24); Student s3 = new Student("wangwu", 25); //2.创建集合对象 TreeSet<Student> ts = new TreeSet<>(); //3.添加元素 ts.add(s1); ts.add(s2); ts.add(s3); //4.打印集合 System.out.println(ts); } }
-
TreeSet的两种比较方式
- 方式一:默认排序/自然排序:Javabean类实现Comparable接口指定比较规则。
- 方式二:比较器排序:创建TreeSet对象的时候,传递比较器Comparable制定规则
使用原则:默认使用第一种,如果第一种不能满足当前需求,就使用第二种
-
练习:TreeSet对象排序练习题
需求:请自行选择比较器排序和自然排序两种方式
要求:存入四个字符串,“c”,“ab”,“df”,“qwer” 按照长度排序,如果一样长则按照首字母排序
public class TreeSetDemo03 { public static void main(String[] args) { /* 需求:请自行选择比较器排序和自然排序两种方式 要求:存入四个字符串,"c","ab","df","qwer" 按照长度排序,如果一样长则按照首字母排序 采取第二种排序方式:比较器排序 */ //1.创建集合 //o1:表示当前要添加的元素 //o2:表示已经在红黑树存在的元素 //返回值规则跟之前是一样的 //匿名内部类 /*TreeSet<String> ts = new TreeSet<>(new Comparator<String>() { @Override public int compare(String o1, String o2) { //按照长度排序 int i = o1.length() - o2.length(); //如果一样长则按照首字母排序 i = i == 0 ? o1.compareTo(o2) : i; return i; } });*/ //lambda表达式 TreeSet<String> ts = new TreeSet<>((o1, o2) -> { //按照长度排序 int i = o1.length() - o2.length(); //如果一样长则按照首字母排序 i = i == 0 ? o1.compareTo(o2) : i; return i; } ); //2.添加元素 ts.add("c"); ts.add("ab"); ts.add("df"); ts.add("qwer"); //3.打印集合 System.out.println(ts); } }
-
练习:TreeSet对象排序练习题
需求:创建5个学生对象
属性:(姓名、年龄、语文成绩、数学成绩、英语成绩)
按照总分从高到低输出到控制台
如果总分一样,按照语文成绩排
如果语文一样,按照数学成绩排
如果数学成绩一样,按照英语成绩排
如果英语成绩一样,按照年龄排
如果年龄一样,按照姓名的字母顺序排
如果都一样,认为是同一个学生,不存
public class NewStudent implements Comparable<NewStudent>{ //姓名 private String name; //年龄 private int age; //语文成绩 private int chinese; //数学成绩 private int math; //英语成绩 private int english; public NewStudent() { } public NewStudent(String name, int age, int chinese, int math, int english) { this.name = name; this.age = age; this.chinese = chinese; this.math = math; this.english = english; } @Override /*按照总分从高到低输出到控制台 如果总分一样,按照语文成绩排 如果语文一样,按照数学成绩排 如果数学成绩一样,按照英语成绩排 如果英语成绩一样,按照年龄排 如果年龄一样,按照姓名的字母顺序排 如果都一样,认为是同一个学生,不存*/ public int compareTo(NewStudent o) { int sum1 = this.getChinese() + this.getMath() + this.getEnglish(); int sum2 = o.getChinese() + o.getMath() + o.getEnglish(); //比较两者的总分 int i = sum1 - sum2; //如果总分一样,按照语文成绩排 i = i == 0 ? this.getChinese() - o.getChinese() : i; //如果语文一样,按照数学成绩排 i = i == 0 ? this.getMath() - o.getMath() : i; //如果数学成绩一样,按照英语成绩排 i = i == 0 ? this.getEnglish() - o.getEnglish() : i; //如果英语成绩一样,按照年龄排(可以省略不写) i = i == 0 ? this.getAge() - o.getAge() : i; // 如果年龄一样,按照姓名的字母顺序排 i = i == 0 ? this.getName().compareTo(o.getName()) : i; return i; } /** * 获取 * @return name */ public String getName() { return name; } /** * 设置 * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * @return age */ public int getAge() { return age; } /** * 设置 * @param age */ public void setAge(int age) { this.age = age; } /** * 获取 * @return chinese */ public int getChinese() { return chinese; } /** * 设置 * @param chinese */ public void setChinese(int chinese) { this.chinese = chinese; } /** * 获取 * @return math */ public int getMath() { return math; } /** * 设置 * @param math */ public void setMath(int math) { this.math = math; } /** * 获取 * @return english */ public int getEnglish() { return english; } /** * 设置 * @param english */ public void setEnglish(int english) { this.english = english; } public String toString() { return "NewStudent{name = " + name + ", age = " + age + ", chinese = " + chinese + ", math = " + math + ", english = " + english + "}"; } }
public class TreeSetDemo04 { public static void main(String[] args) { /* 需求:创建5个学生对象 属性:(姓名、年龄、语文成绩、数学成绩、英语成绩) 按照总分从高到低输出到控制台 如果总分一样,按照语文成绩排 如果语文一样,按照数学成绩排 如果数学成绩一样,按照英语成绩排 如果英语成绩一样,按照年龄排 如果年龄一样,按照姓名的字母顺序排 如果都一样,认为是同一个学生,不存 第一种:默认排序/自然排序 第二种:比较器排序 默认情况下,用第一种方式,如果第一种方式不能满足当前的需求,采取第二种方式 */ //1.创建学生对象 NewStudent s1 = new NewStudent("zhangsan", 23, 90, 99, 50); NewStudent s2 = new NewStudent("lisi", 24, 90, 98, 50); NewStudent s3 = new NewStudent("wangwu", 25, 95, 100, 30); NewStudent s4 = new NewStudent("zhaoliu", 26, 60, 99, 70); NewStudent s5 = new NewStudent("qianqi", 26, 70, 80, 70); //2.创建集合 //默认ArrayList //HashSet //TreeSet TreeSet<NewStudent> ts = new TreeSet<>(); //3.添加元素 ts.add(s1); ts.add(s2); ts.add(s3); ts.add(s4); ts.add(s5); //4.打印集合 //System.out.println(ts); for (NewStudent t : ts) { System.out.println(t); } } }
Collention下各集合适用场景
-
如果想要集合中的元素可重复
用ArrayList,基于数组的(用的最多)
-
如果想要集合中的元素可重复,而且当前的增删操作明显多于查询操作
用LinkedList,基于链表的
-
如果想对集合中的元素去重
用HashSet,基于哈希表的。(用的最多)
-
如果想对集合中的元素去重,而且保证存取顺序
用LinkedHashSet,基于哈希表和双链表,效率低于HashSet
-
如果相对集合中的元素进行排序
用TreeSet,基于红黑树,后续也可以用List集合实现排序
说明:
以上笔记学习是学习b站黑马程序员时记下的笔记,仅限于帮助理解学习,不做任何其他商业用途。