复习
1.集合的理解
理解为是容器,大小可变。
集合和数组:
数组: 1.长度固定 2.只能存储相同类型的数据 3.基本类型、引用类型 4.下标
集合: 1.长度可变 2.存储不同类型的数据 3.引用类型 4.有的有下标(索引),有的没有
2.集合框架结构
Collection
--List
--ArrayList : 数组 适合做查找
--LinkedList : 双向链表 适合增删
在链表的头和尾新增了方法, get、add、remove
节点(node) : 引用值和数据
--Set
--HashSet
--TreeSet
Map
--HashMap
--TreeMap
3.Collection
单列集合的根接口
add()/size()/remove()…
iterator(): 获取迭代器,迭代器的作用就是用来遍历集合元素(删除)
4.List
是Collection的子接口,有序可重复。
新增了操作索引的方法。
get(index) add(index,e) remove(index) set(index,e)
listIterator():列表迭代器,是迭代器子接口,可以正向或逆向遍历元素,遍历的过程中添加、修改、删除元素。
5.实现类:ArrayList和LinkedList
6.数据结构:
栈(先进后出)、队列(先进先出)、数组、链表
课程
一.泛型
(一)泛型的概述和使用
1、泛型:广泛的类型,在定义一个类的时候,类型中有些方法参数、返回值类型不确定,可以使用一个符号,来表示那些尚未确定的类型,这个符号,就称为泛型。泛型是java5中引入的特性,它提供了编译时类型安全检测机制
2、使用:在集合当中,我们去创建集合对象的时候,后面可以添加一个<引用数据类型> ,表示泛型,表示的是该集合当中,存储的元素的数据类型。在集合的创建中添加了泛型之后,该集合只能存储该类型的数据,或者是该类型子类的数据,不能存储不是该类型,并且不能用该类型接受的数据。
例如:ArrayList al = new ArrayList();
3、泛型的好处:
(1) 提高了数据的安全性,将运行时的问题,提前暴露在编译时期
(2) 避免了强转的麻烦
4、注意事项:
(1) 前后一致:在创建对象时,赋值符号前面和后面的类型的泛型,必须一致
(2) 泛型推断:如果前面的引用所属的类型已经写好了泛型,后面创建对象的类型就可以只写一个尖括号,尖括号中可以不写任何内容。<>特别像菱形,称为“菱形泛型”,jdk1.7特性
(二)泛型类的定义
1、泛型类:带着泛型定义的类
2、格式:
class 类名<泛型类型1, 泛型类型2, .....> {
}
3、说明:
(1) 类名后面跟着的泛型类型,是泛型的声明,一旦泛型声明出来,就相当于这个类型成为了已知类型,这个类型就可以在整个类中使用
(2) 泛型的声明名称,只需要是一个合法的标识符即可,但是通常我们使用单个大写字母来表示,常用字母:T、W、Q、K、V、E
(3) 泛型确定的时机:将来在使用这个类,和创建对象的时候
import java.util.ArrayList;
import java.util.Iterator;
public class Demo1 {
public static void main(String[] args) {
ArrayList<String> list= new ArrayList<>();
list.add("abc");
/*list.add(123);*/
list.add("123");
for (int i = 0; i < list.size(); i++) {
/*String s = (String)list.get(i);*/
System.out.println(list.get(i).toUpperCase());
}
Iterator<String> it = list.iterator();
String next = it.next();
}
}
/*
泛型:
是jdk5.0的新特性,属于一种安全机制,可以让运行时出现的类型转换问题问题提前至编译时期,好处不需要做向下转型。
弊端,只能操作泛型指定的类型,或泛型指定的类型的子类型。
格式:<引用类型名>
* */
(三)泛型方法的定义
1、在方法声明中,带着泛型声明的方法,就是泛型方法
2、格式:
修饰符 <泛型声明1, 泛型声明2,.....> 返回值类型 方法名称(参数列表) {
}
3、说明:
(1) 在方法上声明的泛型,可以在整个方法中,当做已知类型来使用
(2) 如果【非静态】方法上没有任何泛型的声明,那么可以使用类中定义的泛型
(3) 如果【静态】方法上没有任何的泛型声明,那么就不能使用泛型,连类中定义的泛型,也不能使用,因为类中的泛型需要在创建对象的时候才能确定。所以【静态】方法想使用泛型,就必须在自己的方法上单独声明。
public class Demo2 {
public static void main(String[] args) {
//不确定泛型类型
GenericDemo1 gd = new GenericDemo1();
gd.show("abc");
//确定泛型类型
GenericDemo1<String,Integer> gd1 = new GenericDemo1<>();
gd1.t = 123;
gd1.e = "123";
gd1.show(111);
}
}
/*
* 自定义泛型:
* 泛型类
* 泛型方法
* 泛型接口
* 泛型名: 通常用大写字母来表示。
* */
/*泛型类
class 类名<名1,名2,....>{}
泛型类上的泛型整个类都可以使用:属性、方法参数、返回值类型
泛型类上的泛型,在封装类中成员时也是可以不使用的。
在创建对象时,确定泛型类型。如果不确定其类型,那么就是Object类型。
*/
class GenericDemo1<E,T>{
String name;
T t;
E e;
public void show(T t){
System.out.println(t);
}
public void method(double d){
System.out.println(d);
}
}
import java.util.ArrayList;
import java.util.Arrays;
public class Demo3 {
public static void main(String[] args) {
GenericDemo2 gd = new GenericDemo2();
gd.method("abc");
gd.method(123);
gd.show('a',1);
GenericDemo2.fun(123);
ArrayList<String> list = new ArrayList<>();
list.add("123");
list.add("124");
list.add("125");
/*
集合转数组:
创建的数组长度和集合长度 相等,会将集合中的数据复制到数组中。
创建的数组长度 大于 集合长度 ,会将集合中的数据复制到数组中,多余的位置存储null。
创建的数组长度 小于 集合长度 ,会将集合中的数据复制到新数组中,并作为方法的返回值,而定义的数组存储null。
*/
String[] arr = new String[2];
String[] newarr = list.toArray(arr);
System.out.println(Arrays.toString(arr));
System.out.println(Arrays.toString(newarr));
}
}
/*
* 泛型方法:
* 修饰符 <名1,名2,...> 返回值类型 方法名 (参数列表){
* ...
* }
* 修饰符 static <名1,名2,...> 返回值类型 方法名 (参数列表){
* ...
* }
*
* 调用方法时,确定泛型方法上的类型。
*
* 注意: 如果类上有泛型,静态方法不可以使用类上的泛型,非静态方法可以使用类上的泛型
*
* */
class GenericDemo2<E>{
public <T> void method(T t){
System.out.println(t);
}
public <T,W> void show(T t,W w){
System.out.println(t);
}
public static <U> void fun(U u){
}
}
public class Demo4 {
public static void main(String[] args) {
GenericDemo4<Double> gd = new GenericDemo4<>();
gd.method(1.23);
}
}
/*
* 泛型接口:
* interface 接口名<名1,名2,...>{
*
* }
*
* 1.定义实现类,直接确定泛型类型
* 2.定义实现类对象时,确定泛型类型
* */
interface Inter<T>{
void method(T t);
}
class GenericDemo3 implements Inter<String>{
@Override
public void method(String s) {
}
}
class GenericDemo4<T> implements Inter<T>{
@Override
public void method(T t) {
}
}
(四)泛型通配符
1、使用泛型的时候,没有使用具体的泛型声明T,而是使用了和声明过的某个泛型T有关的一类类型,就称为泛型的通配符。三种形式:
2、第一种形式,使用?来表示可以是任意类型,例如:
Collection接口中的removeAll(Collection<?> c),表示可以接收任意泛型类型的集合,作为该方法的实际参数,参数集合的泛型,可以是与E没有任何关系
3、第二种形式,使用? extends E来表示必须是某个泛型类型或是该泛型类型的子类,例如:
Collection接口中的addAll(Collection<? extends E> c),表示可以接收泛型类型是调用者泛型类型或者其子类的集合,作为该方法的实际参数。参数的泛型和调用者的泛型,必须有关(相同或者是子父类)。确定了泛型的上边界。
4、第三种形式,使用? super E来表示必须是某个泛型类型或者是该泛型类型的父类,例如:
Arrays工具类中,排序方法static void sort(T[] a, Comparator<? super T> c),T是该方法的泛型,T表示的是数组中元素的类型,<? super T>表示可以接收泛型类型是数组元素类型T或者是元素类型T的父类的比较器,作为sort方法的参数。参数的泛型和方法的泛型,必须有关(相同或者是子父类)。确定了泛型的下边界。
import java.util.ArrayList;
class Person{}
class Student extends Person{}
class Teacher extends Person{}
public class Demo5 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1223");
list.add("1224");
ArrayList<Person> l1 = new ArrayList<>();
l1.add(new Person());
l1.add(new Student()); //多态
ArrayList<Student> l2 = new ArrayList<>();
l2.add(new Student());
ArrayList<Teacher> l3 = new ArrayList<>();
l3.add(new Teacher());
test3(l1);
test3(l2);
// test3(l3);
// test1(list);
// test1(l1);
}
public static void test1(ArrayList<?> list){
for(Object obj:list){
System.out.println(obj);
}
}
public static void test2(ArrayList<? extends Person> list){ //能接受的类型是Person类型或person的子
for(Object obj:list){
System.out.println(obj);
}
}
public static void test3(ArrayList<? super Student> list){ //能接受的类型是Student类型或Student的父
for(Object obj:list){
System.out.println(obj);
}
}
}
/*
* 泛型通配符:
* <?> : 通配符或占位符,可以匹配任意类型,相当于是Object
泛型限定:
<? extends E> : 能接受的类型是 E类型或E的子类型 ---上限
<? super E> :能接受的类型是 E 类型或E的父类型 ---下限
以上使用在方法的参数上。
* */
二. 无序单列集合Set
(一) 概述
-
java.util.Set接口和java.util.List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入
的元素不出现重复。 -
特点:
(1) 无序:没有任何前后的分别,存入的顺序和取出的顺序不一定一致
(2) 没有索引:集合中没有任何位置,元素也就没有位置的属性
(3) 不重复:没有位置的区分,相同值的元素没有任何分别,所以不能重复 -
Set的常用实现类:HashSet Set s = new HashSet()
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Demo6 {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
set.add(12);
set.add(19);
set.add(8);
set.add(22);
set.add(22);
System.out.println(set);
for (Integer in : set)
System.out.println(in);
System.out.println("------------------------");
Iterator<Integer> iterator = set.iterator();
while(iterator.hasNext())
System.out.println(iterator.next());
}
}
/*
* Set集合:
* 是一个无序不可重复的集合,只能最多存储一个null。
* 实现类:
* HashSet、TreeSet
* */
- Set集合的遍历方式: 迭代器遍历, 增强for遍历
练习1(比较器)
使用合适的容器存储5个不重复的1-99之间的任意随机整数
分析 :
容器: 数组, List(有序,有索引,可重复), Set(无序,无索引,不重复)
import java.util.HashSet;
import java.util.Set;
public class Demo7 {
public static void main(String[] args) {
//使用合适的容器存储5个不重复的1-99之间的任意随机整数
Set<Integer> set = new HashSet<>();
/*while(true) {
if (set.size() == 5) {
break;
}
set.add((int) (Math.random() * 100));
}*/
while(set.size() != 5) {
set.add((int) (Math.random() * 100));
}
System.out.println(set);
}
}
import java.util.Comparator;
import java.util.TreeSet;
class MyCom implements Comparator<Person1>{
@Override
public int compare(Person1 p1, Person1 p2) {
System.out.println(p1.getName()+"----------------------"+p2.getName());
int num = p1.getName().compareTo(p2.getName());
if(num == 0)
return p1.getAge()-p2.getAge();
return num;
}
}
public class Demo9 {
public static void main(String[] args) {
// TreeSet<Person1> set = new TreeSet<>();
// TreeSet<Person1> set = new TreeSet<>(new MyCom());
TreeSet<Person1> set = new TreeSet<>(new Comparator<Person1>() { //匿名内部类
@Override
public int compare(Person1 p1, Person1 p2) {
//负整数 前对象的值 < 后对象的值 , 位置排在前
//零 前对象的值 = 后对象的值 , 位置不变
//正整数 前对象的值 > 后对象的值 , 位置排在后
int num = p1.getName().compareTo(p2.getName());
if(num == 0)
return p1.getAge()-p2.getAge();
return num;
}
});
/*set.add("22");
set.add("34");
set.add("66");
set.add("11");
set.add("99");
set.add("99");
for (String s: set)
System.out.println(s);*/
set.add(new Person1("zhangsan001",30));
set.add(new Person1("zhangsan111",31));
set.add(new Person1("zhangsan221",32));
set.add(new Person1("zhangsan001",39));
set.add(new Person1("zhangsan331",30));
for (Person1 per:set)
System.out.println(per);
}
}
/*
* TreeSet:
* 存储的元素默认会按照自然顺序(字典)进行排序存储,不会存储重复数据。
* 底层原理就是: 二叉树算法。
* 1.调用add方法添加元素时,元素要提升为Comparable类型(Comparable是一个接口,实现该接口的对象,会按照自然顺序进行排序),调用compareTo
* 方法进行元素的自然顺序排序,方法的返回值是0,视为相同元素则不存储,返回的是正整数或负整数存储到对应的位置上。
* 2.创建TreeSet容器时,可以传递比较器,按照比较器的方式进行排序。根据比较器Comparator中的compare方法的返回值决定顺序,如果返回值是0
* 是相同元素则不存储,如果返回的是正整数或负整数那么就按照顺序排序。
* 按照以上哪种方式排序,取决于调用的构造方法。
* */
class Person1 implements Comparable<Person1>{
private String name;
private int age;
public Person1(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person1{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Person1 p) {
//public int compareTo(Student o){} 这个方法,它返回三种 int 类型的值: 负整数,零 ,正整数。
//返回值 含义
//负整数 当前对象的值 < 比较对象的值 , 位置排在前
//零 当前对象的值 = 比较对象的值 , 位置不变
//正整数 当前对象的值 > 比较对象的值 , 位置排在
System.out.println(this.name+"----"+p.name);
int num = this.age-p.age;
if(num == 0)
return this.name.compareTo(p.name);
return num;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
(二) 数据结构之哈希表
-
哈希值
(1) 是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
(2) 如何获取哈希值: Object类中的public int hashCode()返回对象的哈希码值
(3) 哈希值的特点
同一个对象多次调用hashCode()方法返回的哈希值是相同的
默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同 -
哈希表结构
(1) JDK1.8以前: 数组 + 链表
(2) JDK1.8以后:
a. 节点个数少于等于8个: 数组 + 链表
b. 节点个数多于8个: 数组 + 红黑树
(三) HashSet保证元素唯一源码分析
1、某个对象obj,在即将要存储到HashSet集合的时候,首先计算obj的hashCode值
2、在集合中的所有元素的哈希值,都和obj的哈希值不同,说明在集合中不存在obj,可以直接将obj存储到HashSet中
3、在集合中有若干元素的哈希值,和obj的哈希值相同,并不能说明obj已经存在于集合中,需要使用equals判断obj是否和那些与自己哈希值相同的元素是否相等
4、如果这些元素所有的和obj比较equals之后,都不相等,那么就说明obj不存在于集合中,可以将obj存储到HashSet中
5、如果这些元素有任意一个和obj比较equals之后,发现相等,那么就说明obj已经存在于集合中,所以obj就不能存储,存储失败
import java.util.HashSet;
import java.util.Objects;
public class Demo8 {
public static void main(String[] args) {
//System.out.println(new Demo8().hashCode()); //返回对象的哈希值
//创建hashSet容器,存储自定义对象Student,属性信息相同视为相同给元素,则不存储。
HashSet<Student1> set = new HashSet<>();
set.add(new Student1("xiaoming11",18));
set.add(new Student1("xiaoming22",19));
set.add(new Student1("xiaoming22",19));
set.add(new Student1("xiaoming33",17));
set.add(new Student1("xiaoming44",16));
//遍历set
for (Student1 stu : set)
System.out.println(stu);
}
}
/*
* HashSet:
* 底层算法是哈希表算法。
* 原理 :
* 当添加元素时,元素会进行hashCode方法调用,计算自己的哈希值,如果哈希值是第一次出现,直接存储。
* 如果出现哈希值重复,则会进行第二次校对,比较是否是同一对象,依赖于Object类中的equals方法,
* 返回的返回值是true,是相同元素不存储,如果是false,不是相同元素,则要存储,此时的存储在jdk1.8之前采用的链表算法;
* 在jdk1.8之后,采用红黑树算法(前提:链表的节点个数大于8,并且元素的总个数超过64)
* */
class Student1{
private String name;
private int age;
public Student1(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student1 student1 = (Student1) o;
return age == student1.age &&
Objects.equals(name, student1.name);
}
@Override
public int hashCode() {
System.out.println("---");
return Objects.hash(name, age);
}
/*@Override
public int hashCode(){
System.out.println("-----------------");
return name.hashCode()/age;
}
@Override
public boolean equals(Object obj){
System.out.println("===========================================");
if(obj instanceof Student1) {
Student1 stu = (Student1) obj;
return this.name.equals(stu.name) && this.age == stu.age;
}
return false;
}*/
@Override
public String toString() {
return "Student1{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
import java.util.HashSet;
import java.util.LinkedHashSet;
public class Demo10 {
public static void main(String[] args) {
// LinkedHashSet是 HashSet的子类对象,采用的是:哈希表+链表算法,保证元素的迭代顺序,并且元素唯一。
// LinkedHashSet<Integer> set = new LinkedHashSet<>();
HashSet<Integer> set = new HashSet<>();
set.add(99);
set.add(11);
set.add(55);
set.add(22);
set.add(99);
System.out.println(set);
}
}
三. 双列集合
(一) Map概述
1、体系位置:双列集合的顶层接口
2、类比理解:map单词含义,地图,地图上的每个点,都表示了生活中的一个具体位置。地图的点和生活中的位置,有一个一一对应的关系,这种关系是通过穷举的方式来描述的。
3、数据结构:描述的就是一个数据(key)到另一个数据(value)的映射关系(对应关系)
4、Map<K,V>的特点:称为键值对映射关系(一对一)
Key(键)是唯一的(不重复),value(值)不是唯一的
每个键都只能对应确定唯一的值
- Map集合没有索引, 因此存储的元素不能保证顺序
(二) Map常用的子类
通过查看Map接口描述,看到Map有多个实现类,这里我们主要讲解常用的HashMap集合 LinkedHashMap集合.
HashMap<K,V>:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
LinkedHashMap<K,V>:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
(三) Map接口中常用的方法
- public V put(K key, V value):
- 如果添加的key值在map集合中不存在, 那么put方法表示将键值对添加到map集合中
- 如果添加的key值在map集合中存在, 那么put方法表示修改value值
这个方法会返回修改前的值,第一次添加的返回null。
- public V remove(Object key): 把指定的键所对应的键值对元素在Map集合中删除,返回被删除元素的值。
- public V get(Object key):根据指定的键,在Map集合中获取对应的值。
- boolean containsKey(Object key): 判断集合中是否包含指定的键。
- void clear() : 表示清空Map集合中所有键值对数据
- boolean isEmpty() : 验证Map集合中是否还是键值对数据, 如果没有返回true, 如果有返回false
- int size() : 获取到Map集合中键值对儿数据个数(求Map集合的长度)
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class Demo11 {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
//存储key-value
System.out.println(map.put(1,"zhangsan"));//null
System.out.println(map.put(1,"lisi")); //zhangsan
map.put(3,"wangwu");
map.put(4,"zhaoliu");
//清空
//map.clear();
//移除 key=value,并返回value
//System.out.println(map.remove(3));
//获取,根据key获取value
//System.out.println(map.get(3));
//返回键值对的个数
//System.out.println(map.size());
//获取所有的value
//Collection<String> values = map.values();
//System.out.println(values);
//是否包含指定的key
System.out.println(map.containsKey(3));
//是否包含指定的value
System.out.println(map.containsValue("zhangsan"));
//是否为空,容器中的键值对个数是否为0,不空是false
System.out.println(map.isEmpty());
System.out.println(map);
}
}