集合类
0.集合类概述
1. 为什么需要集合类?
很多情况下,我们需要对一组对象进行操作。为了解决事先并不知道到底有多少个对象,Java 提供了集合类供我们使用。(存储更多类型问题, 扩容问题, 内存空间浪费问题, 数据查找问题, 数据删除问题等等)
2. 集合类的特点
a. 只能存储引用数据类型
b. 可以自动地调整自己的大小
3. 数组和集合类都是容器,它们有何不同?
a. 数组可以存储基本数据类型的数据,集合不可以。
b. 数组的长度是固定的,集合可以自动调整自己的大小。
c. 数组效率高,集合效率较低。
d. 数组没有API,集合有丰富的API。
数据结构:
数据结构是相互之间存在一种或多种特定关系的数据元素的集合。
元素之间,通常有以下四种基本结构:
1.集合
2.线性结构
3.树形结构
4.图或网状结构
逻辑结构&物理结构:
逻辑结构:数据元素间的逻辑关系
物理结构:数据结构在计算机中的表示,又称为存储结构或者映像。
结构的表示可以分为两种:顺序存储结构 (顺序映像) 和 链式存储结构 (非顺序映像)。
顺序映像:借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系。(数组)
非顺序映像:借助指示元素存储地址的”指针”,来表示数据元素的逻辑关系。(链表)
1.Collection
概念
Collection 层次结构中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。
- 一些 collection 允许有重复的元素,而另一些则不允许
- 一些 collection 是有序的,而另一些则是无序的
public class CollectionDemo4 {
public static void main(String[] args) {
ArrayList collection1 = new ArrayList();//ArrayList就是动态数组
collection1.add("zs");
collection1.add("zs");
HashSet collection2 = new HashSet();//称为集合,该容器中只能存储不重复的对象。
// 它是基于 HashMap 实现的,底层采用 HashMap 来保存元素
collection2.add("zs");
collection2.add("zs");
System.out.println(collection1);//输出[zs, zs]
System.out.println(collection2);//输出[zs]
}
}
(1)Collection接口的API
1.boolean add(E e):确保此 collection 包含指定的元素(可选操作)
2.boolean remove(Object o):从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。
3.void clear():移除此 collection 中的所有元素(可选操作)。
4.boolean contains(Object o):如果此 collection 包含指定的元素,则返回 true
5.boolean isEmpty():如果此 collection 不包含元素,则返回 true
6.int size():返回此 collection 中的元素数
7.boolean addAll(Collection c)
8.boolean removeAll(Collection c):移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)。
9.boolean containsAll(Collection c):如果此 collection 包含指定 collection 中的所有元素,则返回 true
10.boolean retainAll(Collection c):仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)
11.boolean equals(Object o):比较此 collection 与指定对象是否相等
12. int hashCode() :返回此 collection 的哈希码值
public class CollectionDemo6 {
public static void main(String[] args) {
//添加元素
Collection collection = new ArrayList();
collection.add("ff");
collection.add("ggf");
collection.add("sf");
collection.add("gf");
collection.add("eh");
System.out.println("添加元素:"+collection);
//添加所有元素
Collection collection2 = new ArrayList();
collection2.addAll(collection);
System.out.println("添加所有元素:"+collection2);
//查找是否存在
boolean ff = collection.contains("ff");
boolean ff2 = collection.contains("ffeee");
System.out.println(ff);
System.out.println(ff2);
boolean ff3 = collection.containsAll(collection2);
System.out.println(ff3);
boolean equals = collection.equals(collection2);
System.out.println("查找集合是否存在:"+equals);
//删除一个元素
Collection collection3 = new ArrayList();
collection3.add("sf");
collection3.add("yt");
collection3.add("yd");
System.out.println("输出当前集合:"+collection3);
boolean sf = collection3.remove("sf");
System.out.println("是否删除成功:"+sf);
System.out.println("删除一个元素sf后的集合:"+collection3);
Collection collection4 = new ArrayList();
collection4.add("yt");
collection4.add("yd");
collection3.removeAll(collection4);
System.out.println("删除所有元素后collection3的集合:"+collection3);
System.out.println("collection3的集合:"+collection4);
//保存元素
Collection collection5 = new ArrayList();
collection5.add("eh");
collection5.add("zzz");
collection.retainAll(collection5);
System.out.println("保存元素:"+collection);
//获得所有存储元素的个数
int size = collection.size();
System.out.println("获得所有存储元素的个数:"+size);
//判断是否为空
boolean empty = collection.isEmpty();
System.out.println("判断是否为空:"+empty);
//清空容器中所有元素
collection.clear();
System.out.println("清空容器中所有元素:"+collection);
}
}
结果输出:
添加元素:[ff, ggf, sf, gf, eh]
添加所有元素:[ff, ggf, sf, gf, eh]
true
false
true
查找集合是否存在:true
输出当前集合:[sf, yt, yd]
是否删除成功:true
删除一个元素sf后的集合:[yt, yd]
删除所有元素后collection3的集合:[]
collection3的集合:[yt, yd]
保存元素:[eh]
获得所有存储元素的个数:1
判断是否为空:false
清空容器中所有元素:[]
13.Iterator iterator():迭代器,集合的专用遍历方式
14.Object[] toArray(): 返回包含此 collection 中所有元素的数组
[1] 这个数组足够长:会返回一个和给定长度相等的数组, 剩余空间用null填充;且当做参数的数组, 和返回的数组是等价的。
[2]若这个数组不够长:返回的数组等价于集合类中存储的元素数目;且当做参数的数组, 全是null。
public class CollectionDemo7 {
public static void main(String[] args) {
Collection collection1 = new ArrayList();
collection1.add("zs");
collection1.add("ls");
// Object[] toArray()
// 返回包含此 collection 中所有元素的数组。
//<T> T[]
// toArray(T[] a)
// 返回包含此 collection 中所有元素的数组;返回数组的运行时类型与指定数组的运行时类型相同。
Object[] objs = new Object[10];//创建单位为10的数组
Object[] objects = collection1.toArray(objs);//将collection1集合中的元素放在单位为10的数组中
for (int i = 0; i <objects.length ; i++) {
System.out.println("数组足够长:"+objects[i]);
}
Object[] objs2 = new Object[1];//创建单位为10的数组
Object[] objects2 = collection1.toArray(objs2);//将collection1集合中的元素放在单位为10的数组中
for (int i = 0; i <objects2.length ; i++) {
System.out.println("数组不足够长:"+objects[i]);
}
Object[] objs3 = new Object[1];//创建单位为10的数组
Object[] objects3 = collection1.toArray(objs3);//将collection1集合中的元素放在单位为10的数组中
for (int i = 0; i <objs3.length ; i++) {
System.out.println("数组不足够长(参数数组):"+objs3[i]);
}
}
}
结果输出:
数组足够长:null
数组足够长:null
数组足够长:null
数组足够长:null
数组足够长:null
数组足够长:null
数组足够长:null
数组足够长:null
数组不足够长:zs
数组不足够长:ls
数组不足够长(参数数组):null
(2)Iterator接口(collection集合的遍历)
- 它是对集合进行迭代的迭代器
- 依赖于集合对象存在
- boolean hasNext():如果仍有元素可以迭代,则返回true
- E next():返回迭代的下一个元素
- void remove(): 删除上一次遍历过的元素
public class CollectionDemo8 {
public static void main(String[] args) {
Collection collection1 = new ArrayList();
collection1.add("zs");
collection1.add("ls");
collection1.add("ls");
collection1.add("ls");
collection1.add("ls");
//Tterator iterator() 返回了一个迭代器
//迭代:循环遍历
Iterator iterator = collection1.iterator();
boolean b = iterator.hasNext();//如果仍有元素可以迭代,则
System.out.println("是否仍有元素可以迭代:"+b);
Object next = iterator.next();// 返回迭代的下一个元素
System.out.println("返回迭代的下一个元素:"+next);
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
CollectionDemo8结果输出;
是否仍有元素可以迭代:true
返回迭代的下一个元素:zs
ls
ls
ls
ls
public class CollectionDemo9 {
public static void main(String[] args) {
Collection collection1 = new ArrayList();
collection1.add("zs");
collection1.add("ls");
collection1.add("lh");
collection1.add("aeth");
collection1.add("hsd");
System.out.println(collection1);
Iterator it = collection1.iterator();
//remove()
// 删除上一次遍历(迭代)过的元素,不能连续删除,不能在遍历之前删除
//应用:在遍历过程中,某个元素不符合预期,把这个元素删除
Object next = it.next();
System.out.println("当前迭代的元素:"+next);
it.remove();
System.out.println("删除上一次迭代的元素后:"+collection1);
Object next2 = it.next();
System.out.println("当前迭代的元素:"+next2);
it.remove();
System.out.println("删除上一次迭代的元素后:"+collection1);
}
}
CollectionDemo9结果输出:
[zs, ls, lh, aeth, hsd]
当前迭代的元素:zs
删除上一次迭代的元素后:[ls, lh, aeth, hsd]
当前迭代的元素:ls
删除上一次迭代的元素后:[lh, aeth, hsd]
案例:collection集合存储学生对象并遍历
(3)并发修改异常
ConcurrentModificationException
产生原因:
迭代器遍历的过程中,通过集合对象修改了集合中的元素,造成了迭代器获取元素中判断预期修改值和实际修改值不一致
在iterator迭代中, 通过iterator返回的迭代对象, 会保存原集合数据的修改次数, 并且这个iterator再每次通过遍历的时候, 会重复检查, 保存修改次数, 和此时此刻的源集合类的修改次数是否还一样, 如果不一样, 意味着, 这个源集合类在迭代的过程中, 被别人修改了, 这个iterator就会抛出并发修改异常。
解决方案;
用for循环遍历,然后用集合对象做对应的操作即可
ps:ListIteratror:列表迭代器
可以实现上述功能,而不产生错误
public class CollectionDemo10 {
public static void main(String[] args) {
Collection collection1 = new ArrayList();
collection1.add("zs");
collection1.add("ls");
collection1.add("lh");
collection1.add("aeth");
collection1.add("hsd");
System.out.println(collection1);
//ConcurrentModificationException并发修改异常
//在iterator迭代中, 通过iterator返回的迭代对象, 会保存原集合数据的修改次数,
// 并且这个iterator再每次通过遍历的时候, 会重复检查, 保存修改次数,
// 和此时此刻的源集合类的修改次数是否还一样, 如果不一样, 意味着, 这个源集合类在迭代的过程中,
// 被别人修改了, 这个iterator就会抛出并发修改异常
Iterator it = collection1.iterator();
it.next();//刚刚遍历完it对象代码的集合collection1,
collection1.remove("zs");//马上就删除了collection1中的zs
it.next();//????
//collection1.remove("zs");//解决方案
}
}
结果输出;
改进后;
public class CollectionDemo10 {
public static void main(String[] args) {
Collection collection1 = new ArrayList();
collection1.add("zs");
collection1.add("ls");
collection1.add("lh");
collection1.add("aeth");
collection1.add("hsd");
System.out.println(collection1);
//ConcurrentModificationException并发修改异常
//在iterator迭代中, 通过iterator返回的迭代对象, 会保存原集合数据的修改次数,
// 并且这个iterator再每次通过遍历的时候, 会重复检查, 保存修改次数,
// 和此时此刻的源集合类的修改次数是否还一样, 如果不一样, 意味着, 这个源集合类在迭代的过程中,
// 被别人修改了, 这个iterator就会抛出并发修改异常
Iterator it = collection1.iterator();
it.next();//刚刚遍历完it对象代码的集合collection1,
//collection1.remove("zs");//马上就删除了collection1中的zs
it.next();//????
collection1.remove("zs");//解决方案
}
}
结果输出;
怎么避免抛出并发修改异常:
1.多线程, 没有办法避免,
2.单线程, 迭代的过程还没有迭代结束, 就使用了源集合类的方法修改了源集合类.
注意事项:
1. 用迭代器对集合遍历的时候,不要使用集合的API对集合进行修改
2. 使用集合对象的时候,不要使用while循环,可以使用for循环,最好使用foreach循环。
(4)Foreach循环:
Foreach/加强的for循环/ 增强的for循环
for (Object str : collection1) {
System.out.println(str);
}
foreach循环, 在编译之后, 会转化为iterator迭代
注意:
1, 只有实现iterator方法的集合类,才可以使用foreach循环 (数组)
2, 不要在使用foreach的时候, 调用源集合类的方法修改源集合类(会产生并发修改异常) -> 如果使用foreach循环, 一般仅仅是用来查找元素
循环示例1:
public class CollectionDemo11 {
public static void main(String[] args) {
Collection collection1 = new ArrayList();
collection1.add("zs");
collection1.add("ls");
collection1.add("lh");
collection1.add("aeth");
collection1.add("hsd");
System.out.println(collection1);
Object[] objects = collection1.toArray();
for (int i = 0; i <objects.length ; i++) {
System.out.println("遍历方式一:"+objects[i]);
}
System.out.println("--------");
Iterator iterator = collection1.iterator();
while (iterator.hasNext()){
System.out.println("遍历方式二:"+iterator.next());
}
}
}
结果输出;
[zs, ls, lh, aeth, hsd]
遍历方式一:zs
遍历方式一:ls
遍历方式一:lh
遍历方式一:aeth
遍历方式一:hsd
--------
遍历方式二:zs
遍历方式二:ls
遍历方式二:lh
遍历方式二:aeth
遍历方式二:hsd
循环示例2:
public class CollectionDemo12 {
public static void main(String[] args) {
Collection collection1 = new ArrayList();
collection1.add("zs");
collection1.add("ls");
collection1.add("lh");
collection1.add("aeth");
collection1.add("hsd");
System.out.println(collection1);
for (Object str : collection1) {
System.out.println("加强版for循环:" + str);
}
//在class文件中,实际上就是迭代循环
// Iterator var2 = collection1.iterator();
//
// while(var2.hasNext()) {
// Object str = var2.next();
// System.out.println("加强版for循环:" + str);
// }
String[] strs = {"zs", "ls", "zf"};
for (String str : strs) {
strs[0]=null;//值传递,引用传递?
System.out.println(str);
}
}
}
结果输出;
[zs, ls, lh, aeth, hsd]
加强版for循环:zs
加强版for循环:ls
加强版for循环:lh
加强版for循环:aeth
加强版for循环:hsd
zs
ls
zf
Collection 小结:
Collection不同实现子类, 底层机构不一样 -> 每一个实现子类的遍历方式必然不一样
1.1List及其子类
线性表:n个数据元素的有序序列。
1.首先,线性表中元素的个数是有限的。
2.其次,线性表中元素是有序的。
List接口概述:
有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
与 set 不同,列表通常允许重复的元素。更确切地讲,列表通常允许满足 e1.equals(e2) 的元素对 e1 和 e2,并且如果列表本身允许 null 元素的话,通常它们允许多个 null 元素。
List接口API:
1.void add(int index,E element): 在列表的指定位置插入指定元素(可选操作)
2.E remove(int index):移除列表中指定位置的元素(可选操作)。
3.E get(int index): E get(int index)
返回列表中指定位置的元素。
4.E set(int index,E element):用指定元素替换列表中指定位置的元素(可选操作)。
5.ListIterator listIterator():返回此列表元素的列表迭代器(按适当顺序)
ListIterator listIterator(int index) :返回列表中元素的列表迭代器(按适当顺序),从列表的指定位置开始。
6.boolean hasPrevious()
7.E previous()
8.boolean addAll(int index, Collection<? extends E> c) :将指定 collection 中的所有元素都插入到列表中的指定位置(可选操作)
9. int indexOf(Object o):返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。
10. int lastIndexOf(Object o) :返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。
11. List subList(int fromIndex, int toIndex):返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图
public class ListDemo02 {
public static void main(String[] args) {
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
//void add(int index,E element):在此集合中的指定位置插入指定的元素
// list.add(1,"javaee");
//IndexOutOfBoundsException
// list.add(11,"javaee");
//E remove(int index):删除指定索引处的元素,返回被删除的元素
// System.out.println(list.remove(1));
//IndexOutOfBoundsException
// System.out.println(list.remove(11));
//E set(int index,E element):修改指定索引处的元素,返回被修改的元素
// System.out.println(list.set(1,"javaee"));
//IndexOutOfBoundsException
// System.out.println(list.set(11,"javaee"));
//E get(int index):返回指定索引处的元素
// System.out.println(list.get(1));
//IndexOutOfBoundsException
// System.out.println(list.get(11));
//输出集合对象
// System.out.println(list);
//遍历集合
// System.out.println(list.get(0));
// System.out.println(list.get(1));
// System.out.println(list.get(2));
//用for循环改进遍历
for (int i=0; i<list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
}
}
案例:List集合存储学生对象并遍历
Student
public class Student {
private String name;
private int age;
public Student() {
}
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;
}
}
ListDemo
public class ListDemo {
public static void main(String[] args) {
//创建List集合对象
List<Student> list = new ArrayList<Student>();
//创建学生对象
Student s1 = new Student("林青霞", 30);
Student s2 = new Student("张曼玉", 35);
Student s3 = new Student("王祖贤", 33);
//把学生添加到集合
list.add(s1);
list.add(s2);
list.add(s3);
//迭代器方式
Iterator<Student> it = list.iterator();
while (it.hasNext()) {
Student s = it.next();
System.out.println(s.getName() + "," + s.getAge());
}
System.out.println("--------");
//for循环方式
for(int i=0; i<list.size(); i++) {
Student s = list.get(i);
System.out.println(s.getName() + "," + s.getAge());
}
}
}
ListIteratror:列表迭代器
Listiterator 是 iterator的子接口, 功能上是iterator的增强(可沿任意方向遍历列表的迭代器), 它提供了更丰富的api
1.void add(E e) :将指定的元素插入列表(可选操作)
2. boolean hasNext() :以正向遍历列表时,如果列表迭代器有多个元素,则返回 true(换句话说,如果 next 返回一个元素而不是抛出异常,则返回 true)。
3. boolean hasPrevious() :如果以逆向遍历列表,列表迭代器有多个元素,则返回 true。
4. E next() :返回列表中的下一个元素
5. int nextIndex() :返回对 next 的后续调用所返回元素的索引
6. E previous():返回列表中的前一个元素。
7. int previousIndex() : 返回对 previous 的后续调用所返回元素
8. void remove() : 从列表中移除由 next 或 previous 返回的最后一个元素(可选操作)。
9. void set(E e) :用指定元素替换 next 或 previous 返回的最后一个元素(可选操作)。
public class ListIteratorDemo {
public static void main(String[] args) {
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
//通过List集合的listIterator()方法得到
// ListIterator<String> lit = list.listIterator();
// while (lit.hasNext()) {
// String s = lit.next();
// System.out.println(s);
// }
// System.out.println("--------");
//
// while (lit.hasPrevious()) {
// String s = lit.previous();
// System.out.println(s);
// }
//获取列表迭代器
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
String s = lit.next();
if(s.equals("world")) {//不会产生并发修改异常
lit.add("javaee");//最后的实际修改值会赋值给预期修改值
}
}
System.out.println(list);
}
}
List集合存储学生对象三种方式遍历
Student
public class Student {
private String name;
private int age;
public Student() {
}
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;
}
}
ListDemo
public class ListDemo {
public static void main(String[] args) {
//创建List集合对象
List<Student> list = new ArrayList<Student>();
//创建学生对象
Student s1 = new Student("林青霞", 30);
Student s2 = new Student("张曼玉", 35);
Student s3 = new Student("王祖贤", 33);
//把学生添加到集合
list.add(s1);
list.add(s2);
list.add(s3);
//迭代器:集合特有的遍历方式
Iterator<Student> it = list.iterator();
while (it.hasNext()) {
Student s = it.next();
System.out.println(s.getName()+","+s.getAge());
}
System.out.println("--------");
//普通for:带有索引的遍历方式
for(int i=0; i<list.size(); i++) {
Student s = list.get(i);
System.out.println(s.getName()+","+s.getAge());
}
System.out.println("--------");
//增强for:最方便的遍历方式
for(Student s : list) {
System.out.println(s.getName()+","+s.getAge());
}
}
}
List的子类(ArrayList、Vector、LinkedList)
-
ArrayList
底层数据结构是数组,查询快,增删慢
线程不安全,效率高
-
Vector
底层数据结构是数组,查询快,增删慢;
线程安全,效率低;
Vector 特有的API;
public void addElement(E obj)
public E elementAt(int index)
public Enumeration elements() -
LinkedList
底层数据结构是链表,查询慢,增删快
线程不安全,效率高
LinkedList 特有的API
① public void addFirst(E e)及addLast(E e)
② public E getFirst()及getLast()
③ public E removeFirst()及public E removeLast()
练习:
去重
请用ArrayList实现栈数据结构,并测试。
3. 集合的嵌套遍历
4. 获取10个1-20之间的随机整数,要求集合中的数不能重复。
1.2 Set及其子类
Set 概述
一个不包含重复元素的 collection。更确切地讲, set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2, 并且最多包含一个 null 元素。
注意事项:Set 集合并不一定都是无序的,有些 Set 集合是有序的。
(1)HashSet 概述
- 底层是HashMap
- 不保证迭代顺序,更不保证该顺序恒久不变 (无序的)
- 允许存储 null 元素
- 不同步
HashSet 的元素具有唯一性(Set 存储的元素是作为 Map 的 key,而Map的key是唯一的)
HashSet 依赖于存储元素的两个方法: int hashCode() & boolean equals(Object obj)
注意事项:千万不要修改 HashSet 元素的属性值!
public class CollectionDemo5 {
public static void main(String[] args) {
ArrayList collection1 = new ArrayList();//ArrayList就是动态数组
collection1.add("zs");
collection1.add("ls");
collection1.add("wu");
collection1.add("z1");
collection1.add("1");
Collection collection2 = new HashSet();//HashSet添加数据是随机加入
collection2.add("zs");
collection2.add("ls");
collection2.add("wu");
collection2.add("z1");
collection2.add("1");
collection2.add("有");
System.out.println(collection1);//输出[zs, ls, wu, z1, 1]
System.out.println(collection2);//输出[1, ls, z1, zs, 有, wu],不按顺序
}
}
HashSet集合存储学生对象并遍历
(2)LinkedHashSet 概述
- HashSet 的子类
- 底层是HashMap & 双向链表
- HashMap 保证了元素的唯一性
- 链表定义了迭代的顺序,按照元素的插入顺序进行迭代
- 不同步
(3)TreeSet 概述
自然排序Comparable的使用
Student
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;
}
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;
}
@Override
public int compareTo(Student s) {
// return 0;
// return 1;
// return -1;
//按照年龄从小到大排序
int num = this.age - s.age;
// int num = s.age - this.age;
//年龄相同时,按照姓名的字母顺序排序
int num2 = num==0?this.name.compareTo(s.name):num;
return num2;
}
}
TreeSetDemo02
public class TreeSetDemo02 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<Student>();
//创建学生对象
Student s1 = new Student("xishi", 29);
Student s2 = new Student("wangzhaojun", 28);
Student s3 = new Student("diaochan", 30);
Student s4 = new Student("yangyuhuan", 33);
Student s5 = new Student("linqingxia",33);
Student s6 = new Student("linqingxia",33);
//把学生添加到集合
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//遍历集合
for (Student s : ts) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
比较器排序Comparable的使用
Student
public class Student {
private String name;
private int chinese;
private int math;
public Student() {
}
public Student(String name, int chinese, int math) {
this.name = name;
this.chinese = chinese;
this.math = math;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getChinese() {
return chinese;
}
public void setChinese(int chinese) {
this.chinese = chinese;
}
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
public int getSum() {
return this.chinese + this.math;
}
}
public class TreeSetDemo {
public static void main(String[] args) {
//创建TreeSet集合对象,通过比较器排序进行排序
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// int num = (s2.getChinese()+s2.getMath())-(s1.getChinese()+s1.getMath());
//主要条件
int num = s2.getSum() - s1.getSum();
//次要条件
int num2 = num == 0 ? s1.getChinese() - s2.getChinese() : num;
int num3 = num2 == 0 ? s1.getName().compareTo(s2.getName()) : num2;
return num3;
}
});
//创建学生对象
Student s1 = new Student("林青霞", 98, 100);
Student s2 = new Student("张曼玉", 95, 95);
Student s3 = new Student("王祖贤", 100, 93);
Student s4 = new Student("柳岩", 100, 97);
Student s5 = new Student("风清扬", 98, 98);
Student s6 = new Student("左冷禅", 97, 99);
// Student s7 = new Student("左冷禅", 97, 99);
Student s7 = new Student("赵云", 97, 99);
//把学生对象添加到集合
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
ts.add(s7);
//遍历集合
for (Student s : ts) {
System.out.println(s.getName() + "," + s.getChinese() + "," + s.getMath() + "," + s.getSum());
}
}
}
- TreeSet 底层的数据结构是 TreeMap, TreeMap的底层是红黑树
- 若创建对象时,传入了 Comparator 对象,根据 Comparator 进行排序,否则根据自然顺排序。
- 不能存储 null 元素,除非在Comparator中定义的null的比较规则
- 不同步
TreeSet 的元素具有唯一性(依赖于存储元素的 compareTo(obj) 方法,或者是传入的 Comparator 对象的 compare(o1, o2) 方法)
注意事项:千万不要修改 TreeSet 元素的属性值!
除Set接口中定义的方法外,由于TreeSet 中的元素是大小有序的,因此它还有一些特殊的方法。
1.E first();
2.E last();
3.E pollFirst();
4.E pollLast();
5.E ceiling(E e);
6.E floor(E e);
7.E higher(E e)
8.E lower(E e)
9.NavigableSet subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive);
练习:
键盘录入5个学生信息 (姓名,语文成绩,数学成绩,英语成绩),并按照指定的格式,总分从高到低,输出到文件
2.Map及其子类
Map接口概述
- 将键映射到值的对象。(我们可以根据键快速地查找到值)
- Map 中键是唯一的 (不能包含重复的键)
- 每个键最多只能映射到一个值
Map接口API
- V get(Object key):返回指定key所对应的value:如果此Map中不包呈该key,则返回null
public class project02 {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("赵丽颖", 17);
map.put("杨幂", 18);
map.put("林志玲", 19);
map.put("小龙女", 20);
System.out.println(map);
System.out.println("关于get的验证:");
Integer v1 = map.get("赵丽颖");
Integer v2 = map.get("小蝴蝶");
System.out.println("v1:"+v1);
System.out.println("v2:"+v2);
System.out.println(map);
System.out.println("关于containsKey的验证:");
boolean b1 = map.containsKey("赵丽颖");
boolean b2 = map.containsKey("小蝴蝶");
System.out.println("b1:"+b1);
System.out.println("b2:"+b2);
System.out.println(map);
}
}
结果输出:
{林志玲=19, 赵丽颖=17, 小龙女=20, 杨幂=18}
关于get的验证:
v1:17
v2:null
{林志玲=19, 赵丽颖=17, 小龙女=20, 杨幂=18}
关于containsKey的验证:
b1:true
b2:false
{林志玲=19, 赵丽颖=17, 小龙女=20, 杨幂=18}
- V put(K key,V value):添加一个key-value对,如果当前Map中已有一个与该key相等的key-value对,则新的key-value对会覆盖原来的key-value对,返回被替换的value值。若key不重复,返回null
public class project01 {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
String v1=map.put("李晨","范冰冰1");
System.out.println("v1:"+v1);
System.out.println(map);
String v2=map.put("李晨","范冰冰2");
System.out.println("v2:"+v2);
System.out.println(map);
String v3=map.put("杨过","小龙女");
String v4=map.put("高富帅","小龙女");
System.out.println(map);
}
}
结果输出:
v1:null
{李晨=范冰冰1}
v2:范冰冰1
{李晨=范冰冰2}
{高富帅=小龙女, 杨过=小龙女, 李晨=范冰冰2}
- V remove(Object key):删除指定key所对应的key-value对,返回被删除key所关联的value,如果该key不存在,则返回null
public class project02 {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("赵丽颖", 17);
map.put("杨幂", 18);
map.put("林志玲", 19);
map.put("小龙女", 20);
System.out.println(map);
Integer v1 = map.remove("赵丽颖");
Integer v2 = map.remove("小蝴蝶");
System.out.println("v1:"+v1);
System.out.println("v2:"+v2);
System.out.println(map);
}
}
结果输出:
{林志玲=19, 赵丽颖=17, 小龙女=20, 杨幂=18}
v1:17
v2:null
{林志玲=19, 小龙女=20, 杨幂=18}
- void clear()
- boolean containsKey(Object key)
- boolean containsValue(Object value)
- boolean isEmpty()
- int size()
- Set keySet()
/**
* @Author:gaoyuan
* @Description:map集合第一种遍历方式:通过键找值的方式 实现步骤;
* 1.使用map集合中的方法keyset(),把map集合所有的key取出来,存储到一个set集合中
* 2.遍历set集合,获取map集合中的每一个key
* 3.通过map集合中的方法get(key),通过key找到value
* @DateTime:2021/2/13 16:23
**/
public class project03 {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("赵丽颖", 17);
map.put("杨幂", 18);
map.put("林志玲", 19);
map.put("小龙女", 20);
System.out.println(map);
System.out.println("关于keyset的验证:");
// 1.使用map集合中的方法keyset(),把map集合所有的key取出来,存储到一个set集合中
Set<String> set = map.keySet();
//2.遍历set集合,获取map集合中的每一个key
Iterator<String> it = set.iterator();
//3.通过map集合中的方法get(key),通过key找到value
while (it.hasNext()) {
String key = it.next();
Integer value = map.get(key);
System.out.println(key + "=" + value);
}
System.out.println("______________________");
for (String key : set) {
Integer value = map.get(key);
System.out.println(key + "=" + value);
}
}
}
结果输出:
{林志玲=19, 赵丽颖=17, 小龙女=20, 杨幂=18}
关于keyset的验证:
林志玲=19
赵丽颖=17
小龙女=20
杨幂=18
______________________
林志玲=19
赵丽颖=17
小龙女=20
杨幂=18
- Collection values()
- Set<Map.Entry<K,V>> entrySet()
/**
* @Author:gaoyuan
* @Description:map集合第二种遍历方式:把map集合中多个Entry对象取出来,存储到一个set集合中 实现步骤;
* 1.使用map集合中的方法entrySet(),把map集合中多个entry对象取出来,存储到一个set集合中
* 2.遍历set集合,获取map集合中的每一个entry对象
* 3.通过map集合中的方法getKey()和getValue()获取键与值
* @DateTime:2021/2/13 16:23
**/
public class project04 {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("赵丽颖", 17);
map.put("杨幂", 18);
map.put("林志玲", 19);
map.put("小龙女", 20);
System.out.println(map);
System.out.println("关于getKey()的验证:");
//1.使用map集合中的方法entrySet(),把map集合中多个entry对象取出来,存储到一个set集合中
Set<Map.Entry<String, Integer>> set = map.entrySet();
Iterator<Map.Entry<String, Integer>> it = set.iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> next = it.next();
String key = next.getKey();
Integer value = next.getValue();
System.out.println(key + "=" + value);
}
System.out.println("-------------------" );
for (Map.Entry<String,Integer> entry:set){
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + "=" + value);
}
}
}
结果输出:
{林志玲=19, 赵丽颖=17, 小龙女=20, 杨幂=18}
关于getKey()的验证:
林志玲=19
赵丽颖=17
小龙女=20
杨幂=18
-------------------
林志玲=19
赵丽颖=17
小龙女=20
杨幂=18
Map集合的获取功能
Map集合的遍历
方式一
方式二
案例:ArrayList集合存储HashMap元素并遍历
public class ArrayListIncludeHashMapDemo {
public static void main(String[] args) {
//创建ArrayList集合
ArrayList<HashMap<String, String>> array = new ArrayList<HashMap<String, String>>();
//创建HashMap集合,并添加键值对元素
HashMap<String, String> hm1 = new HashMap<String, String>();
hm1.put("孙策", "大乔");
hm1.put("周瑜", "小乔");
//把HashMap作为元素添加到ArrayList集合
array.add(hm1);
HashMap<String, String> hm2 = new HashMap<String, String>();
hm2.put("郭靖", "黄蓉");
hm2.put("杨过", "小龙女");
//把HashMap作为元素添加到ArrayList集合
array.add(hm2);
HashMap<String, String> hm3 = new HashMap<String, String>();
hm3.put("令狐冲", "任盈盈");
hm3.put("林平之", "岳灵珊");
//把HashMap作为元素添加到ArrayList集合
array.add(hm3);
//遍历ArrayList集合
for (HashMap<String, String> hm : array) {
Set<String> keySet = hm.keySet();
for (String key : keySet) {
String value = hm.get(key);
System.out.println(key + "," + value);
}
}
}
}
案例:HashMap集合存储ArrayList元素并遍历
public class HashMapIncludeArrayListDemo {
public static void main(String[] args) {
//创建HashMap集合
HashMap<String, ArrayList<String>> hm = new HashMap<String, ArrayList<String>>();
//创建ArrayList集合,并添加元素
ArrayList<String> sgyy = new ArrayList<String>();
sgyy.add("诸葛亮");
sgyy.add("赵云");
//把ArrayList作为元素添加到HashMap集合
hm.put("三国演义",sgyy);
ArrayList<String> xyj = new ArrayList<String>();
xyj.add("唐僧");
xyj.add("孙悟空");
//把ArrayList作为元素添加到HashMap集合
hm.put("西游记",xyj);
ArrayList<String> shz = new ArrayList<String>();
shz.add("武松");
shz.add("鲁智深");
//把ArrayList作为元素添加到HashMap集合
hm.put("水浒传",shz);
//遍历HashMap集合
Set<String> keySet = hm.keySet();
for(String key : keySet) {
System.out.println(key);
ArrayList<String> value = hm.get(key);
for(String s : value) {
System.out.println("\t" + s);
}
}
}
}
案例:统计字符串中每个字符出现的次数
public class HashMapDemo {
public static void main(String[] args) {
//键盘录入一个字符串
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String line = sc.nextLine();
//创建HashMap集合,键是Character,值是Integer
// HashMap<Character, Integer> hm = new HashMap<Character, Integer>();
TreeMap<Character, Integer> hm = new TreeMap<Character, Integer>();
//遍历字符串,得到每一个字符
for (int i = 0; i < line.length(); i++) {
char key = line.charAt(i);
//拿得到的每一个字符作为键到HashMap集合中去找对应的值,看其返回值
Integer value = hm.get(key);
if (value == null) {
//如果返回值是null:说明该字符在HashMap集合中不存在,就把该字符作为键,1作为值存储
hm.put(key,1);
} else {
//如果返回值不是null:说明该字符在HashMap集合中存在,把该值加1,然后重新存储该字符和对应的值
value++;
hm.put(key,value);
}
}
//遍历HashMap集合,得到键和值,按照要求进行拼接
StringBuilder sb = new StringBuilder();
Set<Character> keySet = hm.keySet();
for(Character key : keySet) {
Integer value = hm.get(key);
sb.append(key).append("(").append(value).append(")");
}
String result = sb.toString();
//输出结果
System.out.println(result);
}
}
HashMap概述
- 基于哈希表的Map接口实现
- 允许null键和null值
- 不保证映射的顺序,特别是它不保证该顺序恒久不变
- 不同步
存储自定义类型键值:
- Map集合保证key是唯一的:
- 作为key的元素,必须重写hashcode方法和equals方法,以保证key唯一
Person.java(还未重写hashcode方法和equals)
public class Person {
private String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
HashMapDemo.java
/**
* @Author:gaoyuan
* @Description:HashMap存储自定义类型键值
* Map集合保证key是唯一的:
* 作为key的元素,必须重写hashcode方法和equals方法,以保证key唯一
* @DateTime:2021/2/13 21:24
**/
public class HashMapDemo {
public static void main(String[] args) {
show01();
}
/**
*
* HashMap存储自定义类型键值
*key:string类,重写hashcode方法和equals方法,可保证key唯一
*value:Interge类,可重复
*/
private static void show01(){
HashMap<String, Person> map = new HashMap<>();
map.put("北京",new Person("大娃",100));//key值与后面一样,新的value值覆盖前面的value值。重写了hashcode与equals
map.put("成都",new Person("二娃",99));
map.put("南京",new Person("三娃",98));
map.put("北京",new Person("四娃",97));
System.out.println(map);
//使用keyset+增强for遍历map集合
Set<String> set = map.keySet();
for (String key:set){
Person value = map.get(key);
System.out.println(key+"-->"+value);
}
HashMap<Person,String> map2 = new HashMap<>();
//往集合中添加元素
map2.put(new Person("女王",18),"英国");//没有重写hashcode方法和equals方法,key不唯一
map2.put(new Person("秦始皇",19),"秦国");
map2.put(new Person("普京",20),"俄罗斯");
map2.put(new Person("女王",18),"漂亮国");
Set<Map.Entry<Person, String>> entries = map2.entrySet();
for (Map.Entry<Person,String> entrie:entries){
Person key = entrie.getKey();
String value = entrie.getValue();
System.out.println(key + "=" + value);
}
}
}
结果输出:女王出现重复
{成都=Person{name='二娃', age=99}, 北京=Person{name='四娃', age=97}, 南京=Person{name='三娃', age=98}}
成都-->Person{name='二娃', age=99}
北京-->Person{name='四娃', age=97}
南京-->Person{name='三娃', age=98}
Person{name='普京', age=20}=俄罗斯
Person{name='秦始皇', age=19}=秦国
Person{name='女王', age=18}=英国
Person{name='女王', age=18}=漂亮国
Person重写后:
public class Person {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return getAge() == person.getAge() &&
Objects.equals(getName(), person.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getAge());
}
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
结果输出:
{成都=Person{name='二娃', age=99}, 北京=Person{name='四娃', age=97}, 南京=Person{name='三娃', age=98}}
成都-->Person{name='二娃', age=99}
北京-->Person{name='四娃', age=97}
南京-->Person{name='三娃', age=98}
Person{name='女王', age=18}=漂亮国
Person{name='秦始皇', age=19}=秦国
Person{name='普京', age=20}=俄罗斯
LinkedHashMap概述
- HashMap的子类
- Map 接口的哈希表和链表实现,具有可预知的迭代顺序
- 链表定义了迭代顺序,该迭代顺序就是键值对的插入顺序
- 不同步
public class project05 {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
map.put("a","a");
map.put("c","c");
map.put("b","b");
map.put("a","z");
System.out.println("HashMap是无序输出:"+map);
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("a","a");
linkedHashMap.put("c","c");
linkedHashMap.put("b","b");
linkedHashMap.put("a","z");
System.out.println("LinkedHashMap是有序输出:"+linkedHashMap);
}
}
结果输出:
HashMap是无序输出:{a=z, b=b, c=c}
LinkedHashMap是有序输出:{a=z, c=c, b=b}
TreeMap概述
- 底层的数据结构是红黑树
- 如果创建对象时,传入了 Comparator 对象,键将按 Comparator 进行排序,否则键将按自然顺序进行排序。
- 不同步
除了Map接口中定义的方法外,由于TreeMap中的键是大小有序的,因此它还有一些特殊的方法。
1.K firstKey()
2.K lastKey()
3.Map.Entry<K, V> pollFirstEntry();
4.Map.Entry<K,V> pollLastEntry();
5.K floorKey(K key)
6.K ceilingKey(K key)
7.K lowerKey(K key)
8.K higherKey(K key)
9.NavigableMap<K, V> submap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive )
Properties概述
- Hashtable<Object, Object> 的子类
- Properties 类表示了一个可持久的属性集
- Properties 可保存在流中或从流中加载
- Properties 中每个键及其对应值都是一个字符串
注意事项:不要使用Hashtable里面定义的方法添加键值对!因为它们可以插入不是String 类型的数据。
如果一个Properties中含有非String的键值对,那么这样的Properties是”不安全”的。调用 store 或者 save 方法将失败
Properties 的 API:
- String getProperty(String key)
- String getProperty(String key, String defaultValue)
- Object setProperty(String key, String value)
- Set stringPropertyNames();
- void store(OutputStream out, String comments)
- void store(Writer out, String comments)
- void load(InputStream inStream)
- void load(Reader read)
注意事项:字节流默认使用 ISO 8859-1 字符编码。
练习:
-
“aababcabcdabcde”,获取字符串中每一个字母出现的次数要求结果:a(5)b(4)c(3)d(2)e(1)
-
给定一个整数数组和一个目标值,找出数组中和为目标值的两个数, 返回它们的索引。
你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。
比如:nums = [2, 7, 11, 15], target = 9.
因为 nums[0] + nums[1] = 2 + 7 = 9. 所以返回 [0, 1].
- 请设计一个猜数字小游戏,可以试玩5次。试玩结束之后,给出提示:游戏试玩结束,
请付费。
Map 小结:
3.JDK1.5新特性
(1)泛型的引入及好处:
a. 提高了程序的安全性
b. 将运行期遇到的问题转移到了编译期
c. 省去了类型强转的麻烦
/*
需求:Collection集合存储字符串并遍历
*/
public class GenericDemo {
public static void main(String[] args) {
//创建集合对象
// Collection c = new ArrayList();
//提前将集合类型设定为<String>类型,将运行期可能遇到的问题转移到了编译期
Collection<String> c = new ArrayList<String>();
//添加元素
c.add("hello");
c.add("world");
c.add("java");
// c.add(100);
//遍历集合
// Iterator it = c.iterator();
Iterator<String> it = c.iterator();//用.var,自动变为Iterator<String>
while (it.hasNext()) {
// Object obj = it.next();//刚开始没有指定类型,用Object表示类型
// System.out.println(obj);
// String s = (String)it.next(); //ClassCastException
String s = it.next();//泛型指定类型后,不用强转类型
System.out.println(s);
}
}
}
结果输出:
hello
world
java
泛型应用
泛型类(把泛型定义在类上)
格式:public class 类名<泛型类型1,…>
注意:参数化类型必须是引用类型
示例1:
Student.java
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Teacher.java
public class Teacher {
private Integer age;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
Generic.java
public class Generic<T>{
private T t;//类型为T类型
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
GenericDemo.java
/*
测试类
*/
public class GenericDemo {
public static void main(String[] args) {
Student s = new Student();
s.setName("林青霞");
System.out.println(s.getName());
Teacher t = new Teacher();
t.setAge(30);
// t.setAge("30");
System.out.println(t.getAge());
System.out.println("--------");
Generic<String> g1 = new Generic<String>();//创建一个String类型
g1.setT("林青霞");
System.out.println(g1.getT());
Generic<Integer> g2 = new Generic<Integer>();//创建一个Integer类型
g2.setT(30);
System.out.println(g2.getT());
Generic<Boolean> g3 = new Generic<Boolean>();//创建一个Boolean类型
g3.setT(true);
System.out.println(g3.getT());
}
}
结果输出:
林青霞
30
--------
林青霞
30
true
示例2:
方法的一般调用:
Generic.java
public class Generic {
public void show(String s){
System.out.println(s);
}
public void show(Integer i){
System.out.println(i);
}
public void show(Boolean b){
System.out.println(b);
}
}
GenericDemo.java
public class GenericDemo {
public static void main(String[] args) {
Generic g = new Generic();
g.show("林青霞");
g.show(30);
g.show(true);
}}
结果输出;
林青霞
30
true
用泛型类改进:(方法代码较简介,测试类中代码多次创建,较复杂)
Generic.java
public class Generic<T> {
public void show(T t){
System.out.println(t);
}
}
GenericDemo.java
public class GenericDemo {
public static void main(String[] args) {
Generic<String> g1 = new Generic<String>();
g1.show("林青霞");
Generic<Integer> g2 = new Generic<Integer>();
g2.show(30);
Generic<Boolean> g3 = new Generic<Boolean>();
g3.show(true);
}
}
结果输出;
林青霞
30
true
泛型方法:(把泛型定义在方法上)
格式:public <泛型类型> 返回类型 方法名(泛型类型 .)
示例2:
用泛型方法改进:(方法代码和测试类代码都较简介)
Generic.java
public class Generic {
public <T> void show(T t) {
System.out.println(t);
}
}
GenericDemo.java
public class GenericDemo {
public static void main(String[] args) {
Generic g = new Generic();
g.show("林青霞");
g.show(30);
g.show(true);
g.show(23.676);
}
}
结果输出;
林青霞
30
true
23.676
泛型接口:(把泛型定义在接口上)
格式:public interface 接口名<泛型类型1…>
Generic.java(接口)
public interface Generic<T> {
void show(T t);
}
GenericImpl.java(接口的实现类)
public class GenericImpl<T> implements Generic<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
GenericDemo.java
public class GenericDemo {
public static void main(String[] args) {
GenericImpl<String> g1 = new GenericImpl<String>();
g1.show("林青霞");
GenericImpl<Integer> g2 = new GenericImpl<Integer>();
g2.show(30);
}
}
结果输出;
林青霞
30
泛型通配符
① 泛型通配符<?>
任意类型,如果没有明确,那么就是Object以及任意的Java类了
②? extends E
向下限定,E及其子类
③ ? super E
向上限定,E及其父类
GenericDemo.java
public class GenericDemo {
public static void main(String[] args) {
//父子类关系Object>Number>Integer
List<?> list1 = new ArrayList<Object>();
List<?> list2 = new ArrayList<Number>();
List<?> list3 = new ArrayList<Integer>();
System.out.println("------------");
//? extends E
//类型通配符上限,向下拓展,E及其子类
//List<? extends Number> list4 = new ArrayList<Object>();//此行报错,Object是Number的父类
List<? extends Number> list5 = new ArrayList<Number>();
List<? extends Number> list6 = new ArrayList<Integer>();
//? super E
//类型通配符下限,向上拓展,E及其父类
List<? super Number> list7 = new ArrayList<Object>();
List<? super Number> list8 = new ArrayList<Number>();
//List<? super Number> list9 = new ArrayList<Integer>();//此行报错,Integer是Number的子类
}
}
泛型的注意事项
1.泛型 jdk 1.5:
List list = new ArrayList();
jdk1.7 改进:
List list = new ArrayList<>();
List list = new ArrayList ();
没有传泛型 --> 如果我们设计一个类,( ArrayList)他是需要泛型的。但是, 在创建对象的时候, 我们又没有给定泛型, 那么对于这个被创建的对象, 它的泛型到底是什么那? 是Object类型替代
2.泛型一般用于哪些地方?
集合类, 数据容器
3.泛型方法
使用泛型的方法, 并不一定是泛型方法
泛型方法: 在方法上定义了泛型的方法
4.泛型必须使用引用类型
泛型不能使用基本类型.
集合类: 集合类之中是带有泛型的设计:
a. 只能存储引用数据类型(实际上是泛型特点一个延伸)
b. 可以自动地调整自己的大小
5.泛型的参数写法
T: type 类型
E: element 元素
K: key 键值对的键
V: value 键值对的值
6.泛型的擦除
所有带泛型的代码, 在编译完成之后, 泛型完全就不存在了, 全部替换成Object泛型仅存在于编译之前
(2)增强for循环(foreach)
增强for概述:简化数组和Collection集合的遍历
格式:
for(元素数据类型 变量 : 数组或者Collection集合) {
使用变量即可,该变量就是元素
}
好处:简化遍历
注意事项:增强for的目标要判断是否为null
import java.util.ArrayList;
import java.util.List;
/*
增强for:简化数组和Collection集合的遍历
实现Iterable接口的类允许其对象成为增强型 for语句的目标
它是JDK5之后出现的,其内部原理是一个Iterator迭代器
格式:
for(元素数据类型 变量名 : 数组或者Collection集合) {
//在此处使用变量即可,该变量就是元素
}
*/
public class ForDemo {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
for(int i : arr) {
System.out.println(i);
}
System.out.println("--------");
String[] strArray = {"hello","world","java"};
for(String s : strArray) {
System.out.println(s);
}
System.out.println("--------");
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
for(String s : list) {
System.out.println(s);
}
System.out.println("--------");
//内部原理是一个Iterator迭代器
/*
for(String s : list) {
if(s.equals("world")) {
list.add("javaee"); //ConcurrentModificationException
}
}
*/
}
}
(3)可变长参数
可变参数概述:定义方法的时候不知道该定义多少个参数,我们就可以使用可变长参数。
格式:
修饰符 返回值类型 方法名(数据类型… 变量名){}
注意:
1.这里的变量其实是一个数组
2.可变长参数只能位于最后
3.一个方法不能有多个可变长参数
示例;
用可变参数实现加法操作
public class ArgsDemo01 {
public static void main(String[] args) {
System.out.println(sum(10, 20));
System.out.println(sum(10, 20, 30));
System.out.println(sum(10, 20, 30, 40));
System.out.println(sum(10, 20, 30, 40, 50));
System.out.println(sum(10, 20, 30, 40, 50, 60));
System.out.println(sum(10, 20, 30, 40, 50, 60, 70));
System.out.println(sum(10, 20, 30, 40, 50, 60, 70, 80, 90, 100));
}
public static int sum(int... a) {
int sum = 0;
for (int i : a) {
sum += i;
}
return sum;
}
}
结果输出:
20
50
90
140
200
270
540
(4)可变长参数的使用:
Arrays工具类中有一个静态方法:
- public static List asList(T… a):返回由指定数组支持的固定大小的列表
- 返回的集合只能做修改操作
示例:
public class ArgsDemo02 {
public static void main(String[] args) {
List<String> list= Arrays.asList("hello","world","java");
//list.add("javase");//UnsupportedOperationException,因为添加改变了原数组的长度
// list.remove("hello");//UnsupportedOperationException,因为删除改变了原数组的长度
list.set(1,"FSGDvc");//修改元素不改变数组的大小
System.out.println(list);
}
}
结果输出:
[hello, FSGDvc, java]
List接口中有一个静态方法:
- public static List of(E… elements):返回包含任意数量元素的不可变列表
- 返回的集合不能做增删改操作
示例:
public class ArgsDemo03 {
public static void main(String[] args) {
List<String> list = List.of("hello", "world", "java", "world");
list.add("javaee");//UnsupportedOperationException
list.remove("java");//UnsupportedOperationException
list.set(1,"javaee");//UnsupportedOperationException
System.out.println(list);
}
}
结果输出:
hello
world
java
world
Set接口中有一个静态方法:
- public static Set of(E… elements) :返回一个包含任意数量元素的不可变集合
- 在给元素的时候,不能重复。
- 返回的集合不能做增删操作,没有修改的方法
示例:
public class ArgsDemo03 {
public static void main(String[] args) {
List<String> list = List.of("hello", "world", "java", "world");//IllegalArgumentException
list.add("javaee");//UnsupportedOperationException
list.remove("java");//UnsupportedOperationException
list.set(1,"javaee");
System.out.println(list);
}
}
4.Collections
public class CollectionsDemo01 {
public static void main(String[] args) {
//创建集合对象
List<Integer> list = new ArrayList<Integer>();
//添加元素
list.add(30);
list.add(20);
list.add(50);
list.add(10);
list.add(40);
//public static <T extends Comparable<? super T>> void sort(List<T> list):将指定的列表按升序排序
Collections.sort(list);
System.out.println(list);
//public static void reverse(List<?> list):反转指定列表中元素的顺序
Collections.reverse(list);
System.out.println(list);
//public static void shuffle(List<?> list):使用默认的随机源随机排列指定的列表
Collections.shuffle(list);
System.out.println(list);
}
}
案例:ArrayList集合存储学生并排序
Student
public class Student {
private String name;
private int age;
public Student() {
}
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 class CollectionsDemo02 {
public static void main(String[] args) {
//创建ArrayList集合对象
ArrayList<Student> array = new ArrayList<Student>();
//创建学生对象
Student s1 = new Student("linqingxia", 30);
Student s2 = new Student("zhangmanyu", 35);
Student s3 = new Student("wangzuxian", 33);
Student s4 = new Student("liuyan", 33);
//把学生添加到集合
array.add(s1);
array.add(s2);
array.add(s3);
array.add(s4);
//使用Collections对ArrayList集合排序
//sort(List<T> list, Comparator<? super T> c)
Collections.sort(array, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
//按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
int num = s1.getAge() - s2.getAge();
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
return num2;
}
});
//遍历集合
for (Student s : array) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
5.数组和链表
1.数组
数组的长度是固定的
数组只能存储同一种数据类型的元素
注意:在Java中只有一维数组的内存空间是连续,多维数组的内存空间不一定连续。
那么数组又是如何实现随机访问的呢?
寻址公式:i_address = base_address + i * type_length
为什么数组的效率比链表高?
数组是连续存储, 链表是非连续存储
数组的基本操作:
添加 (保证元素的顺序)
最好情况:O(1)
最坏情况:移动n个元素,O(n)
平均情况:移动 n/2 个元素,O(n)
删除 (保证元素的顺序)
最好情况:O(1)
最坏情况:移动n-1个元素,O(n)
平均情况:移动(n-1)/2个元素,O(n)
查找
a. 根据索引查找元素:O(1)
b. 查找数组中与特定值相等的元素
①大小无序:O(n)
②大小有序:O(log2n)
总结: 数组增删慢,查找快。
2.链表
形象地说,链表就是用一串链子将结点串联起来。
结点:包含数据域和指针域。
数据域:数据
指针域:下一个结点的地址
链表及循环链表示例:
Node.java
public class Node {
String value;
Node next;
public Node(String value, Node next) {
this.value = value;
this.next = next;
}
}
LinkedDemo01.java
public class LinkedDemo01 {
public static void main(String[] args) {
Node node1 = new Node("20", null);
Node node2 = new Node("40", node1);
Node node3 = new Node("70", node2);
node1.next=node3;//创建循环链表
System.out.println("输出节点node1的next节点node3的值:"+node1.next.value);
//
//node3->node2->node1
//node3.next.next等价于->node1
System.out.println("输出节点node3的next的next节点的地址:"+node3.next.next.next);
Node nexttest= node2.next;//nexttest即为node1的地址对象
System.out.println("输出nexttest即为node1的地址对象:"+nexttest);
String str="zs";
System.out.println(str);
}
}
结果输出:
输出节点node1的next节点node3的值:70
输出节点node3的next的next节点的地址:com.ketang.day28_20210131.Node@1b6d3586
输出nexttest即为node1的地址对象:com.ketang.day28_20210131.Node@4554617c
zs
双向链表及双向循环链表示例:
DBNode.java
public class DBNode {
String value;
DBNode pre;
DBNode next;
public DBNode(String value, DBNode pre, DBNode next) {
this.value = value;
this.pre = pre;
this.next = next;
}
}
DBNodeDemo.java
public class DBNodeDemo {
public static void main(String[] args) {
DBNode dbNode1 = new DBNode("1", null, null);
DBNode dbNode2 = new DBNode("2", dbNode1, null);
DBNode dbNode3 = new DBNode("3", dbNode2, null);
DBNode dbNode4 = new DBNode("4", dbNode3, null);
// //实现1-->2-->3-->4的双向链表
// // 1<--2<--3<--4
dbNode1.next=dbNode2;
dbNode2.next=dbNode3;
dbNode3.next=dbNode4;
System.out.println(dbNode3.pre.pre.value);
//实现双向循环链表
dbNode1.pre=dbNode4;
dbNode4.next=dbNode1;
System.out.println(dbNode4.next.value);
System.out.println(dbNode1.pre.value);
}
}
DBNodeDemo.java
class DBNodeDemo {
public static void main(String[] args) {
DBNode dbNode1 = new DBNode("1", null, null);
DBNode dbNode2 = new DBNode("2", dbNode1, null);
DBNode dbNode3 = new DBNode("3", dbNode2, null);
DBNode dbNode4 = new DBNode("4", dbNode3, null);
// //实现1-->2-->3-->4的双向链表
// // 1<--2<--3<--4
dbNode1.next=dbNode2;
dbNode2.next=dbNode3;
dbNode3.next=dbNode4;
System.out.println("输出dbNode3节点pre的pre节点dbNode1的值:"+dbNode3.pre.pre.value);
//实现双向循环链表
dbNode1.pre=dbNode4;
dbNode4.next=dbNode1;
System.out.println("输出dbNode4节点next节点dbNode1的值:"+dbNode4.next.value);
System.out.println("输出dbNode1节点pre节点dbNode4的值:"+dbNode1.pre.value);
}
}
结果输出:
输出dbNode3节点pre的pre节点dbNode1的值:1
输出dbNode4节点next节点dbNode1的值:1
输出dbNode1节点pre节点dbNode4的值:4
循环链表我们用的一般比较少,但是当处理的数据具有环形结构时,就特别适合用循环链表,比如约瑟夫问题。
单链表:
增加(在某个结点后面添加)
//添加方法
public boolean add(String str) {
//判断链表是否为空
if (top == null || size == 0) {
//代表一个空链表
top = new Node(str, null);//创建头节点
size++;//长度加一
return true;
}
//查找尾节点
Node mid = top;
while (mid.next != null) {
mid = mid.next;
}
//上述循环的跳出条件,就是mid是尾节点
mid.next = new Node(str, null);//从尾节点开始添加节点
size++;
return true;
}
删除(在某个结点后面删除)
//删除方法
public String delete(String str) {
// TODO:参数验证
if (top == null || size == 0) throw new RuntimeException("linked is null");
//如果要删除的是头结点
if (top.value.equals(str)) {
//要删除的节点是头结点
top = top.next;
size--;
return str;
}
//如果要删除的不是头结点
Node mid = top;
while (mid.next != null && !mid.next.value.equals(str)) {//一直向后查找
mid = mid.next;
}
//意味着
//1.mid.next=null,没有删除的元素
//2.mid,next就是要删除的点
if (mid.next == null) {
//没找到
return null;
}
//mid.next要找的节点
mid.next = mid.next.next;//覆盖删除
size--;
return str;
}
修改:
//修改方法
public boolean set(String oldStr, String newStr) {
//TODO:参数验证:
if (top == null || size == 0) throw new RuntimeException("likend is null");
//修改是否是头结点
if (top.value.equals(oldStr)) {
//就是要修改头结点
top.value = newStr;
return true;
}
//修改的不是头结点,一直向下寻找
Node mid = top;
//查找要替换的元素
while (mid.next != null && !mid.next.value.equals(oldStr)) {
mid = mid.next;
}
//没找到
if (mid.next == null) {
return false;
}
//必然找到
mid.next.value = newStr;
return true;
}
查找:
a. 根据索引查找元素
b. 查找链表中与特定值相等的元素
①元素大小有序
②元素大小无序
总结:链表增删快,查找慢。
单链表增删改示例:
MyLinked.java
public class MyLinked {
//单链表
Node top;//头结点
int size;
@Override
public String toString() {
return "MyLinked{" +
"top=" + top +
", size=" + size +
'}';
}
//添加方法
public boolean add(String str) {
//判断链表是否为空
if (top == null || size == 0) {
//代表一个空链表
top = new Node(str, null);//创建头节点
size++;//长度加一
return true;
}
//查找尾节点
Node mid = top;
while (mid.next != null) {
mid = mid.next;
}
//上述循环的跳出条件,就是mid是尾节点
mid.next = new Node(str, null);//从尾节点开始添加节点
size++;
return true;
}
//删除方法
public String delete(String str) {
// TODO:参数验证
if (top == null || size == 0) throw new RuntimeException("linked is null");
//如果要删除的是头结点
if (top.value.equals(str)) {
//要删除的节点是头结点
top = top.next;
size--;
return str;
}
//如果要删除的不是头结点
Node mid = top;
while (mid.next != null && !mid.next.value.equals(str)) {//一直向后查找
mid = mid.next;
}
//意味着
//1.mid.next=null,没有删除的元素
//2.mid,next就是要删除的点
if (mid.next == null) {
//没找到
return null;
}
//mid.next要找的节点
mid.next = mid.next.next;//覆盖删除
size--;
return str;
}
//修改方法
public boolean set(String oldStr, String newStr) {
//TODO:参数验证:
if (top == null || size == 0) throw new RuntimeException("likend is null");
//修改是否是头结点
if (top.value.equals(oldStr)) {
//就是要修改头结点
top.value = newStr;
return true;
}
//修改的不是头结点,一直向下寻找
Node mid = top;
//查找要替换的元素
while (mid.next != null && !mid.next.value.equals(oldStr)) {
mid = mid.next;
}
//没找到
if (mid.next == null) {
return false;
}
//必然找到
mid.next.value = newStr;
return true;
}
//查找方法
public boolean find(String str){
//TODO:参数验证:
if (top == null || size == 0) throw new RuntimeException("likend is null");
//头结点是否是要查找的元素
if (top.value.equals(str)) {
return true;
}
Node mid=top;
//查找指定的元素
while (mid.next != null && !mid.next.value.equals(str)) {
mid = mid.next;
}
//没找到
if (mid.next == null) {
return false;
}else//否则找到
return true;
}
class Node {
String value;
Node next;
public Node(String value, Node next) {
this.value = value;
this.next = next;
}
@Override
public String toString() {
return "Node{" +
"value='" + value + '\'' +
", next=" + next +
'}';
}
}
}
MyLinkedtest.java
public class MyLinkedtest {
public static void main(String[] args) {
MyLinked myLinked = new MyLinked();
myLinked.add("dd");
myLinked.add("gg");
myLinked.add("ss");
System.out.println(myLinked.toString());
String gg = myLinked.delete("gg");
System.out.println("删除gg后的链表:" + myLinked);
myLinked.set("dd", "aa");
myLinked.set("ss", "agrnzb");
System.out.println("修改后的链表:" + myLinked);
System.out.println("是否找到指定元素:" +myLinked.find("dd"));
System.out.println("是否找到指定元素:" +myLinked.find("gg"));
System.out.println("是否找到指定元素:" +myLinked.find("ss"));
System.out.println("是否找到指定元素:" +myLinked.find("agrnzb"));
}
}
结果输出;
MyLinked{top=Node{value='dd', next=Node{value='gg', next=Node{value='ss', next=null}}}, size=3}
删除gg后的链表:MyLinked{top=Node{value='dd', next=Node{value='ss', next=null}}, size=2}
修改后的链表:MyLinked{top=Node{value='aa', next=Node{value='agrnzb', next=null}}, size=2}
是否找到指定元素:false
是否找到指定元素:false
是否找到指定元素:false
是否找到指定元素:true
承接以上代码实现:(根据下标删除元素)
/* 根据下标删除元素*/
public boolean deleteindex(int index) {
if (top == null || size == 0) throw new RuntimeException("likend is null");
if (index >= size) throw new RuntimeException("越界异常:indexOutOfBoundsException!");
Node mid = top;
//如果节点为第一个节点
if (index == 0) {
System.out.println("第一个节点已删除!");
top = top.next;//将头结点指向第二个节点
size--;
return true;
}
//找到删除节点的前一个节点
int j = 0;
while (mid.next != null && j < index - 1) {
top = top.next;
j++;
}
top.next = top.next.next;
size--;
return true;
}
结果输出;
MyLinked{top=Node{value='dd', next=Node{value='gg', next=Node{value='ss', next=null}}}, size=3}
是否删除下标为1的元素:true
删除下标为1元素后的单链表:MyLinked{top=Node{value='dd', next=Node{value='ss', next=null}}, size=2}
Exception in thread "main" java.lang.RuntimeException: 越界异常:indexOutOfBoundsException!
at com.ketang.day28_20210131.MyLinked.MyLinked.deleteindex(MyLinked.java:123)
at com.ketang.day28_20210131.MyLinked.MyLinkedtest.main(MyLinkedtest.java:32)
双向链表:
双向链表和单链表的时间复杂度是一样的,但双向链表有单链表没有的独特魅力——它有一条指向前驱结点的链接。(双向链表更常用)
增加 (在某个结点前面添加元素)
删除 (删除该结点)
查找
a. 查找前驱结点
b. 根据索引查找元素
c. 查找链表中与特定值相等的元素
① 元素大小无序
② 元素大小有序
//查找方法
public boolean find(String str){
//TODO:参数验证:
if (top == null || size == 0) throw new RuntimeException("likend is null");
//头结点是否是要查找的元素
if (top.value.equals(str)) {
return true;
}
Node mid=top;
//查找指定的元素
while (mid.next != null && !mid.next.value.equals(str)) {
mid = mid.next;
}
//没找到
if (mid.next == null) {
return false;
}else//否则找到
return true;
}
总结:虽然双向链表更占用内存空间,但是它在某些操作上的性能是优于单链表的。
思想:用空间换取时间。
练习:
1.求链表的中间元素:
解题思路:
定义两个指针fast和slow。slow一次遍历一个节点,fast一次遍历两个节点,由于fast的速度是slow的两倍,所以当fast遍历完链表时,slow所处的节点就是链表的中间节点。
public class practise01 {
public static void main(String[] args) {
Node node1 = new Node("1", null);
Node node2 = new Node("2", node1);
Node node3 = new Node("3", node2);
Node node4 = new Node("4", node3);
Node node5 = new Node("5", node4);
Node z=middleNode(node5);
System.out.println(z);
}
public static Node middleNode(Node head) {
if (head == null || head.next == null) {
return head;
}
Node fast = head.next;//快指针
Node low = head;//慢指针
//快指针走两步,慢指针走一步
while (fast != null && fast.next != null) {
fast = fast.next.next;
low = low.next;
}
return fast == null ? low : low.next;
}
}
结果输出:
Node{value='3', next=Node{value='2', next=Node{value='1', next=null}}}
2.判断链表中是否有环(circle)
3.反转单链表
3.总结
1.数组是连续内存,CPU的高速缓存可预读数据(链表不能)。若数组过大无连续空间,会抛出OOM、
2.数组的缺点是大小固定。数组太大,浪费内存空间;数组太小,需重新申请更大数组,并将数据拷贝过去,耗时。
3.若对内存的使用苛刻,数组更适合。因结点有指针域,更耗内存。且对链表的频繁插入和删除,会导致结点对象的频繁创建和销毁,有可能会导致频繁的GC活动。