4. 泛型
4.1 泛型概述
4.1.1 生活中的例子
-
举例1:中药店,每个抽屉外面贴着标签
-
举例2:超市购物架上很多瓶子,每个瓶子装的是什么,有标签
-
举例3:家庭厨房中:
Java中的泛型,就类似于上述场景中的
标签
。
4.1.2 泛型的引入
在Java中,我们在声明方法时,当在完成方法功能时如果有未知的数据
需要参与,这些未知的数据需要在调用方法时才能确定,那么我们把这样的数据通过形参
表示。在方法体中,用这个形参名来代表那个未知的数据,而调用者在调用时,对应的传入实参
就可以了。
受以上启发,JDK1.5设计了泛型的概念。泛型即为“类型参数
”,这个类型参数在声明它的类、接口或方法中,代表未知的某种通用类型。
举例1:
集合类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK5.0之前只能把元素类型设计为Object,JDK5.0时Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时指定集合元素的类型。比如:List<String>
,这表明该List只能保存字符串类型的对象。
使用集合存储数据时,除了元素的类型不确定,其他部分是确定的(例如关于这个元素如何保存,如何管理等)。
举例2:
java.lang.Comparable
接口和java.util.Comparator
接口,是用于比较对象大小的接口。这两个接口只是限定了当一个对象大于另一个对象时返回正整数,小于返回负整数,等于返回0,但是并不确定是什么类型的对象比较大小。JDK5.0之前只能用Object类型表示,使用时既麻烦又不安全,因此 JDK5.0 给它们增加了泛型。
其中<T>
就是类型参数,即泛型。
所谓泛型,就是允许在定义类、接口时通过一个
标识
表示类中某个属性的类型
或者是某个方法的返回值或参数的类型
。这个类型参数将在使用时(例如,继承或实现这个接口、创建对象或调用方法时)确定(即传入实际的类型参数,也称为类型实参)。
4.2 使用泛型举例
自从JDK5.0引入泛型的概念之后,对之前核心类库中的API做了很大的修改,例如:JDK5.0改写了集合框架中的全部接口和类、java.lang.Comparable接口、java.util.Comparator接口、Class类等。为这些接口、类增加了泛型支持,从而可以在声明变量、创建对象时传入类型实参。
4.2.1 集合中使用泛型(ArrayList,HashMap)
集合中没有使用泛型时:
集合中使用泛型时:
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。即,把不安全的因素在编译期间就排除了,而不是运行期;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换。
同时,代码更加简洁、健壮。
把一个集合中的内容限制为一个特定的数据类型,这就是generic背后的核心思想。
package com.atguigu.java;
import org.junit.Test;
import java.util.*;
/**
*
* 泛型的使用
* 1.jdk 5.0新增的特性
*
* 2.在集合中使用泛型:
* 总结:
* ① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
* ② 集合框架在声明接口和其实现类时,使用了泛型(jdk5.0),在实例化集合对象时:
* 情况1:如果没有使用泛型,则认为操作的是Object类型的数据。
* 情况2:如果使用了泛型,则需要指明泛型的具体类型。一旦指明了泛型的具体类型,则在集合的相关的方法中,
* 凡是使用类的泛型的位置(比如:方法、构造器、属性等),都替换为具体的泛型类型。
* 比如:add(E e) --->实例化以后:add(Integer e)
*
* ③ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
* ④ 在集合类或接口中凡是定义类或接口时,如果实例化时,没有指明泛型的类型,在eclipse中会有警告
* 信息 在idea中没有警告。警告就是不强制 你不用也不报错。
*
* 3.如何自定义泛型结构:泛型类、泛型接口;泛型方法。见 4.4
*
* @author shkstart
* @create 2019 上午 9:59
*/
public class GenericTest {
//在集合中使用泛型之前的情况:
@Test
public void test1(){
ArrayList list = new ArrayList();
//需求:存放学生的成绩
list.add(78);
list.add(76);
list.add(89);
list.add(88);
//问题一:类型不安全,没有限制可以存放任何类型。
// list.add("Tom");
for(Object score : list){
//问题二:强转时,可能出现ClassCastException。因为强转要满足父子类关系,Tom是String类型 不满足条件。
int stuScore = (Integer) score;//强转+自动拆箱
System.out.println(stuScore);//78,76,89,88
}
}
//在集合中使用泛型的情况:以ArrayList为例
@Test
public void test2(){
//泛型都是引用数据类型,基本数据类型用它的包装类。
/*
* 说明:
* ArrayList能指定泛型的前提是,底层源码定义这个类时声明了泛型 如:public class ArrayList<E> extends AbstractList<E>,
* 创建对象时相当于是给这个泛型赋值了个具体的类型 如:ArrayList<Integer> list = new ArrayList<Integer>(); 为Integer类型,
* 那么凡是在这个底层源码的类ArrayList中用到泛型的地方 如:add()方法使用了泛型 public boolean add(E e) {},则在实例化对象时,
* 通过对象调用这个方法 如list.add(87);中元素的类型也应该和声明的泛型类型保持一致,即实例化的类型是Integer ,add方法添加元素类型也是Integer。
*
* 注意:如果定义类时没有指定泛型,那么创建对象时不能直接使用泛型。
* */
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(78);//public boolean add(E e) {} 方法声明用的泛型和定义时一致,创建对象时有指定泛型为Integer,所以这里只能用Integer类型数据添加。
list.add(87);
list.add(99);
list.add(65);
//编译时,就会进行类型检查,保证数据的安全
// list.add("Tom");
//方式一:
// for(Integer score : list){//直接就可以使用Integer类型来接收
// //避免了强转操作
// int stuScore = score;
//
// System.out.println(stuScore);
//
// }
//方式二:
/* Iterator在定义这个类时就使用了泛型,所以在这个地方就可以使用泛型了。public interface Iterator<E> {}
* 问题:为什么这个地方Iterator<Integer> iterator调用方法的返回值 ,直接是Integer类型呢???
* 原因:在ArrayList中,这个iterator()方法: public Iterator<E> iterator() {return new Itr();}使用了泛型,
* 在实例化ArrayList时已经指定了泛型为Integer,所以这个方法中的泛型也是Integer。
*
* */
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
int stuScore = iterator.next();
System.out.println(stuScore);
}
}
//在集合中使用泛型的情况:以HashMap为例。Map集合有2值所以k v都要指定泛型。
@Test
public void test3(){
//定义Map接口时泛型就写了2个,所以实例化对象也要写2个。public interface Map<K,V> {}
//jdk7新特性:类型推断。 原来的写法:Map<String,Integer> map = new HashMap<String,Integer>();
Map<String,Integer> map = new HashMap<>();
map.put("Tom",87);
map.put("Jerry",87);
map.put("Jack",67);
// map.put(123,"ABC");
//解释1,泛型的嵌套:map.entrySet()返回的数据放在Set集合中,
// 第一层嵌套:Set集合里面是一个个的Entry对象,所以泛型是Entry类型
// 第二层嵌套:而Entry里面又有自己的泛型k v ,所以在进一步指定泛型。
//解释2:Map.Entry为什么是通过Map.来调用
// Entry为Map集合接口的一个内部接口,所以想要使用内部接口就需要Map.Entry一下才能使用。
// 如果想要省写为Set<Entry<String,Integer>> entry = map.entrySet();,那么在上面需要进行导包:import java.util.Map.*;
// 因为Map导了import java.util.*;,如果连Map都没导还是需要写全。
Set<Map.Entry<String,Integer>> entry = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();
while(iterator.hasNext()){
Map.Entry<String, Integer> e = iterator.next();
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key + "----" + value);
}
}
}
4.2.2 比较器中使用泛型
题目需求:
-
定义一个Employee类。
该类包含:private成员变量name,age,birthday,其中 birthday 为 MyDate 类的对象;
并为每一个属性定义 getter, setter 方法;
并重写 toString 方法输出 name, age, birthday -
MyDate类包含:
private成员变量year,month,day;并为每一个属性定义 getter, setter 方法; -
创建该类的 5 个对象,并把这些对象放入 TreeSet 集合中(TreeSet 需使用泛型来定义)
-
分别按以下两种方式对集合中的元素进行排序,并遍历输出:
1). 使Employee 实现 Comparable 接口,并按 name 排序
2). 创建 TreeSet 时传入 Comparator对象,按生日日期的先后排序。
Employee:
package com.atguigu01.use.exer1;
/**
* ClassName: Employee
* Description:
* 定义一个Employee类。
* 该类包含:private成员变量name,age,birthday,其中 birthday 为 MyDate 类的对象;
* 并为每一个属性定义 getter, setter 方法;
* 并重写 toString 方法输出 name, age, birthday
* @Author 尚硅谷-宋红康
* @Create 17:03
* @Version 1.0
*/
/**
* Comparable源码:
* public interface Comparable<T> {
* public int compareTo(T o);
* }
*/
public class Employee implements Comparable<Employee>{ //原先没有写泛型,说明是Object类型 想比较谁就写谁
private String name;
private int age;
private MyDate birthday;
public Employee(String name, int age, MyDate birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public Employee() {
}
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 MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}';
}
//指明泛型时的写法
@Override
public int compareTo(Employee o) {
return this.name.compareTo(o.name);
}
//没有指明泛型时的写法
//按 name 排序
// @Override
// public int compareTo(Object o) {
// if(o instanceof Employee){
// Employee e = (Employee)o;
// return this.name.compareTo(e.name);
// }
return 0;
// throw new RuntimeException("传入的数据类型不一致!");
// }
}
MyDate:
package com.atguigu01.use.exer1;
/**
* MyDate类包含:
private成员变量year,month,day;并为每一个属性定义 getter, setter 方法;
* @author shkstart
* @create 2019 上午 10:21
*/
public class MyDate implements Comparable<MyDate>{//想要让MyDate排序,写的肯定是MyDate类型
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public MyDate() {
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
//没有使用泛型的写法
// @Override
// public int compareTo(Object o) {
// if(o instanceof MyDate){
// MyDate m = (MyDate)o;
//
// //比较年
// int minusYear = this.getYear() - m.getYear();
// if(minusYear != 0){
// return minusYear;
// }
// //比较月
// int minusMonth = this.getMonth() - m.getMonth();
// if(minusMonth != 0){
// return minusMonth;
// }
// //比较日
// return this.getDay() - m.getDay();
// }
//
// throw new RuntimeException("传入的数据类型不一致!");
//
// }
//使用泛型的写法
@Override
public int compareTo(MyDate m) {
//比较年
int minusYear = this.getYear() - m.getYear();
if(minusYear != 0){
return minusYear;
}
//比较月
int minusMonth = this.getMonth() - m.getMonth();
if(minusMonth != 0){
return minusMonth;
}
//比较日
return this.getDay() - m.getDay();
}
}
EmployeeTest:
package com.atguigu01.use.exer1;
import org.junit.Test;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
/**
* 创建该类的 5 个对象,并把这些对象放入 TreeSet 集合中(下一章:TreeSet 需使用泛型来定义)
分别按以下两种方式对集合中的元素进行排序,并遍历输出:
1). 使Employee 实现 Comparable 接口,并按 name 排序
2). 创建 TreeSet 时传入 Comparator对象,按生日日期的先后排序。
*
* @author shkstart
* @create 2019 上午 10:23
*/
public class EmployeeTest {
//问题二:创建 TreeSet 时传入 Comparator对象,按生日日期的先后排序 (定制排序 自定义类)
@Test
public void test2(){
//泛型:往集合里面添谁就写谁(比较的是2个对象,只不过是按照对象中的属性进行排序)
Comparator<Employee> comparator = new Comparator<Employee>() { //匿名实现类的非匿名对象
//使用泛型以后的写法
@Override
public int compare(Employee o1, Employee o2) {
MyDate b1 = o1.getBirthday();
MyDate b2 = o2.getBirthday();
//方式一:自定义类的自然排序不符合或者就没有写自然排序,那么可以使用定制排序
// //比较年
// int minusYear = b1.getYear() - b2.getYear();
// if(minusYear != 0){
// return minusYear;
// }
// //比较月
// int minusMonth = b1.getMonth() - b2.getMonth();
// if(minusMonth != 0){
// return minusMonth;
// }
// //比较日
// return b1.getDay() - b2.getDay();
//方式二:自然排序就是按照日期进行排序,那么为什么不直接使用自然排序反而使用定制排序呢???
// 如果直接使用MyData类的自然排序,那么集合中添加的就应该是MyData类型。
// 现在集合中添加的是Employee类型,MyData类是Employee类中的一个属性private MyDate birthday,
// 你没有办法直接使用自然排序,只能是在定制排序中先调用birthday对象,之后在按照日期排序
return b1.compareTo(b2);//调用的是自定义类MyDate重写后的方法
}
/*
//使用泛型之前的写法
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Employee && o2 instanceof Employee){
Employee e1 = (Employee)o1;
Employee e2 = (Employee)o2;
MyDate b1 = e1.getBirthday();
MyDate b2 = e2.getBirthday();
//方式一:
// //比较年
// int minusYear = b1.getYear() - b2.getYear();
// if(minusYear != 0){
// return minusYear;
// }
// //比较月
// int minusMonth = b1.getMonth() - b2.getMonth();
// if(minusMonth != 0){
// return minusMonth;
// }
// //比较日
// return b1.getDay() - b2.getDay();
//方式二:
return b1.compareTo(b2);
}
// return 0;
throw new RuntimeException("传入的数据类型不一致!");
}
*/
};
TreeSet<Employee> set = new TreeSet<>(comparator);
Employee e1 = new Employee("liudehua",55,new MyDate(1965,5,4));
Employee e2 = new Employee("zhangxueyou",43,new MyDate(1987,5,4));
Employee e3 = new Employee("guofucheng",44,new MyDate(1987,5,9));
Employee e4 = new Employee("liming",51,new MyDate(1954,8,12));
Employee e5 = new Employee("liangzhaowei",21,new MyDate(1978,12,4));
set.add(e1);
set.add(e2);
set.add(e3);
set.add(e4);
set.add(e5);
Iterator<Employee> iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
//问题一:使Employee 实现 Comparable 接口,并按 name 排序(自然排序 自定义类)
@Test
public void test1(){
TreeSet<Employee> set = new TreeSet<Employee>();
Employee e1 = new Employee("liudehua",55,new MyDate(1965,5,4));
Employee e2 = new Employee("zhangxueyou",43,new MyDate(1987,5,4));
Employee e3 = new Employee("guofucheng",44,new MyDate(1987,5,9));
Employee e4 = new Employee("liming",51,new MyDate(1954,8,12));
Employee e5 = new Employee("liangzhaowei",21,new MyDate(1978,12,4));
set.add(e1);
set.add(e2);
set.add(e3);
set.add(e4);
set.add(e5);
Iterator<Employee> iterator = set.iterator();
while (iterator.hasNext()){
Employee employee = iterator.next();
System.out.println(employee);
}
}
}
4.3 自定义泛型结构
4.3.1 泛型的基础说明
1、<类型>这种语法形式就叫泛型。
-
<类型>的形式我们称为类型参数,这里的"类型"习惯上使用T表示,是Type的缩写。即:
<T>
。 -
<T>
:代表未知的数据类型,我们可以指定为<String>,<Integer>,<Circle>
等。- 类比方法的参数的概念,我们把
<T>
,称为类型形参,将<Circle>
称为类型实参,有助于我们理解泛型
- 类比方法的参数的概念,我们把
-
这里的T,可以替换成K,V等任意字母。
2、在哪里可以声明类型变量<T>
- 声明类或接口时,在类名或接口名后面声明泛型类型,我们把这样的类或接口称为
泛型类
或泛型接口
。
【修饰符】 class 类名<类型变量列表> 【extends 父类】 【implements 接口们】{
}
【修饰符】 interface 接口名<类型变量列表> 【implements 接口们】{
}
//例如:
public class ArrayList<E>
public interface Map<K,V>{
....
}
- 声明方法时,在【修饰符】与返回值类型之间声明类型变量,我们把声明了类型变量的方法,称为泛型方法。
[修饰符] <类型变量列表> 返回值类型 方法名([形参列表])[throws 异常列表]{
//...
}
//例如:java.util.Arrays类中的
public static <T> List<T> asList(T... a){
....
}
4.3.2 自定义泛型类或泛型接口
当我们在类或接口中定义某个成员时,该成员的相关类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型类、泛型接口。
说明:自定义泛型类和接口的用法相同,只是接口和类的区别
格式:
class A<T>{
}
interface B<T1,T2>{ //多个泛型参数用逗号隔开
}
1) 说明
① 我们在声明完自定义泛型类以后,可以在类的内部(比如:属性、方法、构造器中)使用类的泛型。
② 我们在创建自定义泛型类的对象时,可以指明泛型参数类型。一旦指明,内部凡是使用类的泛型参数的位置,都具体化为指定的类的泛型类型。
③ 如果在创建自定义泛型类的对象时,没有指明泛型参数类型,那么泛型将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
即:Order order = new Order(); 不等价于 Order<Object> order3 = new Order<>();
- 经验:泛型要使用一路都用。要不用,一路都不要用。
④ 泛型的指定中必须使用引用数据类型。不能使用基本数据类型,此时只能使用包装类替换。
⑤ 除创建泛型类对象外,子类继承泛型类时、实现类实现泛型接口时,也可以确定泛型结构中的泛型参数。比如:SubOrder2
如果我们在给泛型类提供子类时,子类也不确定泛型的类型,则可以继续使用泛型参数。比如:SubOrder3
我们还可以在现有的父类的泛型参数的基础上,新增泛型参数。比如:SubOrder4,SubOrder5
2) 注意
① 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
② 泛型类的构造器如下: public GenericClass(){}
而下面是错误的: public GenericClass<E>(){}
③ 泛型不同的引用不能相互赋值
④ JDK7.0 开始,泛型的简化操作:ArrayList<Fruit> flist = new ArrayList<>();
⑤ 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
⑥ 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
⑦ 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,但不可以在静态方法中使用类的泛型。(因为类的泛型是实例化创建对象时指定,静态资源是类加载时创建。)
⑧ 异常类不能是带泛型的。
⑨ 泛型类的普通方法中不能写try-catch
3) 举例
用到的资源类结构:
Person 普通类:
package com.atguigu02.selfdefine;
//定义普通类
public class Person {
}
Order 泛型父类:
package com.atguigu02.selfdefine;
import java.util.ArrayList;
//定义泛型类
public class Order<T>{
//声明了类的泛型参数以后,就可以在类的内部使用此泛型参数。
// 小技巧:可以把T看成是一个类型
T t;
int orderId;
// static T t1;
public Order() {
}
public Order(T t, int orderId) {
this.t = t;
this.orderId = orderId;
}
//如下的两个方法不是泛型方法
public T getT() { //不是
return t;
}
public void setT(T t) { //不是
this.t = t;
}
public int getOrderId() {
return orderId;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
@Override
public String toString() {
return "Order{" +
"t=" + t +
", orderId=" + orderId +
'}';
}
//不可以在静态方法中使用类的泛型 因为类的泛型是实例化创建对象时指定,静态资源是类加载时创建。
// public static void method1(){
// System.out.println("t : " + t);
// }
}
SubOrder1 子类
package com.atguigu02.selfdefine;
//SubOrder1不是泛型类!
public class SubOrder1 extends Order{
}
SubOrder2 子类
package com.atguigu02.selfdefine;
//SubOrder2不是泛型类
//是不是泛型类取决于有没有一个不确定的类型
public class SubOrder2 extends Order<Integer>{
}
SubOrder3 子类
package com.atguigu02.selfdefine;
//SubOrder3是泛型类
public class SubOrder3<T> extends Order<T>{
public void show(T t){
System.out.println(t);
}
}
SubOrder4 子类
package com.atguigu02.selfdefine;
//SubOrder4是泛型类
public class SubOrder4<E> extends Order<Integer>{//父类里面确定了泛型,子类自己又多了个不确定的类型
E e;
public SubOrder4() {
}
public SubOrder4(E e) {
this.e = e;
}
public SubOrder4(Integer integer, int orderId, E e) {
super(integer, orderId);
this.e = e;
}
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
}
SubOrder5 子类
package com.atguigu02.selfdefine;
//SubOrder5是泛型类
public class SubOrder5<T,E> extends Order<T>{//父类不确定的类型到子类中还不确定,并且子类又多了个不确定的类型
E e;
public SubOrder5() {
}
public SubOrder5(T t, int orderId, E e) {
super(t, orderId);
this.e = e;
}
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
}
测试类:GenericTest
package com.atguigu02.selfdefine;
import org.junit.Test;
import java.util.ArrayList;
public class GenericTest {
/**
* 1.测试普通类
* Person普通类结构:public class Person { }
*/
@Test
public void test1(){
//1.1 普通类在声明的时候没有定义泛型,所以实例化时也不能使用泛型
Person p1 = new Person(); //对
// Person<String> p2 = new Person<>(); //错
//1.2 系统提供的类在jdk1.5时都增加了泛型,所以实例化时就可以指明类的泛型参数的类型
ArrayList<String> list = new ArrayList<>();
list.add("AA"); //对
// list.add(123); //错
//1.3 系统提供的类定义了泛型,在实例化时可以使用泛型 也可以不用,没有使用的情况下认为是Object类型
ArrayList list1 = new ArrayList(); //向下兼容。jdk5.0之前集合是没有声明为泛型的。
list1.add(123);
list1.add("AA"); //认为是Object类型,所以都可以添加
}
/**
* 2.测试自定义的泛型类
* Order泛型类的结构:public class Order<T>{ }
*/
@Test
public void test2(){
//2.1 同样:自定义的类即便在定义的时候声明了泛型参数,在实例化的时候任然可以不使用,此时类型为Object类型
Order order = new Order(); //不等价于 Order<Object> order3 = new Order<>();
Object obj = order.getT();
//2.2 泛型参数在指明时,是不可以使用基本数据类型的!但是可以使用包装类替代基本数据类型。
// Order<int> order1 = new Order<>(); 错误
//在实例化时,可以指明类的泛型参数的具体类型!一旦指明了泛型的类型,则在泛型类中凡是使用泛型
// 参数的位置,都替换为指定的类型。(即:泛型类中有泛型变量、泛型方法,则在实例化对象 通过对象调属性方法的时候,都需要替换为对应的泛型)
Order<Integer> order2 = new Order<Integer>();
Integer t = order2.getT();
Order<String> order3 = new Order<>();
order3.setT("AA");
}
//3.测试:子类继承泛型父类时的各种情况
@Test
public void test3(){
/*
* 3.1 泛型父类结构:public class Order<T>{
* }
*
* 子类继承结构:public class SubOrder1 extends Order{
* }
* SubOrder1不是泛型类
* */
SubOrder1 sub1 = new SubOrder1();
Object t = sub1.getT(); //此时为Object类型
// SubOrder1<Integer> sub2 = new SubOrder1<>();//因为SubOrder1不是泛型类,此处编译错误
}
@Test
public void test4(){
/*
* 3.2 泛型父类结构:public class Order<T>{
* }
*
* 子类继承结构:public class SubOrder2 extends Order<Integer>{
* }
* SubOrder2不是泛型类:是不是泛型类取决于有没有一个不确定的类型
*
* 对于Order父类什么时候可以指明类型呢??
* 一:造对象的时候 Order<Integer> order2 = new Order<Integer>();
* 二:子类在继承Order父类的时候 public class SubOrder2 extends Order<Integer>{ }
* 一旦指定后,子类继承过来的属性和方法不再是不确定的类型,而是这个指明的类型。
* */
SubOrder2 sub2 = new SubOrder2();
Integer t = sub2.getT();//此时是Integer类型
/*
* 3.3 泛型父类结构:public class Order<T>{
* }
*
* 子类继承结构:public class SubOrder3<T> extends Order<T>{
* }
* SubOrder3是泛型类
* */
SubOrder3<String> sub3 = new SubOrder3<>();
String t1 = sub3.getT();
sub3.show("AA");
/*
* 3.4 泛型父类结构:public class Order<T>{
* }
*
* 子类继承结构:public class SubOrder4<E> extends Order<Integer>{ 父类里面确定了泛型,子类自己又多了个不确定的类型
* }
* SubOrder4是泛型类
* */
SubOrder4<String> sub4 = new SubOrder4<>();
Integer t2 = sub4.getT(); //使用T的时候是个确定的类型
String e = sub4.getE(); //使用E的时候是自己指明的类型
/*
* 3.5 泛型父类结构:public class Order<T>{
* }
*
* 子类继承结构:public class SubOrder5<T,E> extends Order<T>{//父类不确定的类型到子类中还不确定,并且子类又多了个不确定的类型
* }
* SubOrder5是泛型类
* */
SubOrder5<String,Integer> sub5 = new SubOrder5<>(); //泛型的顺序要一致
String t3 = sub5.getT(); //T是指定的String类型
Integer e1 = sub5.getE(); //E是指定的Integer类型
}
}
4.3.3 自定义泛型方法
如果我们定义类、接口时没有使用<泛型参数>,但是某个方法形参类型不确定时,这个方法可以单独定义<泛型参数>。
1) 说明
- 泛型方法的格式:
//通常在形参列表或返回值类型的位置会出现泛型参数T
// <泛型>只是为了标识此方法为泛型方法
[访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称]) [抛出的异常]{
}
eg:
public <E> E get(int id, E e) {
E result = null;
return result;
}
- 声明泛型方法时,一定要添加泛型参数
- 方法,也可以被泛型化,与其所在的类是否是泛型类没有关系。
- 泛型方法中的泛型参数在方法被调用时确定。
- 泛型方法可以根据需要,声明为static的。
- 泛型方法可以声明为静态的。泛型类中的方法不可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
2) 举例
举例1:泛型方法所属的类是否是一个泛型类,都可以
public class DAO {
public <E> E get(int id, E e) {
E result = null;
return result;
}
}
举例2:
Order泛型类:
package com.atguigu02.selfdefine;
import java.util.ArrayList;
//定义泛型类
public class Order<T>{
//声明了类的泛型参数以后,就可以在类的内部使用此泛型参数。
// 小技巧:可以把T看成是一个类型
T t;
int orderId;
public Order() {
}
public Order(T t, int orderId) {
this.t = t;
this.orderId = orderId;
}
//如下的两个方法不是泛型方法
//在泛型类的方法中,使用了类的泛型参数。此方法不一定是泛型方法
public T getT() { //不是
return t;
}
public void setT(T t) { //不是
this.t = t;
}
public int getOrderId() {
return orderId;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
@Override
public String toString() {
return "Order{" +
"t=" + t +
", orderId=" + orderId +
'}';
}
//自定义泛型方法
public <E> E method(E e){
return null;
}
//定义泛型方法,将E[]数组元素添加到对应类型的ArrayList中,并返回
//注意:数组元素是E类型,那么返回值也应该是ArrayList<E>,但是这样写还不对 编译器会误认为这个E是类如String一个具体的类型,
// 而不会认为是一个参数 我们想要的是当作参数,在调用的时候进行赋值,解决需要在方法的返回值类型前面加上<E>才会当做是参数来看。
public <E> ArrayList<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
//定义泛型方法,将E[]数组元素添加到对应类型的ArrayList中,并返回 (泛型方法可以定义为静态的,通过类名调用)
//泛型方法,可以声明为静态的。泛型类中的方法不可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
public static <E> ArrayList<E> copyFromArrayToList1(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
}
GenericTest:
package com.atguigu02.selfdefine;
import org.junit.Test;
import java.util.ArrayList;
public class GenericTest {
//测试泛型方法的使用
@Test
public void test5(){
Order<String> order1 = new Order<>();
Integer[] arr = new Integer[]{1,2,3,4,5};
//泛型参数在方法调用时,指明其具体的类型
ArrayList<Integer> list = order1.copyFromArrayToList(arr);
for(Integer i :list){
System.out.println(i);
}
}
//测试泛型方法的使用
@Test
public void test6(){
Order<String> order1 = new Order<>();
Integer[] arr = new Integer[]{1,2,3,4,5};
//泛型参数在方法调用时,指明其具体的类型
ArrayList<Integer> list = Order.copyFromArrayToList1(arr);//通过类名调用静态的泛型方法
for(Integer i :list){
System.out.println(i);
}
}
}
3) 练习
练习1: 泛型方法
编写一个泛型方法,实现任意引用类型数组指定位置元素交换。
public static void method1( E[] e,int a,int b)
/**
* @author 尚硅谷-宋红康
* @create 9:11
*/
public class Exer01 {
//编写一个泛型方法,实现任意引用类型数组指定位置元素交换。
public static <E> void method( E[] arr,int a,int b){
E temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
@Test
public void testMethod(){
Integer[] arr = new Integer[]{10,20,30,40};
method(arr,2,3);
for(Integer i : arr){
System.out.println(i);
}
}
}
4.3.4 泛型类和泛型方法的使用场景(同样的Dao)
说明:
- 通常一张数据库中的表对应,一个实体类 ,一个Dao层
场景分析:此时有2张表(customers、order),对应2个实体类(Customer、Order),对应2个Dao层(CustomerDAO、OrderDAO),此时每张表都要进行crud操作。
- 原先: 每个Dao层都要写一遍crud的代码,2个dao层的代码几乎相同,只不过是用到的类不同。
- 缺点:这样造成每个Dao层都有很多相似的代码,代码冗余(即编程时不必要的代码段)。
- 解决:
- 使用泛型定义一个通用的Dao层,在里面编写这些通用的代码,凡是用到类的地方都替换为泛型参数
- 之后使用子类继承这个泛型父类,同时给这个泛型参数赋值为一个具体的对象类型
- 好处:这样只需要在父类Dao中编写一次crud的代码,子类dao继承泛型父类,并指定具体的对象类型赋值给泛型参数,简写了代码。每个子类的dao还可以编写每张表特有的功能。
Customer:实体类 对应数据库中的customer表
package com.atguigu02.selfdefine.apply;
import java.sql.Date;
/**
* ClassName: Customer
* Description:
*
* ORM思想(object relational mapping)
* 数据库中的一个表 与 Java中的一个类对应
* 表中的一条记录 与 Java类的一个对象对应
* 表中的一个字段(或列) 与 Java类的一个属性(或字段)对应
*
* @Author 尚硅谷-宋红康
* @Create 10:04
* @Version 1.0
*/
public class Customer {
int id;
String name;
String email;
Date birth;
}
Order:实体类 对应数据库中的order表
package com.atguigu02.selfdefine.apply;
import java.sql.Date;
/**
* ClassName: Order
* Description:
*
* @Author 尚硅谷-宋红康
* @Create 10:07
* @Version 1.0
*/
public class Order {
int orderId;
String orderName;
Date orderDate;
}
DAO:通用数据层
package com.atguigu02.selfdefine.apply;
import java.util.List;
/**
* DAO:data(base) access object。内部封装了操作数据库相关表的增删改查操作。(CRUD)
*
*/
public class DAO<T> { //表的共性操作的DAO
//增
public void insert(T bean){
//通过相应的sql语句,将bean对象的属性值写入到数据表中。
}
//删
public T deleteById(int id){
//略
return null;
}
//改
public void update(int id,T bean){
//略
}
//查
//查询一条记录
public T queryForInstance(int id){
//略
return null;
}
//查询多条记录构成的集合
public List<T> queryForList(int id){
return null;
}
//定义泛型方法
//比如:查询表中的记录数。(E:Long类型)
//比如:查询表中最大的生日。(E:Date类型)
public <E> E getValue(String sql){
return null;
}
}
CustomerDAO:Customer特有的数据层
package com.atguigu02.selfdefine.apply;
/**
* ClassName: CustomerDAO
* Description:
*
* @Author 尚硅谷-宋红康
* @Create 10:12
* @Version 1.0
*/
public class CustomerDAO extends DAO<Customer>{//只能操作某一个表的DAO
}
OrderDAO:Order特有的数据层
package com.atguigu02.selfdefine.apply;
/**
* ClassName: OrderDAO
* Description:
*
* @Author 尚硅谷-宋红康
* @Create 10:13
* @Version 1.0
*/
public class OrderDAO extends DAO<Order>{//只能操作某一个表的DAO
}
DAOTest:测试类
package com.atguigu02.selfdefine.apply;
import org.junit.Test;
import java.util.List;
/**
* ClassName: DAOTest
* Description:
*
* @Author 尚硅谷-宋红康
* @Create 10:12
* @Version 1.0
*/
public class DAOTest {
@Test
public void test1(){
/*
* public class DAO<T> { } 父类 泛型类
* public class CustomerDAO extends DAO<Customer>{ } 子类 不是泛型类
* 子类在继承时实例化了泛型参数的类型为Customer,
* 所以此时实例化对象调用方法时用到泛型的地方,都限定泛型的类型为Customer。
* */
CustomerDAO dao1 = new CustomerDAO();//new对象的时候能不能用泛型取决于此类是不是泛型类
dao1.insert(new Customer());
Customer customer = dao1.queryForInstance(1);
}
@Test
public void test2(){
OrderDAO dao1 = new OrderDAO();
dao1.insert(new Order());
List<Order> list = dao1.queryForList(1);
}
}
4.4 泛型在继承上的体现
如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G<B>并不是G<A>
的子类型!
比如:String是Object的子类,但是List<String>并不是List<Object>
的子类。
package com.atguigu01.use;
import org.junit.Test;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
/**
* 泛型在继承方面的体现:
* 情况一:虽然类A是类B的父类,但是G<A> 和G<B>二者不具备子父类关系,二者是并列关系。
* 情况二::类A是类B的父类,A<G> 是 B<G> 的父类
*/
public class GenericTest {
//情况一:类SuperA是类A的父类,则G<SuperA> 与 G<A>的关系:G<SuperA> 和 G<A>是并列的两个类,没有任何子父类的关系。
// 比如:ArrayList<Object> 、ArrayList<String>没有关系
@Test
public void test1(){
//父子类,子类类型可以赋值给父类。
Object obj = null;
String str = null;
obj = str; //基于继承性的多态的使用
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2; //基于继承性的多态的使用
//编译不通过01
// Date date = new Date();
// str = date;
List<Object> list1 = null;
List<String> list2 = new ArrayList<String>();
//此时的list1和list2的类型不具有子父类关系
//编译不通过02,类似于01的写法,list1和list2里面的元素确实是父子类关系,但是list1和list2本身不是父子类关系 顶多是并列关系。
// list1 = list2;
/*
反证法:假设list1 = list2;
list1.add(123);导致混入非String的数据出错。
解释反证法:list1 list2都是引用类型,list1 = list2相当于是 list1和list2都指向堆空间中的对象newArrayList,
那么此时list1调用时也不会报空指针异常,在list1.add(123)添加数据时,由于list1是Object类型所以任何数据都能添加 ,
list1添加integer类型的数据123 就相当于像lis2中添加123它们都指向ArrayList,而ArrayList又要求是String类型的
所以报错。
*/
show(list1);//只能是Object类型
show1(list2);//只能是String类型
}
public void show1(List<String> list){
}
public void show(List<Object> list){
}
//情况二:类SuperA是类A的父类或接口,SuperA<G> 与 A<G>的关系:SuperA<G> 与A<G> 有继承或实现的关系。
// 即A<G>的实例可以赋值给SuperA<G>类型的引用(或变量)
// 比如:List<String> 与 ArrayList<String>
@Test
public void test2(){
AbstractList<String> list1 = null;
List<String> list2 = null;
ArrayList<String> list3 = null;
list1 = list3;
list2 = list3;
List<String> list4 = new ArrayList<>();
}
}
4.5 通配符的使用
当我们声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,例如:Comparator<T>
类型,但是我们仍然无法确定这个泛型类或泛型接口的类型变量<T>
的具体类型,此时我们考虑使用类型通配符 ? 。
4.5.1 通配符的理解
使用类型通配符:?
比如:List<?>
,Map<?,?>
List<?>
是List<String>
、List<Object>
等各种泛型List的父类。
4.5.2 使用注意点
注意点1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?
public static <?> void test(ArrayList<?> list){
}
注意点2:编译错误:不能用在泛型类的声明上
class GenericTypeClass<?>{
}
注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象
ArrayList<?> list2 = new ArrayList<?>();
4.5.3 测试
Person 父类:
package com.atguigu.java1;
public class Person {
}
Student 子类:
package com.atguigu.java1;
public class Student extends Person{
}
测试类:
package com.atguigu.java1;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GenericTest {
/*
通配符的使用:
通配符:?
类A是类B的父类,G<A>和G<B>是没有关系的,二者共同的父类是:G<?>
*/
@Test
public void test3(){
//List<Object>,List<String>是同级关系,List<?>是它们2个的共同父类
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
//编译通过
// print(list1);
// print(list2);
//通配符的读入和写出操作要求
List<String> list3 = new ArrayList<>();
list3.add("AA");
list3.add("BB");
list3.add("CC");
list = list3;
//添加(写入):对于List<?>就不能向其内部添加数据。 不可以往不确定的类型中添加数据。
//除了添加null之外。 集合中添加的是引用类型,不管是什么样的引用类型都可以赋值为null 默认值
// list.add("DD");
// list.add('?');
list.add(null);
//获取(读取):允许读取数据,读取的数据类型为Object。
// 因为不管获取到的是什么类型的元素都可以复值给它的父类Object
Object o = list.get(0);
System.out.println(o);
}
public void print(List<?> list){
Iterator<?> iterator = list.iterator();
while(iterator.hasNext()){
//List<?> 里面的数据如何接收呢???
//答:不管传入的是啥类型都可以使用Object来接收
Object obj = iterator.next();
System.out.println(obj);
}
}
/*
3.有限制条件的通配符的使用。
情况1: <? extends Number> (无穷小 , Number] 即 <=
只允许泛型为Number及Number子类的引用调用
情况2:<? super Number> [Number , 无穷大) 即 >=
只允许泛型为Number及Number父类的引用调用
情况3:<? extends Comparable> 可以看成是情况1
只允许泛型为实现Comparable接口的实现类的引用调用
*/
@Test
public void test4(){
List<? extends Person> list1 = null;// <=Person
List<? super Person> list2 = null;// >=Person
List<Student> list3 = new ArrayList<Student>();
List<Person> list4 = new ArrayList<Person>();
List<Object> list5 = new ArrayList<Object>();
//测试extends:list1可以被 list3 list4赋值成功,list5不能赋值成功。必须是Person的子类或者它本身类型(小于等于list1中的person)
list1 = list3;
list1 = list4;
// list1 = list5;
//测试super:list2可以被list4 list5赋值成功,list3不能赋值成功。必须是Person的父类或者它本身类型(大于等于list2中的person)
// list2 = list3;
list2 = list4;
list2 = list5;
//读取数据:可以读取,只不过要注意接收的类型
list1 = list3;
Person p = list1.get(0);//list1是<=Person,所以最小可以使用Person来接收 (因为接收是用的它的父类)
// 编译不通过
//Student s = list1.get(0);//不能用Student类型接收 Student比Person还小不能接收
list2 = list4;
Object obj = list2.get(0);//list2是>=Person,Person是最小的,比Person还大的数据只能使用Object来接收
//编译不通过
// Person obj = list2.get(0);
//写入数据: 同样null肯定可以写入,对应非null的数据,只要理解2种方式父子类的关系就明白了
//编译不通过 list1代表[-∞,Person] 此时Person是最大的类,list1可以是比Person小很多的类,小到比Student还小,此时把大类型Student赋值给小类型当然报错。
// list1.add(new Student());
//编译通过 list2代表[Person,+∞],此时Person是最小的类,list2别的数据肯定比Person更大,我new的是比Person还小的类,这个小的类当然可以赋值给大的类型list2了。
list2.add(new Person());
list2.add(new Student());
}
}
4.5.4 泛型应用举例(实体类中通用的对象属性)
举例1:泛型嵌套
public static void main(String[] args) {
HashMap<String, ArrayList<Citizen>> map = new HashMap<String, ArrayList<Citizen>>();
ArrayList<Citizen> list = new ArrayList<Citizen>();
list.add(new Citizen("赵又廷"));
list.add(new Citizen("高圆圆"));
list.add(new Citizen("瑞亚"));
map.put("赵又廷", list);
Set<Entry<String, ArrayList<Citizen>>> entrySet = map.entrySet();
Iterator<Entry<String, ArrayList<Citizen>>> iterator = entrySet.iterator();
while (iterator.hasNext()) {
Entry<String, ArrayList<Citizen>> entry = iterator.next();
String key = entry.getKey();
ArrayList<Citizen> value = entry.getValue();
System.out.println("户主:" + key);
System.out.println("家庭成员:" + value);
}
}
举例2:个人信息设计
用户在设计类的时候往往会使用类的关联关系,例如,一个人中可以定义一个信息的属性,但是一个人可能有各种各样的信息(如联系方式、基本信息等),所以此信息属性的类型就可以通过泛型进行声明,然后只要设计相应的信息类即可。
注意
- 原来的写法:
class Person<? extends Info>{ }
- 即:<=Person
- 这里实体类Person的写法:
class Person<T extends Info>{ }
- 这里写的是T不是?通配符,所以不是父类的情况而是一个具体的类型
- 表示 new Person对象的时候,T指明的类型必须是Info的子类
- 这样在new不同的子类对象时就可以根据泛型,动态的设置不同人的个人信息。
interface Info{ // 只有此接口的子类才是表示人的信息
}
class Contact implements Info{ // 表示联系方式
private String address ; // 联系地址
private String telephone ; // 联系方式
private String zipcode ; // 邮政编码
public Contact(String address,String telephone,String zipcode){
this.address = address;
this.telephone = telephone;
this.zipcode = zipcode;
}
public void setAddress(String address){
this.address = address ;
}
public void setTelephone(String telephone){
this.telephone = telephone ;
}
public void setZipcode(String zipcode){
this.zipcode = zipcode;
}
public String getAddress(){
return this.address ;
}
public String getTelephone(){
return this.telephone ;
}
public String getZipcode(){
return this.zipcode;
}
@Override
public String toString() {
return "Contact [address=" + address + ", telephone=" + telephone
+ ", zipcode=" + zipcode + "]";
}
}
class Introduction implements Info{
private String name ; // 姓名
private String sex ; // 性别
private int age ; // 年龄
public Introduction(String name,String sex,int age){
this.name = name;
this.sex = sex;
this.age = age;
}
public void setName(String name){
this.name = name ;
}
public void setSex(String sex){
this.sex = sex ;
}
public void setAge(int age){
this.age = age ;
}
public String getName(){
return this.name ;
}
public String getSex(){
return this.sex ;
}
public int getAge(){
return this.age ;
}
@Override
public String toString() {
return "Introduction [name=" + name + ", sex=" + sex + ", age=" + age
+ "]";
}
}
class Person<T extends Info>{
private T info ;
public Person(T info){ // 通过构造器设置信息属性内容
this.info = info;
}
public void setInfo(T info){
this.info = info ;
}
public T getInfo(){
return info ;
}
@Override
public String toString() {
return "Person [info=" + info + "]";
}
}
public class GenericPerson{
public static void main(String args[]){
Person<Contact> per = null ; // 声明Person对象
per = new Person<Contact>(new Contact("北京市","01088888888","102206")) ;
System.out.println(per);
Person<Introduction> per2 = null ; // 声明Person对象
per2 = new Person<Introduction>(new Introduction("李雷","男",24));
System.out.println(per2) ;
}
}
5. 集合—01
5.1 集合相关说明
5.1.1 出现背景
-
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中) -
数组在存储多个数据方面的特点:
2.1 一旦初始化以后,其长度就确定了。
2.2 数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。如果想要数组里面可以写任意类的数据 则可以定义为Object类型。比如:String[ ] arr;int[ ] arr1;Object[ ] arr2; -
数组在存储多个数据方面的缺点:
3.1 一旦初始化以后,其长度就不可修改。
3.2 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
3.3 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用。(length只是获取数组能存几个数,而不是实际存的个数)
3.4 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。 -
以上缺点集合都能解决。
5.1.2 使用场景
5.1.3 java集合的分类
集合框架:都在util包中。
|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储有序的、可重复的数据。 -->“动态”数组
|----ArrayList 实现类
|----LinkedList
|----Vector
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
|----HashSet
|----LinkedHashSet
|----TreeSet
|----Map接口:双列集合,用来存储一对(key - value)一对的数据 -->高中函数:y = f(x)
|----HashMap
|----LinkedHashMap
|----Hashtable
|----Properties
|----TreeMap
5.2 Collection接口
5.2.1 概述
说明:
- 英文名称Collection,是用来存放Object对象类型的数据结构,其中长度可变,并提供了一组操作成批对象的方法。
- 集合里面存的是Object对象类型的数据结构 即存放的都是任意引用类型的数据,存放基本类型是因为自动装箱功能。集合可以存放各种类型数据这点不好,所以在jdk1.5引用了泛型用来控制类型统一。
- 注意,集合不像数组那样不能直接输出,如果集合中存的数据是系统提供的类,里面自动重写了toString方法,所以可以直接输出。
java集合为什么要遍历???
- 是因为遍历能够访问集合中的每个元素,并对元素进行相应的操作、处理和查找,以满足不同的需求。
- 不遍历直接输出集合,只能够看到集合中的元素是什么,无法对集合里面的元素进行操作。
5.2.2 特点
单列集合
,用来存储一个一个的对象
。- 集合中的数据类型都为 Object类型的引用数据类型,对于基本数据类型能够储存是因为有自动装箱功能变为包装类。
- 遍历方式2种:迭代器,增强for循环(又叫作foreach循环)。也可以直接调用foreach方法进行遍历,详情查看jdk1.8新特性
5.2.3 Collection接口的继承结构
|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储有序的、可重复的数据。 -->“动态”数组
|----ArrayList 实现类
|----LinkedList
|----Vector
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
|----HashSet
|----LinkedHashSet
|----TreeSet
5.2.4 Collection接口的常用方法(集合数组互转)
说明:暂时学习jdk8之前的常用方法(一共15个),其余的到jdk新特性时讲解。
CollectionTest 类:
package com.atguigu.java2;
import org.junit.Test;
import java.util.*;
/**
* Collection接口中的方法的使用:
*
* 结论:
* 向 Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().
*
* @author shkstart
* @create 2019 下午 4:08
*/
public class CollectionTest {
@Test
public void test1(){
Collection coll = new ArrayList();//因为是接口,测试里面的方法只能是多态形式
//1. boolean add(Object e):将元素e添加到集合coll中
coll.add("AA");
coll.add("BB");
coll.add(123);//自动装箱,Object指的是任何引用对象类型,这里填基本数据类型是因为自动装箱为包装类。
coll.add(new Date());
//2. int size():获取添加的元素的个数
System.out.println(coll.size());//4
//3. boolean addAll(Collection coll1):将coll1集合中的元素添加到当前的集合中
Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add("CC");
coll.addAll(coll1);
System.out.println(coll.size());//6
System.out.println(coll);//[AA, BB, 123, Fri Jan 28 21:08:38 CST 2022, 456, CC]
//4. void clear():清空集合元素,但对象还存在
coll.clear();
//5. boolean isEmpty():判断当前集合是否为空
System.out.println(coll.isEmpty());//true
}
@Test
public void test2(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
// Person p = new Person("Jerry",20);
// coll.add(p);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
//6.boolean contains(Object obj):判断当前集合中是否包含obj
//我们在判断时会调用obj对象所在类的equals()。
boolean contains = coll.contains(123);
System.out.println(contains);//true
System.out.println(coll.contains(new String("Tom")));
// System.out.println(coll.contains(p));//true,String默认重写过了equals方法,此时比较的是对象里面的内容,所以为true
//解释:自己定义的类如果没重写比较的是地址,所以为false,开发中一般要求向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().
System.out.println(coll.contains(new Person("Jerry",20)));//false -->true
//7.boolean containsAll(Collection coll1):判断形参coll1中的所有元素是否都存在于当前集合中。
Collection coll1 = Arrays.asList(123,4567);//创建集合的,返回值为List,此时用Collection接收
System.out.println(coll.containsAll(coll1));
}
@Test
public void test3(){
//8.boolean remove(Object obj):从当前集合中移除obj元素。
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
coll.remove(1234);//仍然会调用equals方法,因为数据为包装类型,底层重写了equals方法,所以会移除成功,返回true。
System.out.println(coll);
coll.remove(new Person("Jerry",20));//自己写的类重写了equals方法,所以也能移除成功。返回true.
System.out.println(coll);
//9. boolean removeAll(Collection coll1):差集:从当前集合中移除coll1中所有的元素。
Collection coll1 = Arrays.asList(123,456);
coll.removeAll(coll1);
System.out.println(coll);
}
@Test
public void test4(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
//10.boolean retainAll(Collection coll1):交集:获取当前集合和coll1集合的交集,并返回给当前集合(不会新创建集合,和数组不同)
// Collection coll1 = Arrays.asList(123,456,789);
// coll.retainAll(coll1);
// System.out.println(coll);
//11.boolean equals(Object obj):比较2个对象是否相等:要想返回true,需要当前集合和形参集合的元素都相同。
Collection coll1 = new ArrayList();
coll1.add(456);//考虑顺序问题,如果顺序不一致为false.因为list是有序的。如果是set集合在这个地方就不需要考虑顺序了 为true。
coll1.add(123);
coll1.add(new Person("Jerry",20));
coll1.add(new String("Tom"));
coll1.add(false);
System.out.println(coll.equals(coll1));
}
@Test
public void test5(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
//12.int hashCode():返回当前对象的哈希值
System.out.println(coll.hashCode());
//13.集合 --->数组:Object[] toArray()
Object[] arr = coll.toArray();
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
//拓展:数组 --->集合:调用Arrays类的静态方法static <T> List<T> asList(T... a) 只考虑和List集合互转,因为List本质上就是动态数组(长度可以改变)。
List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(list);
List arr1 = Arrays.asList(new int[]{123, 456});
//此方法还提供了一种方便的方式来创建一个初始化为包含几个元素的固定大小的列表:
// List arr1 = Arrays.asList(123, 456);
System.out.println(arr1.size());//1,这种写法认为数组只有一个元素
List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2,写为包装类型,才认为是2个元素
//14.iIterator<E> terator():返回Iterator接口的实例,用于遍历集合元素。--->放在IteratorTest.java中测试
//15. <T> T[] toArray(T[] a) 返回包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型。--->泛型时讲
}
}
Person 类:
package com.atguigu.java2;
import java.util.Objects;
/**
* @author shkstart
* @create 2019 上午 10:06
*/
public class Person {
private String name;
private int age;
public Person() {
}
public Person(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 String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
System.out.println("Person equals()....");
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
5.2.5 使用迭代器Iterator接口
迭代器的执行原理:
package com.atguigu.java2;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* 集合元素的遍历操作,使用迭代器Iterator接口。迭代器只适合于collection,不适用于Map.
* 1.内部的方法:hasNext() 和 next()
* 2.集合对象每次调用iterator()方法都得到一个全新的迭代器对象,
* 默认游标都在集合的第一个元素之前。
* 3.内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove(),为迭代器提供的方法。
*
* @author shkstart
* @create 2019 上午 10:44
*/
public class IteratorTest {
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
Iterator iterator = coll.iterator();
//方式一:开发中不会这么写。 Object next() 返回迭代中的下一个元素。
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// //如果下条元素不存在,则报异常:NoSuchElementException
// System.out.println(iterator.next());
//方式二:不推荐,因为推荐使用hsahNext()方法
// for(int i = 0;i < coll.size();i++){
// System.out.println(iterator.next());
// }
//方式三:推荐
boolean hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
//next():①指针下移 ②将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
}
@Test
public void test2(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
//错误方式一:会跳着输出,并且报异常
// Iterator iterator = coll.iterator();
// while((iterator.next()) != null){
// System.out.println(iterator.next());
// }
//错误方式二:死循环,一直输出 123
//集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
while (coll.iterator().hasNext()){
System.out.println(coll.iterator().next());
}
}
//测试Iterator中的remove()
//如果还未调用next() (指针问题)或在上一次调用 next 方法之后已经调用了 remove 方法(不能重复删除),
// 再调用remove都会报IllegalStateException。原因:未调用next方法 此时指针在最上方没有数据不能移除,调用了 remove 方法再次调用没有数据无法在移除
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
//删除集合中"Tom"
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
// iterator.remove();
Object obj = iterator.next();
if("Tom".equals(obj)){
iterator.remove();
// iterator.remove();
}
}
//遍历集合
iterator = coll.iterator();//因为上面遍历集合,指针已经到最后一位了。重写遍历需要一个新的迭代器对象 默认游标都在集合的第一个元素之前。
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
5.2.6 新特性:foreach(增强for循环)遍历: 集合,数组
package com.atguigu.java2;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
/**
* jdk 5.0 新增了foreach循环,用于遍历集合、数组
*
* @author shkstart
* @create 2019 上午 11:24
*/
public class ForTest {
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
//for(集合元素的类型 局部变量 : 集合对象)
//内部仍然调用了迭代器。
//执行流程:取第一个元素赋给obj,之后打印obj.取第二个......
//快捷键:集合的名字+for回车
for(Object obj : coll){
System.out.println(obj);
}
}
@Test
public void test2(){
int[] arr = new int[]{1,2,3,4,5,6};
//for(数组元素的类型 局部变量 : 数组对象)
for(int i : arr){
System.out.println(i);
}
}
//练习题
@Test
public void test3(){
String[] arr = new String[]{"MM","MM","MM"};
// //方式一:普通for赋值 输出为GG,因为改变的是数组本身的一个个元素。
// for(int i = 0;i < arr.length;i++){
// arr[i] = "GG";
// }
//方式二:增强for循环 输出为GG,因为把值赋给了s,s把值改变了.
for(String s : arr){
s = "GG";
}
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
}
}
5.3 Collection子接口之一:List接口
5.3.1 概述
有序:存储的数据在底层数组中按照数组索引的顺序添加
- 有序,可重复。
- 以存储多个null值。
- 元素都有索引。
- 遍历方式4种:迭代器,子接口迭代器,增强for循环,普通for循环。
5.3.2 继承结构
List接口框架:
|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储有序的、可重复的数据。 -->“动态”数组,替换原有的数组
|----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData;数组存储
|----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
|----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData;数组存储
5.3.3 测试常用方法。
说明:List接口产生了特有方法,都是按照索引操作的方法,可以存储多个null。
package com.atguigu.java2;
import org.junit.Test;
import java.util.*;
/**
* 1. ArrayList的源码分析:
* 1.1 jdk 7情况下
* ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
* list.add(123);//elementData[0] = new Integer(123);
* ...
* list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
* 默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
*
* 结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity),创建时指定大小 避免扩容影响效率。
*
* 1.2 jdk 8中ArrayList的变化:
* ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
*
* list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
* ...
* 后续的添加和扩容操作与jdk 7 无异。
* 1.3 小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象
* 的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
*
* 2. LinkedList的源码分析:
* LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
* list.add(123);//将123封装到Node中,创建了Node对象。
*
* 其中,Node定义为:体现了LinkedList的双向链表的说法
* private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
*
* 3. Vector的源码分析:jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
* 在扩容方面,默认扩容为原来的数组长度的2倍。
*
* 面试题:ArrayList、LinkedList、Vector三者的异同?
* 同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
* 不同:见上
*
*
*
* 4. List接口中的特有方法,都是按照索引操作,可以存储多个null。
*
* @author shkstart
* @create 2019 上午 11:39
*/
public class ListTest {
/*
void add(int index, Object ele):在index位置插入ele元素
boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index, Object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
总结:常用方法,包括collection的方法。
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:① Iterator迭代器方式
② 增强for循环
③ 普通的循环,因为有索引所以可以使用普通for循环
*/
@Test
public void test3(){
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
//方式一:Iterator迭代器方式
Iterator iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("***************");
//方式二:ListIterator为Iterator接口的子接口,可以逆向遍历但几乎不咋用
ListIterator it2 = list.listIterator() ;
while( it2.hasNext() ) {//判断是否有 后一个 元素
System.out.println( it2.next());//获取后一个 元素
}
//方式三:增强for循环
for(Object obj : list){
System.out.println(obj);
}
System.out.println("***************");
//方式四:普通for循环
for(int i = 0;i < list.size();i++){
System.out.println(list.get(i));
}
}
@Test
public void test2(){
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("Tom",12));
list.add(456);
//4.int indexOf(Object obj):返回obj在集合中首次出现的位置。如果不存在,返回-1. 类似于String中的indexof的方法
int index = list.indexOf(4567);
System.out.println(index);
//5.int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置。如果不存在,返回-1.
System.out.println(list.lastIndexOf(456));
//6.Object remove(int index):移除指定index位置的元素,并返回此元素(相当于重载的collection中的remove方法,之前是根据元素删除,这是根据下标删除)
Object obj = list.remove(0);
System.out.println(obj);
System.out.println(list);
//7.Object set(int index, Object ele):设置指定index位置的元素为ele
list.set(1,"CC");
System.out.println(list);
//8.List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的左闭右开区间的子集合
List subList = list.subList(2, 4);
System.out.println(subList);
System.out.println(list);
}
@Test
public void test1(){
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("Tom",12));
list.add(456);
System.out.println(list);
//1.void add(int index, Object ele):在index位置插入ele元素
list.add(1,"BB");
System.out.println(list);
//2.boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
List list1 = Arrays.asList(1, 2, 3);
list.addAll(list1); //有几个是几个
// list.add(list1); 看成一个元素
System.out.println(list.size());//9
//3.Object get(int index):获取指定index位置的元素
System.out.println(list.get(0));
}
}
5.3.4 面试题
package com.atguigu.exer;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
/**
* @author shkstart
* @create 2019 下午 3:33
*/
public class ListExer {
/*
区分List中remove(int index)和remove(Object obj)
*/
@Test
public void testListRemove() {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);//[1,2]
}
private void updateList(List list) {
// list.remove(2); 直接写用的是根据下标进行删除
list.remove(new Integer(2));//手动装箱后才是根据元素进行删除
}
}
5.4 List接口实现类之一:ArrayList
5.4.1 概述
- 是List接口的实现类,可以使用父接口List的功能也可以使用父父接口Collection的功能 , 没有产生特有方法。
- 底层封装了Object类型的数组存放数据,封装了数组的操作,每个对象都有下标。
- 内部数组默认初始容量是10。如果不够会以1.5倍容量增长。List接口的大小由可变数组的实现。每个 ArrayList 实例都有一个容量。该容量是指用来存储列表元素的数组的大小。
- 查询快,增删数据效率会降低。
- 遍历方式4种:迭代器,子接口迭代器,增强for循环,普通for循环。
凡是用数组的地方都可以替换为ArrayList。
5.4.2 创建对象
new ArrayList()://初始容量是10的空列表
5.5 List接口实现类之二:LinkedList
5.5.1 概述
双向链表,两端效率高。底层就是数组和链表实现的。(增加了特有方法,一般都是和首位相关的方法。) 有下标,但在空间散乱排布,所以查询慢。
双向链表:下标遍历效率低,迭代器遍历效率高(4种遍历方式,除了用下标的普通for循环速度慢,另外3种都可以。)
5.5.2 特点
- 底层维护了一个链表结构.空间不连续.也就造成查询业务效率低.
- 链表结构适用于新增或者删除业务.
- 链表上有两个高效节点:首元素和尾元素
- 创建对象 LinkedList() 构造一个空列表。
5.5.3 特有方法
void addFirst(E e)
将指定元素插入此列表的开头。
void addLast(E e)
将指定元素添加到此列表的结尾。
E getFirst()
返回此列表的第一个元素。
E getLast()
返回此列表的最后一个元素。
E removeFirst()
移除并返回此列表的第一个元素
E removeLast()
移除并返回此列表的最后一个元素。
//用上面那套方法add就行,2种都可以。
boolean offerFirst(E e)
在此列表的开头插入指定的元素。
boolean offer(E e)
将指定元素添加到此列表的末尾(最后一个元素)。
boolean offerLast(E e)
在此列表末尾插入指定的元素。
E peek()
获取但不移除此列表的头(第一个元素)。
E peekFirst()
获取但不移除此列表的第一个元素;如果此列表为空,则返回 null。
E peekLast()
获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null。
E poll()
获取并移除此列表的头(第一个元素)
E pollFirst()
获取并移除此列表的第一个元素;如果此列表为空,则返回 null。
E pollLast()
获取并移除此列表的最后一个元素;如果此列表为空,则返回 null。
E pop()
从此列表所表示的堆栈处弹出一个元素。
5.5.4 练习1:测试特有方法
package cn.tedu.collectiondemo;
import java.util.LinkedList;
//测试LinkedList实现类
public class Test4_LinkedList {
public static void main(String[] args) {
//1,创建对象
//--LinkedList 底层维护了一个链表结构,方便增删,不方便查询.而且整个链表上的节点中,只存在两个高效节点就是首尾元素.
LinkedList<String> list = new LinkedList<>();
//2,常用方法
//TODO 继承自Collection和List接口的方法们
list.add("1");
list.add("2");
list.add("3");
list.add("1");
list.add("2");
System.out.println(list);//有序 + 可重复 [1, 2, 3, 1, 2]
//--LinkedList的特有方法们
list.addFirst("100"); //添加首元素
list.addLast("200"); //添加尾元素
System.out.println(list);//[100, 1, 2, 3, 1, 2, 200]
System.out.println( list.getFirst() ); //获取首元素
System.out.println( list.getLast() ); //获取尾元素
System.out.println( list.removeFirst() );//移除首元素
System.out.println( list.removeLast() );//移除尾元素
System.out.println(list);//[1, 2, 3, 1, 2]
}
}
5.6 List接口实现类之三:Vector
基本不用,略。
6 集合—02
6.1 Collection子接口之二:Set接口
6.1.1 概述
- 一个不包含重复元素的 collection。
- 数据无序(因为set集合没有下标)。
- 由于集合中的元素不可以重复,常用于给数据去重。
- 最多包含一个null元素。
- 使用的都是collection里面的方法,没有产生特有的方法
6.1.2 继承结构
|----Collection接口:单列集合,用来存储一个一个的对象
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
|----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
对于频繁的遍历操作(看似有序实际上还是无序的,要理解无序的概念),
LinkedHashSet效率高于HashSet.
|----TreeSet:可以按照添加对象的指定属性,进行排序。(要求添加的元素是同一个类new的对象)
6.2 Set实现类之一:HashSet
6.2.1 概述
此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素。 (没有特有方法)
6.2.2 特点
底层是:hashMap。像hasSet里面添加数据实际上是向hsahMap中添加数据。
数据储存是:数组+链表。
注意:面试的时候一般不会问HashSet的底层原理实现,因为HashSet的底层是HashMap,直接说HashMap的底层原理就可以了。
6.2.3 测试
SetTest类:
package com.atguigu.java2;
import org.junit.Test;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* 1. Set接口的框架:
*
* |----Collection接口:单列集合,用来存储一个一个的对象
* |----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
* |----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
* |----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
* 对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
* |----TreeSet:可以按照添加对象的指定属性,进行排序。(要求添加的元素是同一个类new的对象)
*
*
* 1. Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
*
* 2. 要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
*
* 要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象(equals为true)必须具有相等的散列码(散列码:即hash值,相同对象的属性 计算的hash值也应该相同)
* 重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field属性,都应该用来计算 hashCode 值。一般自动生成都能保证重写的equals
* 方法和hashCode方法用到的属性一致。
*
*
* @author shkstart
* @create 2019 下午 3:40
*/
public class SetTest {
/*
一、Set:存储无序的、不可重复的数据
以HashSet为例说明:
1. 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
输出结果没有按照添加储存的顺序排列,而是根据哈希值排列的,所以每次输出的结果和第一次输出结果相同。如果是随机则每次输出的结果都不同。
2. 添加元素时会自动调用equals方法,保证添加的元素按照调用equals()判断时 不能返回true.即:相同的元素只能添加一个。
比较对象的内容需要重写equals()方法和hashcode()方法。如果是只比较equals()方法那么添加第1000条数据需要和前999条数据
进行比较,效率太低。所以要先进行hash值比较 具体过程如下。
二、添加元素的过程:以HashSet为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素a添加成功。 --->情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
如果hash值不相同,则元素a添加成功。--->情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。--->情况2
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。(新元素放在数组中,原来的元素放在外面链表中)
jdk 8 :原来的元素在数组中,指向元素a (新元素放在链表中,原来的元素放在数组中)
总结:七上八下
HashSet底层:数组+链表的结构。
*/
@Test
public void test1(){
Set set = new HashSet();
set.add(456);
set.add(123);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new User("Tom",12));
set.add(new User("Tom",12));
set.add(129);
System.out.println(set);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
User类:
package com.atguigu.java2;
/**
* @author shkstart
* @create 2019 下午 3:56
*/
public class User {
private String name;
private int age;
public User() {
}
public User(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 String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//自动生成的equals
@Override
public boolean equals(Object o) {
System.out.println("User equals()....");
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
//自动生成的hashCode
@Override//没有重写调用的是object提供的hashCode()方法 是随机算一个数 不用比就能成功添加,元素可重复。
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
//一般自动生成就行,*31是因为扩大膨胀系数 减少出错概率。
result = 31 * result + age;
return result;
}
/*自己写的方法hashCode:比较粗糙但是也能用,因为有可能不同的属性出现相同的hash值。
解释:一般要求是2个对象的属性不同 算出来的hash值也不同,2个对象的属性相同 算出来的hsah值相同。
我们这种写法有可能出现2个对象的属性不同 算出来的hash值相同。如对象1:name 20 age 24 . 对象2:name 24 age 20,
相加都等于44,在存的时候 hash值相同 在调用equals比较值不同,虽然也能存入成功但是是上下存的,一个存入数组 一个存入链表 用到了指针,
本来不同直接是在同一个数组不同位置进行储存,这种效率更高 用到了指针这种上下存的效率较低。*/
// @Override
// public int hashCode() {
// return name.hashCode() + age;
// }
}
6.2.4 解释膨胀系数为什么是*31
6.3 HashSet的子类:LinkedHashSet
6.3.1 概述
说明:相当于hashSet在添加数据的同时 加上了一层双向链表。可以保证添加顺序和输出结果顺序保持一致,实际上还是无序的。
6.3.2 测试
package com.atguigu.java2;
import org.junit.Test;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
public class SetTest {
//LinkedHashSet的使用
//LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个
//数据和后一个数据。
//优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet,因为记录的有数据位置 不用在一个一个的找了。
@Test
public void test2(){
Set set = new LinkedHashSet();
set.add(456);
set.add(123);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new User("Tom",12));
set.add(new User("Tom",12));
set.add(129);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
6.3.3 测试:在List内去除重复数字值
6.4 Set实现类之二:TreeSet
6.4.1 概述
说明:可以按照添加对象的指定属性,进行排序。(要求添加的元素是同一个类new的对象)
总结:
- List集合需要重写equals方法,里面的一些方法需要调用:contains() /remove()/retainsAll() ….
- hashSet,LinkedHashSet需要重写equals,hashCode方法,放数据时避免重复
- TreeSet不需要重写equals,hsahCode方法用的是Comparable:compareTo,Comparator:compare(Object o1,Object o2)
6.4.2 测试TreeSet的自然排序和定制排序
TreeSetTest 类:
package com.atguigu.java2;
import org.junit.Test;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
/**
* @author shkstart
* @create 2019 下午 4:59
*/
public class TreeSetTest {
/*
1.向TreeSet中添加的数据,要求是相同类的对象。
2.两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator) 其实就是java中的比较器
只不过之前是Arrays.sort或者集合工具类collentions.sort调用的,这个是TreeSet添加数据时调用的。
3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().是否添加数据成功取决于你的compare方法,
如果2个对象有2个属性 name:aa age:20,name:aa age:30,不再是equals方法认为是不同的
对象可以添加成功,compareTo方法如果只比较了一个属性 则代表是相同的对象添加不成功,想要认为是
不同的对象 需要重写compareTo方法时2个属性都进行比较。
4.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals(). 同上......
*/
//自然排序
@Test
public void test1(){
TreeSet set = new TreeSet();
//失败:不能添加不同类的对象
// set.add(123);
// set.add(456);
// set.add("AA");
// set.add(new User("Tom",12));
//举例一:
// set.add(34); //系统提供的类重写compareTo方法默认是从小到大排序的自然排序
// set.add(-34);
// set.add(43);
// set.add(11);
// set.add(8);
//举例二:自定义类
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Mike",65));
set.add(new User("Jack",33));
set.add(new User("Jack",56));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
//定制排序
@Test
public void test2(){
Comparator com = new Comparator() {
//按照年龄从小到大排列
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}else{
throw new RuntimeException("输入的数据类型不匹配");
}
}
};
TreeSet set = new TreeSet(com);//不加参数,默认按照自然排序排列
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Mike",65));
set.add(new User("Mary",33));
//存放相同的对象属性name,因为是按照compare方法比较的,里面只写了age的比较 认为年龄相同是一个对象,只能存放一个对象 谁在前存谁。
set.add(new User("Jack",33));
set.add(new User("Jack",56));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
Person类:
package com.atguigu.java2;
/**
* @author shkstart
* @create 2019 下午 3:56
*/
public class User implements Comparable{
private String name;
private int age;
public User() {
}
public User(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 String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//按照姓名从大到小排列,年龄从小到大排列
@Override
public int compareTo(Object o) {
if(o instanceof User){
User user = (User)o;
// return -this.name.compareTo(user.name);
int compare = -this.name.compareTo(user.name);
if(compare != 0){
return compare;
}else{
return Integer.compare(this.age,user.age);
}
}else{
throw new RuntimeException("输入的类型不匹配");
}
}
}
7. 集合—03
7.1 Map接口
7.1.1 概述 (和collection没啥关系,顶多是平级关系)
7.1.2 特点
- 可以根据键 提取对应的值
- 键不允许重复 值可以重复,如果键重复值会被覆盖(即:如果Key重复不会报错,会把value值覆盖)
- 存放的都是无序数据
- 初始容量是16,默认的加载因子是0.75
- 数据都是键值对,K V同时指定
7.1.3 继承结构
一、Map的实现类的结构:
|----Map:双列数据,存储key-value对的数据 ---类似于高中的函数:y = f(x)
|----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
|----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行效率高于HashMap。
|----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
底层使用红黑树
|----Hashtable(注意table这个t为小写):作为古老的实现类;线程安全的,效率低;不能存储null的key和value
|----Properties:常用来处理配置文件。key和value都是String类型
HashMap的底层:数组+链表 (jdk7及之前)
数组+链表+红黑树 (jdk 8)
与set的对应关系:HashSet底层是HashMap…
HashSet LinkedHashSet TreeSet
HashMap LinkedHashMap TreeMap
7.1.4 常用方法,包括遍历方式
遍历方式图解:
package com.atguigu.java;
import org.junit.Test;
import java.util.*;
/**
* 一、Map的实现类的结构:
* |----Map:双列数据,存储key-value对的数据 ---类似于高中的函数:y = f(x)
* |----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
* 底层:数组+链表 (jdk7及之前)
* 数组+链表+红黑树 (jdk 8)
* |----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
* 原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
* 对于频繁的遍历操作,此类执行效率高于HashMap。
* |----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
* 底层:使用红黑树
* |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
* |----Properties:常用来处理配置文件。key和value都是String类型
*
*
*
*
*
* 面试题:
* 1. HashMap的底层实现原理?
* 2. HashMap 和 Hashtable的异同?
* 3. CurrentHashMap 与 Hashtable的异同?(暂时不讲)
*
* 二、Map结构的理解:
* Map中的key:无序的、不可重复的,使用Set存储所有的key ---> key所在的类要重写equals()和hashCode() 可以保证添加数据时无序不可重复
* (以HashMap为例,TreeMap有涉及到compare,compareTo方法了)
* Map中的value:无序的、可重复的,使用Collection存储所有的value --->value所在的类要重写equals()
* 一个键值对:key-value构成了一个Entry对象。
* Map中的entry:无序的、不可重复的,使用Set存储所有的entry
*
* 三、HashMap的底层实现原理?以jdk7为例说明:
* HashMap map = new HashMap():
* 在实例化以后,底层创建了长度是16的一维数组Entry[] table。
* ...可能已经执行过多次put...
* map.put(key1,value1):
* 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
* 如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
* 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据
* 的哈希值:
* 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
* 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
* 如果equals()返回false:此时key1-value1添加成功。----情况3
* 如果equals()返回true:使用value1替换value2。
*
* 补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
*
* 在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
*
* jdk8 相较于jdk7在底层实现方面的不同:
* 1. new HashMap():底层没有创建一个长度为16的数组
* 2. jdk 8底层的数组是:Node[],而非Entry[]
* 3. 首次调用put()方法时,底层创建长度为16的数组
* 4. jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
* 4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
*
* DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
* DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
* threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
* TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
* MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
*
* 四、HashMap底层源码略,详情查看551集
* LinkedHashMap的底层实现原理(了解)
* 源码中:
* static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;//能够记录添加的元素的先后顺序
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
*
*
* 五、Map中定义的方法:
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
*总结:常用方法:
* 添加:put(Object key,Object value)
* 删除:remove(Object key)
* 修改:put(Object key,Object value)
* 查询:get(Object key)
* 长度:size()
* 遍历:keySet() / values() / entrySet()
* 注意:因为是无序的没有插入功能,你无序的往哪插啊。
*
*
* @author shkstart
* @create 2019 上午 11:15
*/
public class MapTest {
/*
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
*/
/*解释迭代器只适合于Collection集合,Map集合没有提供迭代器iterator方法,那么该如何遍历迭代器呢????
答:
前面讲到Map由k--v结构构成,所有的key放在set集合中 只要拿到所有的key 在通过Set.iterator()便利即可。
所有的value放在Collection集合中 只要拿到所有的value 在通过Collection.iterator()便利即可。
key--value键值对又构成了一个Entry对象,所有的Entry对象放在set集合中 只要拿到所有的Entry对象 在通过Set.iterator()便利即可。
以上需要拿到的数据恰好对应着3个方法。*/
@Test
public void test5(){
Map map = new HashMap();//没有学泛型之前,可以放任何类型
map.put("AA",123);
map.put(45,1234);
map.put("BB",56);
//10.遍历所有的key集:keySet()
Set set = map.keySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println();//AA BB 45
//11.遍历所有的value集:values()
Collection values = map.values();
for(Object obj : values){
System.out.println(obj);
}
System.out.println();//123 56 1234
//12.遍历所有的key-value
//方式一:entrySet()
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
//1.直接输出时查看里面是什么值,开发中是拿到里面的值做运算才有意义
System.out.println(obj);
/*直接输出:
AA=123
BB=56
45=1234*/
//2.分别拿到里面具体的k和v的值
/*entrySet集合中的元素都是entry对象,所以需要强转把object类型转为Entry类型,学了泛型后不用在强转。
*static interface Map.Entry<K,V> :Entry为Map接口里面的内部接口(类似于内部类,接口也有内部接口),静态内部接口创建对象方式为:外部接口.内部接口
*/
Map.Entry entry = (Map.Entry) obj;
/* 此时调用的是Map接口中的内部接口Entry提供的方法 K getKey()和 V getValue(),分别获取k和v
*/
System.out.println(entry.getKey() + "---->" + entry.getValue());
/*输出结果:
AA---->123
BB---->56
45---->1234 */
}
System.out.println();
/* 方式二:方式一对比方式二,方式一是拿到整个entry对象,在通过这个对象分别调用k和v得值。方式二是拿到所有的key,
在遍历所有的key的同时 map集合提供的方法:通过key获取value值*/
Set keySet = map.keySet();
Iterator iterator2 = keySet.iterator();
while(iterator2.hasNext()){
Object key = iterator2.next();
Object value = map.get(key);
System.out.println(key + "=====" + value);
/*输出结果:
AA=====123
BB=====56
45=====1234 */
}
}
/*
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
*/
@Test
public void test4(){
Map map = new HashMap();
map.put("AA",123);
map.put(45,123);
map.put("BB",56);
//5. Object get(Object key)
System.out.println(map.get(45));//123,如果获取的key不存在则为Null
//6.containsKey(Object key) 也会调用 hashCode和equals方法
boolean isExist = map.containsKey("BB");
System.out.println(isExist);//true
//7.size()
System.out.println(map.size());//3
isExist = map.containsValue(123);//一旦找到一个就不会再向下找了
System.out.println(isExist);//true
//8.isEmpty
map.clear();
System.out.println(map.isEmpty());//true
//9.equals(Object obj) 略
}
/*
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
*/
@Test
public void test3(){
Map map = new HashMap();//多态形式
//1.添加
map.put("AA",123);
map.put(45,123);
map.put("BB",56);
//修改
map.put("AA",87);//如果key相同,value会变为替换功能
System.out.println(map);//{AA=87, BB=56, 45=123}
//2.
Map map1 = new HashMap();
map1.put("CC",123);
map1.put("DD",123);
map.putAll(map1);
System.out.println(map);//{AA=87, BB=56, CC=123, DD=123, 45=123}
//3.remove(Object key)
Object value = map.remove("CC");
System.out.println(value);//123
System.out.println(map);//{AA=87, BB=56, DD=123, 45=123}
//4.clear()
map.clear();//与map = null操作不同,对象还存在 只不过里面没有值
System.out.println(map.size());//0
System.out.println(map);//{}
}
@Test
public void test2(){
Map map = new HashMap();//无序的
map = new LinkedHashMap();//和数据添加的顺序保持一致
map.put(123,"AA");
map.put(345,"BB");
map.put(12,"CC");
System.out.println(map);
}
//测试Hashtable不能储存null值
@Test
public void test1(){
Map map = new HashMap();
// map = new Hashtable();
map.put(null,123);
}
}
7.2 HashMap(Map实现类之一,没有参生特有方法)
说明:
- HashMap的键要同时重写hashCode()和equals()
- hashCode()用来判断确定hash值是否相同
- equals()用来判断属性的值是否相同
3.1 equals()判断数据如果相等,hashCode()必须相同
3.2 equals()判断数据如果不等,hashCode()尽量不同
7.2.1 概述
1. 基于哈希表的 Map 接口
的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键
。
2. HashMap底层是一个Entry数组
,当存放数据时会根据hash算法计算数据的存放位置。算法:hash(key)%n,n就是数组的长度。
3. 当计算的位置没有数据时,就直接存放,当计算的位置有数据时也就是发生hash冲突的时候/hash碰撞时,采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。HashMap类的实现则不保证顺序
4. HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。 将元素适当地分布在各桶之间.
特点: 数据无序 + 底层是一个哈希表/散列表
创建对象:
HashMap()
构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空HashMap。
7.2.2 存储结构
存储结构:
7.2.3 测试1:读取HashMap的数据
package seday12;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class Test0_Map {
public static void main(String[] args) {
HashMap map = new HashMap ();
map.put(100, "刘德华");
map.put(101, "梁朝伟");
map.put(102, "古天乐");
map.put(103, "周润发");
//遍历方式1:keySet ()
Set set = m.keySet();
Iterator it = set.iterator();
while(it.hasNext()) {
String key = (String) it.next();
String val = (String) m.get(key);
System.out.println(key+"="+val);
}
//遍历方式2:entrySet ()
Set set2 = m.entrySet();
Iterator it2 = set2.iterator();
while(it2.hasNext()) {
Entry en = (Entry) it2.next();
String key = (String) en.getKey();
String value = (String) en.getValue();
System.out.println(key+"=="+value);
}
}
}
7.2.4 测试2:字符串中的字符统计
说明:接收用户输入的一串字符串,统计出现的每个字符的个数
package cn.tedu.collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
//测试 HashMap
public class Test5_HashMap2 {
public static void main(String[] args) {
//1, 获取用户输入的字符串
String input = new Scanner(System.in).nextLine() ;
//声明map,存数据,格式: {a=3,b=1,c=2}
Map<Character,Integer> map = new HashMap<>() ;
//2,获取到每个字符并统计出现的次数
for (int i = 0; i < input.length() ; i++) {
char key = input.charAt(i) ;//根据下标获取字符--作为key存入map
//value呢???
Integer value = map.get(key) ;
//看看value是默认值null呢?还是已经存过数字了呢?
if(value==null){//如果是null,就是以前没存过,没统计过,这是第一次出现
map.put(key,1) ;
}else{//如果不是null,就是以前存过,在原有数字上+1
map.put(key,value+1) ;
}
}
System.out.println(map);
}
}
7.3 LinkedHashMap(HashMap的实现类)
7.4 TreeMap(Map实现类之二)
7.4.1 概述
7.4.2 测试
TreeMapTest:
package com.atguigu.java;
import org.junit.Test;
import java.util.*;
/**
* @author shkstart
* @create 2019 下午 3:46
*/
public class TreeMapTest {
//向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
//因为要按照key进行排序:自然排序 、定制排序
//自然排序
@Test
public void test1(){
TreeMap map = new TreeMap();
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);
map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());
/*输出结果:
User{name='Tom', age=23}---->98
User{name='Rose', age=18}---->100
User{name='Jerry', age=32}---->89
User{name='Jack', age=20}---->76*/
}
}
//定制排序
@Test
public void test2(){
TreeMap map = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
//按照年龄进行排序
return Integer.compare(u1.getAge(),u2.getAge());
}
throw new RuntimeException("输入的类型不匹配!");
}
});
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);
map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());
/* 输出结果:
User{name='Rose', age=18}---->100
User{name='Jack', age=20}---->76
User{name='Tom', age=23}---->98
User{name='Jerry', age=32}---->89*/
}
}
}
User:
package com.atguigu.java;
/**
* @author shkstart
* @create 2019 下午 3:56
*/
public class User implements Comparable{
private String name;
private int age;
public User() {
}
public User(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 String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
System.out.println("User equals()....");
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() { //return name.hashCode() + age;
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
//自然排序:按照姓名从大到小排列,年龄从小到大排列
@Override
public int compareTo(Object o) {
if(o instanceof User){
User user = (User)o;
// return -this.name.compareTo(user.name);
int compare = -this.name.compareTo(user.name);
if(compare != 0){
return compare;
}else{
return Integer.compare(this.age,user.age);
}
}else{
throw new RuntimeException("输入的类型不匹配");
}
}
}
7.5 Hashtable(Map实现类之三)
说明:太古老了不用,只关心它的子类。
注意:他这个t是小写,太古老了 连命名规范都没有遵循。
7.6 Properties(Hashtable的实现类)
配置文件创造方式一:不需要指定后缀。
配置文件创造方式二:以new File方式需要指定后缀。
7.6.1 测试:
PropertiesTest:
package com.atguigu.java;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
/**
* @author shkstart
* @create 2019 下午 4:07
*/
public class PropertiesTest {
//Properties:常用来处理配置文件。key和value都是String类型
public static void main(String[] args) {
FileInputStream fis = null;
try {
Properties pros = new Properties();
fis = new FileInputStream("jdbc1.properties");
pros.load(fis);//加载流对应的文件
String name = pros.getProperty("name");
String password = pros.getProperty("password");
//name = tomå¸, password = abc123 如果配置文件有中文,在设置编码时没有打钩,那么输出的中文是乱码
//打钩后输出:需要先把原来生的properties文件删除,重新写一个properties文件在输出。 name = tom帅, password = abc123
System.out.println("name = " + name + ", password = " + password);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
PropertiesTest配置文件:
name=tom帅
password=abc123
#注意不要写空格如:name= tom,它会认为你的值是 空格+tom
8 Collections工具类
注意:ArrayList和HashMap都是线程不安全的,如果程序要求线程安全,我们可以将ArrayList和HashMap转换为线程安全的。使用synchronizedList(List list)和synchronizedMap(Map<K,V> m)
8.1 概念:
8.2 常用方法测试
8.2.1 测试1:达内
package cn.tedu.collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
//测试 集合工具类Collections
public class Test6_Collections {
public static void main(String[] args) {
//1,调用Collections常用方法
List<Integer> list = new ArrayList<>();
// list.add(1);//不用工具类,只能一次一次的加
// list.add(2);
// list.add(3);
Collections.addAll(list,1,2,3,4,5) ;//向指定集合list里添加很多元素
System.out.println(list);//[1, 2, 3, 4, 5]
System.out.println( Collections.max(list) ); //获取集合里的最大值5
System.out.println( Collections.min(list) ); //获取集合里的最小值1
Collections.reverse(list);//翻转指定集合里的所有元素
System.out.println(list);//[5, 4, 3, 2, 1]
Collections.swap(list,2,3);//把集合中,指定的两个下标对应的元素交换位置
System.out.println(list);//[5, 4, 2, 3, 1]
Collections.sort(list);//给list里的数据排序
System.out.println(list);//[1, 2, 3, 4, 5]
List<Integer> list2 = new ArrayList<>();
//解决方法:把集合list2的长度变为大于等于list.
Collections.addAll(list2,…elements:55,88,74,55,88,888);
Collections.copy(list2,list);//TODO 把集合list中的内容复制到List2中,但此时list2集合的长度为0,下标越界异常。
System.out.println(list2);
}
}
8.2.2 测试2:尚硅谷
package com.atguigu.java;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Collections:操作Collection、Map的工具类
*
*
* 面试题:Collection 和 Collections的区别?
*
*
* @author shkstart
* @create 2019 下午 4:19
*/
public class CollectionsTest {
/*
1.reverse(List):反转 List 中元素的顺序。注意不适用set,无序的反转也没啥意义。
2.shuffle(List):对 List 集合元素进行随机排序
3.sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
4.sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
5.swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
6.Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
7.Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
8.Object min(Collection)
9.Object min(Collection,Comparator)
10.int frequency(Collection,Object):返回指定集合中指定元素的出现次数
11.void copy(List dest,List src):将src中的内容复制到dest中
12.boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
*/
@Test
public void test1(){
List list = new ArrayList();
list.add(123);
list.add(43);
list.add(765);
list.add(765);
list.add(765);
list.add(-97);
list.add(0);
System.out.println(list);
// Collections.reverse(list);1.反转
// Collections.shuffle(list);2.真正的随机打乱输出,不是无序输出。
// Collections.sort(list); 3.自然排序,默认调用 因为存的是integer的包装类 默认使用的里面的comPareTo方法 从小到大排序。
// Collections.swap(list,1,2); 5.交换2个索引处的元素。
int frequency = Collections.frequency(list, 123);
System.out.println(list);
System.out.println(frequency);//10.
}
@Test
public void test2(){
List list = new ArrayList();
list.add(123);
list.add(43);
list.add(765);
list.add(-97);
list.add(0);
//11. 错误写法:
/* 报异常:IndexOutOfBoundsException("Source does not fit in dest")
List dest = new ArrayList();
Collections.copy(dest,list); 把list原集合 的数据复制到 新集合dest中。源码中要求
原集合.size小于新集合.size 即元素的个数少于新集合,这个新集合新创建没有元素 size为0,
原集合的size当然大于新集合的size,所以添加失败。*/
//正确的:把数组转化为list集合,这个数组的长度为原集合的长度,每个值都为null进行占位。
List dest = Arrays.asList(new Object[list.size()]);
System.out.println(dest.size());//list.size();5, [null,null,null,null,null]
Collections.copy(dest,list);//此时在进行复制就可以了。
System.out.println(dest);//[123, 43, 765, -97, 0]
/*
Collections 类中提供了多个 synchronizedXxx() 方法,
该方法可使将指定集合包装成线程同步的集合,从而可以解决
多线程并发访问集合时的线程安全问题。包括collection和Map
*/
//返回的list1即为线程安全的List,把原来线程不安全的集合转化为线程安全的。
List list1 = Collections.synchronizedList(list);
}
}