集合进阶-单列集合(十二) 集合体系结构
单列集合Collection
List(接口):添加的元素是有序(指存和取顺序一样),可重复(可存在重复的元素),可索引(可通过索引获取元素)
ArrayList(实现类)
LinkedList(实现类)
Vector(已淘汰)(实现类)
Set(接口):添加的元素是无序(指存和取顺序可能不一样),不重复(不能存储重复的元素),无索引(不可通过索引获取元素,不可使用普通for循环遍历)
HashSet(实现类)->LinkedHashSet(实现类)
TreeSet(实现类)
双列集合Map
Collection Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。
常用方法 add public boolean add(E e)
把给定的对象添加到当前集合中
若向List系列集合中添加数据,由于List允许元素重复,永远返回true。
若向Map系列集合中添加数据,由于Map不允许元素重复,若当前添加元素不存在,返回true,表示返回成功;若当前添加元素存在,返回false,表示添加失败。
clear public void clear()
清空集合中所有元素
remove public boolean remove(E e)
删除集合中指定元素,若有,返回true,表示删除成功;若无,返回false,表示删除失败。
因为Collection中定义的是共性方法,List有索引,Map无索引,所以不能通过索引进行删除,只能通过元素对象进行删除
contains public boolean contains(Object obj)
判断当前集合中是否包含给定对象
注 :底层是依赖equals方法判断元素是否存在,若集合中存储的是定义对象,需在Javabean类中重写equals方法。
isEmpty public boolean isEmpty()
判断当前集合是否为空
size public int size()
判断集合中元素个数/集合的长度
范例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 //Test.java import java.util.ArrayList; import java.util.Collection; public class Test{ public static void main(String[] args){ Collection<String> collection=new ArrayList<>(); System.out.println("add方法"); collection.add("aaa"); collection.add("bbb"); collection.add("ccc"); System.out.println(collection+"\n"); System.out.println("clear方法"); collection.clear(); System.out.println(collection+"\n"); System.out.println("remove方法"); System.out.println(collection.remove("aaa")); collection.add("aaa"); collection.add("bbb"); collection.add("ccc"); System.out.println(collection.remove("aaa")); System.out.println(collection+"\n"); System.out.println("contains方法"); System.out.println(collection.contains("bbb")); System.out.println(collection.contains("aaa")); Student student1=new Student(19,"张三"); Collection<Student> collection1=new ArrayList<>(); collection1.add(student1); Student student2=new Student(19,"张三"); System.out.println(collection1.contains(student2)+"\n"); System.out.println("isEmpty方法"); System.out.println(collection.isEmpty()+"\n"); System.out.println("size方法"); System.out.println(collection); System.out.println(collection1); System.out.println(collection.size()); System.out.println(collection1.size()); } } //Student.java import java.util.Objects; public class Student { private int age; private String name; public Student(){} public Student(int age, String name) { this.age = age; this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @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); } } /* add方法 [aaa, bbb, ccc] clear方法 [] remove方法 false true [bbb, ccc] contains方法 true false true isEmpty方法 false size方法 [bbb, ccc] [com.ljsblog.domain12.Student@14ae5a5] 2 1 */
遍历方式
迭代器遍历
增强for遍历
Lambda表达式遍历
迭代器遍历 迭代器在Java之法的类是Iterator,迭代器是集合专用的遍历方式。
获取迭代器 :
常用方法 :
boolean hashNext()
判断当前位置是否有元素,有元素,返回true;无元素,返回false
E next()
default void remove()
从基础集合中移除这个迭代器返回的最后一个元素(可选操作)。
范例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class Test{ public static void main(String[] args){ Collection<String> collection=new ArrayList<>(); collection.add("aaa"); collection.add("bbb"); collection.add("ccc"); Iterator<String> iterator=collection.iterator(); while(iterator.hasNext()){ String str=iterator.next(); System.out.println(str); } } }
注 :
若当前位置没有元素,还要强行获取,则会NoSuchElementException
迭代器遍历完毕,指针不会复位,若想重新遍历,需创建新的迭代器对象
循环中只能用一次next方法,否则可能会越界
迭代器遍历时,不能用集合的方法进行增加或删除。若必须要删除,则可以用迭代器提供的remove方法进行删除。
增强for遍历 JDK5之后出现,内部原理就是一个Iterator迭代器,增强for的底层就是迭代器,为简化迭代器的代码书写的,所有的单列集合和数组才能用增强for进行遍历。
格式:
1 for(元素的数据类型 变量名:数组或单列集合)
注 :修改增强for中的变量,不会改变集合中原本的数据。
范例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.util.ArrayList; import java.util.Collection; public class Test{ public static void main(String[] args){ Collection<String> collection=new ArrayList<>(); collection.add("aaa"); collection.add("bbb"); collection.add("ccc"); for(String s:collection){ System.out.println(s); } } }
Lambda表达式遍历 方法名称 :
default void forEach(Consumer<? super T> action)
说明 :结合lambda遍历集合
范例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import java.util.ArrayList; import java.util.Collection; import java.util.function.Consumer; public class Test{ public static void main(String[] args){ Collection<String> collection=new ArrayList<>(); collection.add("aaa"); collection.add("bbb"); collection.add("ccc"); //匿名内部类 //底层原理: //遍历集合,得到每个元素 //每个元素,传给accept方法。s依次表示集合中的每一个数据 collection.forEach(new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }); //lambda省略版 collection.forEach(s-> System.out.println(s)); } }
List Collection的方法List全部继承,List集合因为有索引,所以多了很多索引操作的方法。
常用方法 add void add(int index,E element)
在此集合的指定位置插入指定的元素
remove 格式1 :
E remove(int index)
删除指定索引处的元素,原来索引的上的元素会依次后移,返回被删除的元素
格式2 :
boolean remove(Object o)
直接删除元素
注: 在调用方法时,若方法出现重载现象,优先调用实参形参一致的方法
set E set(int index,E element)
修改指定索引处的元素,返回被修改的元素
get E get(int index)
返回指定索引处的元素
范例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import java.util.ArrayList; import java.util.List; public class Test{ public static void main(String[] args){ List<String> list=new ArrayList<>(); System.out.println("add方法"); list.add("aaa"); list.add("bbb"); list.add("ccc"); System.out.println(list+"\n"); System.out.println("remove方法,通过索引删除"); System.out.println(list.remove(1)); System.out.println(list+"\n"); System.out.println("remove方法,优先级判定"); List<Integer> list1=new ArrayList<>(); list1.add(1); list1.add(2); list1.add(3); Integer i=1; //此处调用的是boolean remove(Object o)方法 list1.remove(i); System.out.println(list1); List<Integer> list2=new ArrayList<>(); list2.add(1); list2.add(2); list2.add(3); //此处调用的是E remove(int index)方法 list2.remove(1); System.out.println(list2); System.out.println("set方法"); System.out.println(list.set(1,"bbb")); System.out.println(list+"\n"); System.out.println("get方法"); System.out.println(list.get(1)); } } /* add方法 [aaa, bbb, ccc] remove方法,通过索引删除 bbb [aaa, ccc] remove方法,优先级判定 [2, 3] [1, 3] set方法 ccc [aaa, bbb] get方法 bbb */
遍历方式
迭代器遍历 :在遍历的过程中需要删除元素,使用迭代器
列表迭代器 :在遍历的过程中需要增加元素,使用列表迭代器
增强for遍历 :仅仅想遍历,使用增强for或lambda表达式
Lambda表达式
普通for :若遍历时想操作索引,可以使用普通for
范例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ListIterator; public class Test{ public static void main(String[] args){ List<String> list=new ArrayList<>(); list.add("aaa"); list.add("bbb"); list.add("ccc"); list.add("ddd"); //迭代器遍历 Iterator<String> iterator=list.iterator(); while (iterator.hasNext()){ String str=iterator.next(); if("ddd".equals(str)){ iterator.remove(); } System.out.println(str); } System.out.println(list+"\n"); //增强for遍历 for(String s:list){ System.out.println(s); } System.out.println(); //lambda表达式 list.forEach(s-> System.out.println(s)); System.out.println(); //普通for for (int i = 0; i <list.size() ; i++) { System.out.println(list.get(i)); } System.out.println(); //列表迭代器 ListIterator<String> listIterator=list.listIterator(); while (listIterator.hasNext()){ String str=listIterator.next(); if("ccc".equals(str)){ listIterator.add("ddd"); } System.out.println(str); } System.out.println(list); } } /* aaa bbb ccc ddd [aaa, bbb, ccc] aaa bbb ccc aaa bbb ccc aaa bbb ccc aaa bbb ccc [aaa, bbb, ccc, ddd] */
数据结构 数据结构 是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。
常见的数据结构 :
栈
队列
数组
链表
二叉树
二叉查找树
平衡二叉树
红黑树
栈 栈 只能在表的一个固定端进行数据结点的插入和删除操作。
特点 :后进先出,先进后出。
先插入的数据将被压入栈底,最后插入的数据在栈顶,读出数据时,从栈顶开始逐个读出。
数据进入栈模型的过程称为:压/进栈
数据离开栈模型的过程称为:弹/出栈
队列 队列 只允许在表的一端进行插入操作,而在另一端进行删除操作。
特点 :先进先出,后进后出。
进行插入操作的一端称为队尾,进行删除操作的一端称为队头。
数据从队尾进入队列模型的过程称为:入队列
数据从队头离开队列模型的过程称为:出队列
数组 特点 :
查询速度快 :查询数据通过地址值和索引定位,查询任意数据耗时相同(元素在内存中是连续存储的)。
删除效率低 :要将原始数据删除,同时后面每个数据前移
添加效率极低 :添加位置的每个数据后移,再添加数据。
链表 链表 中的结点 是独立的对象,在内存中是不连续 的。
单向链表 每个节点包含数据值 和下个结点的地址 。
双向链表 每个节点包含上个结点的地址 ,数据值 和下个结点的地址 。
链表查询慢 ,无论查询哪个数据都要从头找,但首尾操作 极快。
链表增删 相对较快。
树 二叉树 二叉树 是指树中节点(结点)的度 不大于2的有序树,即二叉树是每个结点最多有两个子树的树结构。通常子树被称作左子树 和右子树 。
二叉树的结点包括 :父节点地址 ,左子节点地址 ,右子节点地址 ,值 。
度 :每个节点的子节点数量。
树高 :树的总层数。
根节点 :最顶层的结点。
左子节点 :左下方的结点。
右子节点 :右下方的结点。
二叉查找树 二叉查找树 ,又称二叉排序树或二叉搜索树。
特点 :
每个结点上最多有两个子节点
任意结点左子树上的值都小于当前节点
任意结点右子树上的值都大于当前节点
添加结点规则 :
小的存左边
大的存右边
一样的不存
弊端 :二叉查找树可能退化成链表,相应的,二叉搜索树的查找操作是和这棵树的高度相关的,而此时这颗树的高度就是这颗树的节点数n。
平衡二叉树 任意结点的左右子树高度不超过1,任意结点的左右两个子树都是一颗平衡二叉树。
遍历方式 所有二叉树都可使用
前序遍历 :从根节点开始,按照当前节点,左子节点,右子节点的顺序遍历。
中序遍历 :从最左边的子节点开始,按照左子节点,当前节点,右子节点的顺序遍历。
后续遍历 :从最左边的子节点开始,按照左子节点,右子节点,当前节点的顺序遍历。
层序遍历 :从根节点开始一层一层遍历。
旋转机制 :左旋和右旋。
触发时机 :当添加一个结点之后,该数不再是一颗平衡二叉树
左旋 确定支点 :从添加的结点开始,不断的往父节点找不平衡的节点。
步骤 :
以不平衡的点为支点
把支点左旋降级,变成左子节点
晋升原来的右子节点
或
以不平衡的点为支点
将根节点的右侧向左拉
原本的右子节点变成新的父节点,并把多余的左子节点出让,给已降级的根节点当右子节点
右旋 确定支点 :从添加的结点开始,不断的往父节点找不平衡的节点。
步骤 :
以不平衡的点为支点
把支点右旋降级,变成右子节点
晋升原来的左子节点
或
以不平衡的点为支点
将根节点的左侧向右拉
原本的左子节点变成新的父节点,并把多余的右子节点出让,给已降级的根节点当左子节点
需旋转的四种情况
左左 :当根节点左子树的左子树有节点插入,导致二叉树不平衡,需进行一次右旋 。
左右 :当根节点左子树的右子树有节点插入,导致二叉树不平衡,需先局部左旋 ,再整体右旋 。
右右 :当根节点右子树的右子树有节点插入,导致二叉树不平衡,需进行一次左旋 。
右左 :当根节点右子树的左子树有节点插入,导致二叉树不平衡,需先局部右旋 ,再整体左旋 。
红黑树 红黑树 :是一种自平衡的二叉查找树 ,是一种特殊的二叉查找树 ,红黑树上的每一个结点都有存储位表示结点的颜色,每一个结点可以是红或者黑,红黑树不是高度平衡的,它的平衡是通过红黑规则 进行实现的。
红黑树 增删改查的性能都很好。
节点 :父节点地址 ,左子节点地址 ,右子节点地址 ,值 ,颜色 。
红黑规则 :
每个节点或是红色,或是黑色
根节点必须是黑色
若一个节点没有子节点或父节点,则该节点响应的指针属性为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的。
若某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
对每一个节点,从该节点到其所有后代叶节点的简单路径(只能向下走,不能往回走)上,均包含相同数目的黑色节点
添加结点规则 :添加的节点默认是红色(效率高)。
根 :直接变为黑色
非根 :
父黑色 :则不需要任何操作
父红色 :
叔叔红色 :将“父”设为黑色,将“叔叔”设为黑色,将”祖父“设为”红色“。若祖父为根,则将根变回黑色;若祖父非根,将祖父设置为当前节点再进行其他判断
叔叔黑色 ,当前节点是父的右孩子 :把父作为当前节点并左旋(旋转时忽视Nil节点),再进行判断
叔叔黑色 ,当前节点是父的左孩子 :将”父“设为”黑色“,将”祖父“设为”红色“,以祖父为支点进行右旋
ArrayList和LinkedList ArrayList
利用空参创建的集合,在底层创建一个默认长度为0的数组。
添加第一个元素时,底层会创建一个新的长度为10的数组
数组存满时,会扩容1.5倍,若一次添加多个元素,1.5倍装不下,则新创建数组的长度以实际为为准。
LinkedList 底层数据结构是双链表,查询慢,增删快,若操作的是首尾元素,速度也是极快的。
LinkedList本身多出很多直接操作首尾元素的方法。
泛型 若并未个集合指定类型,默认数据类型为Object类,此时可以给集合添加任意数据类型,但我们获取数据时,无法使用子类特有的行为。
泛型 :是JDK5中引入的特性,可在编译阶段约束操作的数据类型,并进行检查。
格式 :<引用数据类型>
好处 :统一数据类型,将运行时期的问题提前到编译期间,避免强制类型转换可能出现的异常,因此在编译阶段就能确定下来,并且能调用特有的行为。
细节 :
泛型中不能写基本数据类型,需将基本数据类型转换成对应的包装类
指定泛型的具体类型后,传送数据时,可传送该类类型或其子类类型
若不写泛型,类型默认是Object
泛型可在类 ,方法 ,接口 后进行定义,即泛型类 ,泛型方法 ,泛型接口
泛型类 当一个类中,某个变量的数据类型不确定,可定义泛型类。
格式 :
1 2 3 4 修饰符 class 类名<E>{ } //此处E可以理解为记录数据类型的对象,可写为:T,E,K,V等
范例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 //MyArrayList.java import java.util.Arrays; public class MyArrayList<E> { int size; Object[] objects=new Object[10]; public boolean add(E e){ objects[size]=e; size++; return true; } public E get(int index){ return (E)objects[index]; } @Override public String toString() { return Arrays.toString(objects); } } //Test.java public class Test{ public static void main(String[] args) { MyArrayList<String> myArrayList = new MyArrayList<>(); myArrayList.add("aaa"); myArrayList.add("bbb"); myArrayList.add("ccc"); System.out.println(myArrayList); System.out.println(myArrayList.get(1)); } } /* [aaa, bbb, ccc, null, null, null, null, null, null, null] bbb */
泛型方法 方法中形参类型不确定时:
使用类名后面定义的泛型。(所有方法都能用)
在方法申明上定义自己的泛型。(只有本方法能用)
格式 :
1 2 3 4 修饰符<T> 返回值类型 方法名(类型 变量名){ } //此处T可以理解为记录数据类型的对象,可写为:T,E,K,V等
范例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 //定义一个工具类,ListUtil //类中定义一个静态方法addAll,用来添加另一个集合中的元素 //ListUtil.Java import java.util.ArrayList; public class ListUtil { private ListUtil(){} public static<T> boolean addALL(ArrayList<T> list1, ArrayList<T> list2){ for(int i=0;i<list2.size();i++){ list1.add(list2.get(i)); } return true; } } //test.java import java.util.ArrayList; public class Test{ public static void main(String[] args) { ArrayList<String> arrayList=new ArrayList<>(); arrayList.add("aaa"); arrayList.add("bbb"); arrayList.add("ccc"); ArrayList<String> arrayList1=new ArrayList<>(); arrayList1.add("ddd"); arrayList1.add("eee"); arrayList1.add("fff"); ListUtil.addALL(arrayList, arrayList1); System.out.println(arrayList); } } /* [aaa, bbb, ccc, ddd, eee, fff] */
泛型接口 格式 :
1 2 3 修饰符 interface 接口名<类型>{ }
使用方式 :
实现类给出具体的类型,创造实现类的对象时不需要给出泛型
实现类延续泛型创建实现类对象时再确定类型,创造实现类的对象时需要给出泛型
方式一范例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 //接口类 //ListAdd.java public interface ListAdd<E>{ public abstract void add(E e); } //实现类 //MyArrayList.java import java.util.Arrays; public class MyArrayList implements ListAdd<String> { Object[] objects=new Object[10]; int size=0; @Override public void add(String s) { objects[size]=s; size++; } @Override public String toString() { return Arrays.toString(objects); } } //测试类 //Test.java public class Test{ public static void main(String[] args) { MyArrayList myArrayList=new MyArrayList(); myArrayList.add("aaa"); myArrayList.add("bbb"); myArrayList.add("ccc"); System.out.println(myArrayList); } } /* [aaa, bbb, ccc, null, null, null, null, null, null, null] */
方式二范例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 //接口类 //ListAdd.java public interface ListAdd<E>{ public abstract void add(E e); } //实现类 //MyArrayList.java import java.util.Arrays; public class MyArrayList<E> implements ListAdd<E> { Object[] objects=new Object[10]; int size=0; @Override public void add(E e) { objects[size]=e; size++; } @Override public String toString() { return Arrays.toString(objects); } } //测试类 //Test.java public class Test{ public static void main(String[] args) { MyArrayList<String> myArrayList=new MyArrayList(); myArrayList.add("aaa"); myArrayList.add("bbb"); myArrayList.add("ccc"); System.out.println(myArrayList); } } /* [aaa, bbb, ccc, null, null, null, null, null, null, null] */
通配符 通配符 可以限定类型的范围
泛型不具备继承性,但泛型的数据具备继承性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import java.util.ArrayList; public class Test{ public static void main(String[] args) { ArrayList<Ye> list1=new ArrayList<>(); ArrayList<Fu> list2=new ArrayList<>(); ArrayList<Zi> list3=new ArrayList<>(); //泛型不具有继承性 //method(list1); //method(list2);会报错 //method(list3);会报错 //数据具有继承性 list1.add(new Ye()); list1.add(new Fu()); list1.add(new Zi()); } public static void method(ArrayList<Ye> list){ } } class Ye{ } class Fu extends Ye{ } class Zi extends Fu{ }
通配符 :
? 表示不确定的类型,也可以进行类型的限定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.ljsblog.domain12; import java.util.ArrayList; public class Test{ public static void main(String[] args) { ArrayList<Ye> list1=new ArrayList<>(); ArrayList<Fu> list2=new ArrayList<>(); ArrayList<Zi> list3=new ArrayList<>(); method1(list1); method1(list2); method1(list3); method2(list1); method2(list2); method2(list3); } public static void method1(ArrayList<? extends Ye> list){ } public static void method2(ArrayList<? super Zi> list){ } } class Ye{ } class Fu extends Ye{ } class Zi extends Fu{ }
使用场景 :
若在定义类,方法,接口时,类型不确定,就可以定义,泛型类,泛型方法,泛型接口。
若类型不确定,但只传递某个继承体系,可使用泛型的通配符
范例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 /*需求 定义一个继承结构: 动物 / \ 猫 狗 / \ / \ 波斯猫 狸花猫 泰迪 哈士奇 属性:名字,年龄 行为:吃东西 波斯猫方法体打印:一只叫做XXX的,X岁的波斯猫,正在吃小饼干 狸花猫方法体打印:一只叫做xxx的,x岁的狸花猫,正在吃鱼 泰迪方法体打印:一只叫做xxx的,x岁的泰迪,正在吃骨头,边吃边蹭 哈士奇方法体打印:一只叫做XXX的,X岁的哈士奇,正在吃骨头,边吃边拆家 测试类中定义一个方法用于饲养动物 public static void keepPet(ArrayList<???> list){ 遍历集合,调用动物的eat方法 } 要求1:该方法能养所有品种的猫,但是不能养狗 要求2:该方法能养所有品种的狗,但是不能养猫 要求3:该方法能养所有的动物,但是不能传递其他类型 */ //Animal.java public abstract class Animal { private String name; private int age; public Animal(){} public Animal(String name,int age){ this.name=name; this.age=age; } public void setName(String name){ this.name=name; } public String getName(){ return name; } public void setAge(int age){ this.age=age; } public int getAge(){ return age; } public abstract void eat(); } //Dog.java public abstract class Dog extends Animal{ public Dog() { } public Dog(String name, int age) { super(name, age); } } //Cat.java public abstract class Cat extends Animal { public Cat(){} public Cat(String name,int age){ super(name,age); } } //Bosi.java public class Bosi extends Cat { public Bosi(){} public Bosi(String name,int age){ super(name,age); } @Override public void eat(){ System.out.println("一只叫做"+getName()+"的,"+getAge()+"岁的波斯猫,正在吃小饼干"); } } //Lihua.java public class Lihua extends Cat { public Lihua() { } public Lihua(String name, int age) { super(name, age); } @Override public void eat(){ System.out.println("一只叫做"+getName()+"的,"+getAge()+"岁的狸花猫,正在吃鱼"); } } //Taidi.java public class Taidi extends Dog{ public Taidi() { } public Taidi(String name, int age) { super(name, age); } @Override public void eat(){ System.out.println("一只叫做"+getName()+"的,"+getAge()+"岁的泰迪,正在吃骨头,边吃边蹭"); } } //Hashiqi.java public class Hashiqi extends Dog{ public Hashiqi() { } public Hashiqi(String name, int age) { super(name, age); } @Override public void eat(){ System.out.println("一只叫做"+getName()+"的,"+getAge()+"岁的哈士奇,正在吃骨头,边吃边拆家"); } } //Test.java import java.util.ArrayList; public class Test{ public static void main(String[] args) { ArrayList<Bosi> list1=new ArrayList<>(); Bosi bosi1=new Bosi("波斯猫甲",7); Bosi bosi2=new Bosi("波斯猫乙",9); list1.add(bosi1); list1.add(bosi2); keepPet1(list1); ArrayList<Lihua> list2=new ArrayList<>(); Lihua lihua1=new Lihua("狸花猫甲",1); Lihua lihua2=new Lihua("狸花猫乙",2); list2.add(lihua1); list2.add(lihua2); keepPet1(list2); ArrayList<Hashiqi> list3=new ArrayList<>(); Hashiqi hashiqi1=new Hashiqi("哈士奇甲",2); Hashiqi hashiqi2=new Hashiqi("哈士奇乙",3); list3.add(hashiqi1); list3.add(hashiqi2); keepPet2(list3); ArrayList<Taidi> list4=new ArrayList<>(); Taidi taidi1=new Taidi("泰迪甲",12); Taidi taidi2=new Taidi("泰迪乙",6); list4.add(taidi1); list4.add(taidi2); keepPet2(list4); keepPet3(list1); keepPet3(list2); keepPet3(list3); keepPet3(list4); } //要求1:该方法能养所有品种的猫,但不能养狗 public static void keepPet1(ArrayList<? extends Cat> list){ for(int i=0;i<list.size();i++){ list.get(i).eat(); } } //要求2:该方法能养所有品种的狗,但不能养猫 public static void keepPet2(ArrayList<? extends Dog> list){ for(int i=0;i<list.size();i++){ list.get(i).eat(); } } //要求3:该方法能养所有动物 public static void keepPet3(ArrayList<? extends Animal> list){ for(int i=0;i<list.size();i++){ list.get(i).eat(); } } }
Set Set接口中的方法基本上与Collection的API一致。
实现类 :
Hashset :无序、不重复、无索引
LinkedHashSet :有序,不重复,无索引
TreeSet :可排序,不重复,无索引
若要数据去重,默认使用HashSet,若要求去重且存取有序,使用LinkedHashSet。
范例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 /* 利用Set系列的集合,添加字符串,并使用多种方式遍历 迭代器 增强for lambda表达式 */5 import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class Test { public static void main(String[] args) { Set<String> s=new HashSet<>(); System.out.println(s.add("aaa")); System.out.println(s.add("aaa"));//不可重复 System.out.println(s.add("bbb")); System.out.println(s.add("ccc")); System.out.println(s);//打印无序 System.out.println("\n"+"迭代器"); Iterator<String> iterator=s.iterator(); while(iterator.hasNext()){ String s1=iterator.next(); System.out.println(s1); } System.out.println("\n"+"for增强遍历"); for(String s1:s){ System.out.println(s1); } System.out.println("\n"+"lambda表达式"); s.forEach(s1-> System.out.println(s1)); } } /* true false true true [aaa, ccc, bbb] 迭代器 aaa ccc bbb for增强遍历 aaa ccc bbb lambda表达式 aaa ccc bbb */
HashSet HashSet集合底层采取哈希表 存储数据
哈希表和哈希值 哈希表 是一种对增删改查数据性能都较好的结构
哈希表组成 :
JDK8之前:数组+链表
JDK8开始:数组+链表+红黑树
哈希值 :
根据hashCode方法算出来的int类型的整数
该方法定义在Object类中,所有对象都可调用,默认使用地址值进行计算
一般情况下,会重写hashCode的方法,利用对象内部的属性值计算哈希值
对象的哈希值的特点 :
若并未重写hashCode方法,不同对象计算出的哈希值是不同的
若并重写hashCode方法,不同对象只要属性值相同,计算出的哈希值是一样的
在小部分情况下,不同的属性值或不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 //Test.java public class Test { public static void main(String[] args) { Student student1=new Student("张三",18); Student student2=new Student("张三",18); System.out.println(student1.hashCode()); System.out.println(student2.hashCode()); //哈希碰撞 System.out.println("abc".hashCode()); System.out.println("acD".hashCode()); } } //Student.java import java.util.Objects; 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; } 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); } } /* 24022538 24022538 96354 96354 */
底层原理
创建一个默认长度16,默认加载因为0.75的数组,数组名为table。
加载因即当数组存储元素为16*0.75=12时,会扩容一倍,数组长度变为32
根据元素的哈希值跟数组的长度计算出应存入的位置i
判断当前位置是否为null,若是null直接存入
若位置不为null,表示有元素,则调用equals方法比较属性值
一样,不存;不同:存入数组形成链表
JDK8以前:新元素存入数组,老元素挂在新元素下面
JDK8以后:新元素直接挂在老元素下面
注 :jdk8以后,当链表长度超过8,且数组长度大于等于64时,自动转换为红黑树。
若集合中存储的是自定义对象,必须重写hashCode和equals方法。
范例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 /* 需求:创建一个存储学生对象的集合,存储多个学生对象 使用程序实现在控制台遍历该集合 要求:学生对象的成员变量值相同,我们就认为是同一个对象 */ //Student.java import java.util.Objects; 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; } 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); } } //Test.java import java.util.HashSet; public class Test { public static void main(String[] args) { Student student1=new Student("张三",18); Student student2=new Student("张三",18); Student student3=new Student("李四",18); Student student4=new Student("王五",19); HashSet<Student> hashSet=new HashSet<>(); System.out.println(hashSet.add(student1)); System.out.println(hashSet.add(student2)); System.out.println(hashSet.add(student3)); System.out.println(hashSet.add(student4)); System.out.println(hashSet); } } /* true false true true [Student{name = 王五, age = 19}, Student{name = 张三, age = 18}, Student{name = 李四, age = 18}] */
LinkedHashSet 有序 ,不重复 ,无索引
底层原理 底层数据结构依然是哈希表,只是每个元素又额外多了一个双链表的机制记录存取的顺序。
范例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 //Student.java import java.util.Objects; 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; } 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); } } //Test.java import java.util.LinkedHashSet; public class Test { public static void main(String[] args) { Student student1=new Student("张三",18); Student student2=new Student("张三",18); Student student3=new Student("李四",18); Student student4=new Student("王五",19); LinkedHashSet<Student> lhs=new LinkedHashSet<>(); System.out.println(lhs.add(student1)); System.out.println(lhs.add(student2)); System.out.println(lhs.add(student3)); System.out.println(lhs.add(student4)); System.out.println(lhs); } } /* true false true true [Student{name = 张三, age = 18}, Student{name = 李四, age = 18}, Student{name = 王五, age = 19}] */
TreeSet 不重复,无索引,可排序
可排序 :按照元素的默认规则(从小到大)排序。
TreeSet 集合底层是基于红黑树 的数据结构实现排序的,增删改查性能都比较好。
范例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import java.util.Iterator; import java.util.TreeSet; public class Test { public static void main(String[] args) { TreeSet<Integer> treeSet=new TreeSet<>(); treeSet.add(99); treeSet.add(13); treeSet.add(123); treeSet.add(113); treeSet.add(69); treeSet.add(59); System.out.println(treeSet); System.out.println("\n迭代器遍历"); Iterator<Integer> iterator=treeSet.iterator(); while(iterator.hasNext()){ int i=iterator.next(); System.out.println(i); } System.out.println("\n增强for遍历"); for(int n:treeSet){ System.out.println(n); } System.out.println("\nlambda表达式"); treeSet.forEach(i-> System.out.println(i)); } } /* [13, 59, 69, 99, 113, 123] 迭代器遍历 13 59 69 99 113 123 增强for遍历 13 59 69 99 113 123 lambda表达式 13 59 69 99 113 123 */
TreeSet集合默认的规则 :
对于数值类型:Integer,Double,默认按照从小到大的顺序进行排序
对于字符,字符串类型,按照字符在ASCII码表中的数字升序进行排序
TreeSet的两种比较方式
默认排序/自然排序
比较器排序
使用原则 :默认使用第一种,若第一种不能满足当前需求,则使用第二种。
当方式一和方式二重复时,以方式二为准。
默认排序/自然排序 JavaBean类实现Comparable接口,指定比较规则。
范例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 /* 需求:创建TreeSet集合,并添加4个学生对象 学生对象属性: 姓名,年龄 要求按照学生的年龄进行排序,同年龄按照字母排序 同姓名,同年龄认为是同一个人 */ //Student.java 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 public int compareTo(Student o) { //this:表示当前要添加的元素 //o:表示已在红黑树存在的元素 //返回值: //负数:表示当前要添加的元素是小的,存左边 //正数:表示当前要添加的元素是大的,存右边 //0:表示当前添加的元素已存在,舍弃 int i=this.getAge()-o.getAge(); return i==0?this.getName().compareTo(o.getName()):i; } } //Test.java import java.util.TreeSet; public class Test { public static void main(String[] args) { TreeSet<Student> treeSet=new TreeSet<>(); Student student1=new Student("zs",23); Student student2=new Student("ls",19); Student student3=new Student("bw",19); Student student4=new Student("zz",25); treeSet.add(student1); treeSet.add(student2); treeSet.add(student3); treeSet.add(student4); System.out.println(treeSet); } } /* [Student{name = bw, age = 19}, Student{name = ls, age = 19}, Student{name = zs, age = 23}, Student{name = zz, age = 25}] */
比较器排序 创建TreeSet对象时,传送比较器Comparator指定规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 /* 要求:存入四个字符串,"asadsa""dasddw""rgreg""feg",按照长度排序,若等长则按照首字母排序 */ //import java.util.Comparator; import java.util.TreeSet; public class Test { public static void main(String[] args) { /* TreeSet<String> treeSet=new TreeSet<>(new Comparator<String>() { @Override public int compare(String o1, String o2) { int i = o1.length()-o2.length(); return i==0?o1.compareTo(o2):i; } }); */ //o1:表示当前要添加的元素 //o2:表示已在红黑树中存在的元素 //返回值规则跟之前一样 TreeSet<String> treeSet=new TreeSet<>((o1,o2)->{ int i = o1.length()-o2.length(); return i==0?o1.compareTo(o2):i; }); treeSet.add("asadsa"); treeSet.add("dasddw"); treeSet.add("rgreg"); treeSet.add("feg"); System.out.println(treeSet); } } //[feg, rgreg, asadsa, dasddw]
范例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 /* 创建5个学生对象 属性:姓名,年龄,语文成绩,数学成绩,英语成绩 按照总分从高到低输出 若总分一样,按语文排 若语文一样,按数学排 若数学一样,按英语排 若英语一样,按年龄排 若年龄一样,按姓名排 若全部一样,认为是同一学生,不存 */ //Student.java package com.ljsblog.domain1; public class Student implements Comparable<Student>{ private String name; private int age; private int chinese_score; private int math_score; private int english_score; public Student() { } public Student(String name, int age, int chinese_score, int math_score, int english_score) { this.name = name; this.age = age; this.chinese_score = chinese_score; this.math_score = math_score; this.english_score = english_score; } /** * 获取 * @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_score */ public int getChinese_score() { return chinese_score; } /** * 设置 * @param chinese_score */ public void setChinese_score(int chinese_score) { this.chinese_score = chinese_score; } /** * 获取 * @return math_score */ public int getMath_score() { return math_score; } /** * 设置 * @param math_score */ public void setMath_score(int math_score) { this.math_score = math_score; } /** * 获取 * @return english_score */ public int getEnglish_score() { return english_score; } /** * 设置 * @param english_score */ public void setEnglish_score(int english_score) { this.english_score = english_score; } public int getSum(){ return chinese_score+math_score+english_score; } public String toString() { return "Student{name = " + name + ", age = " + age + ", chinese_score = " + chinese_score + ", math_score = " + math_score + ", english_score = " + english_score + ", sum = " + getSum() + "}"; } @Override public int compareTo(Student o) { if(o.getSum()!=this.getSum()){ return o.getSum()-this.getSum(); }else if(o.getChinese_score()!=this.getChinese_score()){ return o.getChinese_score()-this.getChinese_score(); }else if(o.getMath_score()!=this.getMath_score()){ return o.getMath_score()-this.getMath_score(); }else if(o.getEnglish_score()!=this.getEnglish_score()){ //因为总分相等,语文相等,数学相等,英语必然相等,这里的英语可以不写,但还是写上了 return o.getEnglish_score()-this.getEnglish_score(); }else if(o.getAge()!=this.getAge()){ return o.getAge()-this.getAge(); }else{ return o.getName().compareTo(this.getName()); } } } //Test.java import java.util.TreeSet; public class Test { public static void main(String[] args) { TreeSet<Student> treeSet=new TreeSet<>(); Student student1=new Student("one",19,88,100,99); Student student2=new Student("two",19,88,100,99); Student student3=new Student("three",20,76,26,48); Student student4=new Student("four",23,97,46,18); Student student5=new Student("five",18,65,95,26); Student student6=new Student("one",19,88,100,99); treeSet.add(student1); treeSet.add(student2); treeSet.add(student3); treeSet.add(student4); treeSet.add(student5); treeSet.add(student6); for(Student student:treeSet){ System.out.println(student); } } } /* Student{name = two, age = 19, chinese_score = 88, math_score = 100, english_score = 99, sum = 287} Student{name = one, age = 19, chinese_score = 88, math_score = 100, english_score = 99, sum = 287} Student{name = five, age = 18, chinese_score = 65, math_score = 95, english_score = 26, sum = 186} Student{name = four, age = 23, chinese_score = 97, math_score = 46, english_score = 18, sum = 161} Student{name = three, age = 20, chinese_score = 76, math_score = 26, english_score = 48, sum = 150} */
使用场景
若想集合中元素可重复
若想集合中元素可重复,且当前的增删操作明显多于查询
若想对集合中元素去重
若想对集合中元素去重,而且保证存取顺序
用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet。
若想对集合中的元素进行排序
用TreeSet集合,基于红黑树,后续也可用List集合实现排序