day7--java高级编程:Junit单元测试框架、泛型,集合:集合数组互转,迭代器,增强for循环,集合工具类
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 测试1:在集合ArrayList,HashMap中使用泛型
package com.atguigu.java;
import org.junit.Test;
import java.util.*;
/**
*
* 泛型的使用
* 1.jdk 5.0新增的特性
*
* 2.在集合中使用泛型:
* 总结:
* ① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
* ② 在实例化集合类时,可以指明具体的泛型类型
* ③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
* 比如:add(E e) --->实例化以后:add(Integer e)
* ④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
* ⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。
* ⑥ 在集合类或接口中凡是定义类或接口时,如果实例化时,没有指明泛型的类型,在eclipse中会有警告信息 在idea中没有警告。警告就是不强制 你不要
* 也不报错。
*
* 3.如何自定义泛型结构:泛型类、泛型接口;泛型方法。见 GenericTest1.java
*
* @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.3 测试2:在集合TreeSet中使用泛型实现 自然排序,定制排序
EmployeeTest:
package com.atguigu.exer;
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 {
//问题二:按生日日期的先后排序。
@Test
public void test2(){
TreeSet<Employee> set = new TreeSet<>(new Comparator<Employee>() {
//使用泛型以后的写法
@Override
public int compare(Employee o1, Employee o2) {
MyDate b1 = o1.getBirthday();
MyDate b2 = o2.getBirthday();
return b1.compareTo(b2);
}
//使用泛型之前的写法
//@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("传入的数据类型不一致!");
// }
});
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());
}
}
//问题一:使用自然排序
@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);
}
}
}
Employee:
package com.atguigu.exer;
/**
* 定义一个Employee类。
该类包含:private成员变量name,age,birthday,其中 birthday 为 MyDate 类的对象;
并为每一个属性定义 getter, setter 方法;
并重写 toString 方法输出 name, age, birthday
* @author shkstart
* @create 2019 上午 10:22
*/
public class Employee implements Comparable<Employee>{
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.atguigu.exer;
/**
* MyDate类包含:
private成员变量year,month,day;并为每一个属性定义 getter, setter 方法;
* @author shkstart
* @create 2019 上午 10:21
*/
public class MyDate implements Comparable<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();
}
}
4.4 自定义泛型结构
4.5.1 自定义泛型类,接口
说明:如果是接口只需要改为interface的形式,里面不能有构造方法,用法和类一样就不在讲解了,这里只学习自定义泛型类。
GenericTest1类:
package com.atguigu.java;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
/** 如何自定义泛型结构:泛型类、泛型接口;泛型方法。
*
* 1. 关于自定义泛型类、泛型接口:
*
*
*
* @author shkstart
* @create 2019 上午 11:09
*/
public class GenericTest1 {
@Test
public void test1(){
//如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型(即自定义类声明时使用了泛型,但在实例化时没有使用泛型)
//要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。
Order order = new Order();
order.setOrderT(123);
order.setOrderT("ABC");
//建议:实例化时指明类的泛型。orderName orderId是确定的类型,T orderT是确定的泛型 所以String赋值的是这个不确定的类型T。
Order<String> order1 = new Order<String>("orderAA",1001,"order:AA");
order1.setOrderT("AA:hello");
}
//测试:自定义类Order的子类在继承时,子类泛型书写有二种方式:
@Test
public void test2(){
//方式一:
//由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。
//继承关系:public class SubOrder extends Order<Integer> {},
// 则子类实例化时 SubOrder sub1 = new SubOrder();不用在指定泛型
//此时父类Order为泛型类,子类SubOrder是普通的类。
SubOrder sub1 = new SubOrder();
sub1.setOrderT(1122);//默认里面的元素就是Integer类型
//方式二:
//由于子类在继承带泛型的父类时,没有指明具体的泛型类型。则实例化子类对象时,仍需要指明泛型。
// 继承关系:public class SubOrder1<T> extends Order<T> {},
//则子类实例化时 SubOrder1<String> sub2 = new SubOrder1<>();需要指定泛型
此时父类Order为泛型类,子类SubOrder1也是泛型类。
SubOrder1<String> sub2 = new SubOrder1<>();
sub2.setOrderT("order2...");
}
Order父类:
package com.atguigu.java;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义泛型类
* 说明:如果是接口只需要改为interface的形式,里面不能有构造方法,用法和类一样就不在讲解了。
* @author shkstart
* @create 2019 上午 11:05
*/
public class Order<T> {//通配符为任意字母,这个T不是类 是个参数。 接口中使用泛型
//之前定义类的属性都是确定的类型,如果类中的属性不确定可以再定义接口时加上泛型,之后在类的内部结构就可以使用类的泛型
String orderName;
int orderId;
//类的内部结构就可以使用类的泛型
T orderT; //变量使用泛型
//注意构造方法上不能使用泛型,但是在实例化对象时需要指定泛型。但是可以再构造方法的参数上使用泛型。
// 错误写法:public Order<E>(){}
//正确写法:public Order(){},在实例化时需要指定泛型:Order<String> order1 = new Order<>();
public Order(){
}
public Order(String orderName,int orderId,T orderT){//在构造方法的参数上使用泛型
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
//如下的三个方法都不是泛型方法
public T getOrderT(){//不是泛型方法
return orderT;
}
public void setOrderT(T orderT){
this.orderT = orderT;//不是泛型方法
}
@Override
public String toString() {//不是泛型方法
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", orderT=" + orderT +
'}';
}
}
SubOrder子类:
package com.atguigu.java;
import java.util.ArrayList;
import java.util.List;
/**
* @author shkstart
* @create 2019 上午 11:15
*/
public class SubOrder extends Order<Integer> {//SubOrder:不是泛型类
}
SubOrder1子类:
package com.atguigu.java;
/**
* @author shkstart
* @create 2019 上午 11:18
*/
public class SubOrder1<T> extends Order<T> {//SubOrder1<T>:仍然是泛型类
}
4.5.1 自定义泛型类,接口的注意事项
注意事项2,4,9,10,11测试:
GenericTest1类:
package com.atguigu.java;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
/** 如何自定义泛型结构:泛型类、泛型接口;泛型方法。
*
* 1. 关于自定义泛型类、泛型接口:
*
*
*
* @author shkstart
* @create 2019 上午 11:09
*/
public class GenericTest1 {
//测试注意事项2:泛型类的构造器如下:public GenericClass(){}
// 而下面是错误的:public GenericClass<E>(){}
@Test
public void test(){
Order<String> objectOrder = new Order<>();
}
//测试注意事项4:泛型不同的引用不能相互赋值。
@Test
public void test3(){
ArrayList<String> list1 = null;
ArrayList<Integer> list2 = new ArrayList<Integer>();
//泛型不同的引用不能相互赋值。
// list1 = list2;
Person p1 = null;
Person p2 = null;
//原来的写法可以进行赋值
p1 = p2;
}
//测试泛型方法
@Test
public void test4(){
Order<String> order = new Order<>();
Integer[] arr = new Integer[]{1,2,3,4};
//泛型方法在调用时,指明泛型参数的类型。
List<Integer> list = order.copyFromArrayToList(arr);
System.out.println(list);
}
}
Order类:
package com.atguigu.java;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义泛型类
* 说明:如果是接口只需要改为interface的形式,里面不能有构造方法,用法和类一样就不在讲解了。
* @author shkstart
* @create 2019 上午 11:05
*/
public class Order<T> {//通配符为任意字母,这个T不是类 是个参数。 接口中使用泛型
//之前定义类的属性都是确定的类型,如果类中的属性不确定可以再定义接口时加上泛型,之后在类的内部结构就可以使用类的泛型
String orderName;
int orderId;
//类的内部结构就可以使用类的泛型
T orderT; //变量使用泛型
//测试注意事项2:注意构造方法上不能使用泛型,但是在实例化对象时需要指定泛型。但是可以再构造方法的参数上使用泛型。
// 错误写法:public Order<E>(){}
//正确写法:public Order(){},在实例化时需要指定泛型:Order<String> order1 = new Order<>();
public Order(){
//注意事项11:创建T类型的数组不能是直接new T(),因为T只能看参数 而不能看成是类,解决:可以创建Object类型数组,在进行强转。
//编译不通过
// T[] arr = new T[10];
//编译通过
T[] arr = (T[]) new Object[10];
}
public Order(String orderName,int orderId,T orderT){//在构造方法的参数上使用泛型
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
//如下的三个方法都不是泛型方法
public T getOrderT(){
return orderT;
}
public void setOrderT(T orderT){
this.orderT = orderT;
}
@Override
public String toString() {
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", orderT=" + orderT +
'}';
}
//注意事项9:静态方法中不能使用类的泛型。因为类的泛型是实例化创建对象时指定,静态资源是类加载时创建。
// public static void show(T orderT){
// System.out.println(orderT);
// }
//普通方法中不能写try--catch,解释在定义类时虽然自命为异常类型,但到catch中万一匹配的类型不是它的子类异常,不就编译不通过了
public void show(){
//编译不通过
// try{
//
//
// }catch(T t){
//
// }
}
}
Person类:
package com.atguigu.java;
/**
* @author shkstart
* @create 2019 上午 11:24
*/
public class Person {
}
MyException类:
package com.atguigu.java;
/**
* @author shkstart
* @create 2019 上午 11:29
*/
//注意事项10:异常类不能声明为泛型类
//public class MyException<T> extends Exception{
//}
//这样可以
public class MyException<T> {
}
注意事项12测试:
4.5.1 自定义泛型方法
GenericTest1:
package com.atguigu.java;
import org.junit.Test;
import java.util.List;
/** 如何自定义泛型结构:泛型类、泛型接口;泛型方法。
*
* 1. 关于自定义泛型类、泛型接口:
*
*
*
* @author shkstart
* @create 2019 上午 11:09
*/
public class GenericTest1 {
//测试泛型方法
@Test
public void test4(){
Order<String> order = new Order<>();
Integer[] arr = new Integer[]{1,2,3,4};
//泛型方法在调用时,指明泛型参数的类型。Integer
List<Integer> list = order.copyFromArrayToList(arr);
System.out.println(list);//[1, 2, 3, 4]
}
}
Order:
package com.atguigu.java;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义泛型类
* 说明:如果是接口只需要改为interface的形式,里面不能有构造方法,用法和类一样就不在讲解了。
* @author shkstart
* @create 2019 上午 11:05
*/
public class Order<T> {//通配符为任意字母,这个T不是类 是个参数。 接口中使用泛型
//之前定义类的属性都是确定的类型,如果类中的属性不确定可以再定义接口时加上泛型,之后在类的内部结构就可以使用类的泛型
String orderName;
int orderId;
//类的内部结构就可以使用类的泛型
T orderT; //变量使用泛型
//测试注意事项2:注意构造方法上不能使用泛型,但是在实例化对象时需要指定泛型。但是可以再构造方法的参数上使用泛型。
// 错误写法:public Order<E>(){}
//正确写法:public Order(){},在实例化时需要指定泛型:Order<String> order1 = new Order<>();
public Order(){
//注意事项11:创建T类型的数组不能是直接new T(),因为T只能看参数 而不能看成是类,解决:可以创建Object类型数组,在进行强转。
//编译不通过
// T[] arr = new T[10];
//编译通过
T[] arr = (T[]) new Object[10];
}
public Order(String orderName,int orderId,T orderT){//在构造方法的参数上使用泛型
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
//如下的三个方法都不是泛型方法,他们使用的是类上的泛型
public T getOrderT(){
return orderT;
}
public void setOrderT(T orderT){
this.orderT = orderT;
}
@Override
public String toString() {
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", orderT=" + orderT +
'}';
}
//泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
//换句话说,泛型方法所属的类是不是泛型类都没有关系。
//泛型方法,可以声明为静态的。泛型类中的方法不可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
//需求:在方法的形参中声明一个不确定类型的数组,然后放到list集合中 并返回,
//注意1:E[] arr和Object[] arr效果不一致,E[]是可以赋值给任何类型 但是最终放到集合中的类型只有一种,
// 而Object[]是任意类型,最终放到集合中的类型也是任意的。
//注意2:数组元素是E类型,那么返回值也应该是List<E>,但是这样写还不对 编译器会误认为这个E是类如String一个具体的类型,而不会认为是一个参数 我们
//想要的是当作参数,在调用的时候进行赋值,解决需要在方法的返回值类型前面加上<E>才会当做是参数来看。
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
}
4.5.2 泛型类和泛型方法的使用场景
DAOTest类:
package com.atguigu.java;
import org.junit.Test;
import java.util.List;
/**
* @author shkstart
* @create 2019 上午 11:57
*/
public class DAOTest {
@Test
public void test1(){
//此时实例化dao层子类对象CustomerDAO后,CustomerDAO继承Dao指定了泛型为Customer类型,那么子类实例化就不用指定泛型,
//调用方法时自动就是Customer类型
CustomerDAO dao1 = new CustomerDAO();
dao1.add(new Customer());
List<Customer> list = dao1.getForList(10);
}
}
DAO类:
package com.atguigu.java;
import java.util.List;
/**
* @author shkstart
* @create 2019 上午 11:50
*
*
* DAO:data(base) access object 在java中Dao层定义数据库的访问对象进行crud操作对应 数据库的表中数据的crud操作。
* 数据库中的表有对应java中的一个个自定义类 如:Customer,用自定义的类中的属性进行接收表中的字段。此时数据库中有多张表都需要crud操作,一般而言
* 一张表对应java的一个类 对表的字段进行封装,那么此时操作这个类就相当于操作数据库中的表。如果每张表的crud操作都对应一个Dao层 多张表的操作有相同
* 的功能,那么每张表的操作都写一个Dao层,此时会造成每个Dao层中有很多相似的代码。这就是用到了泛型,可以把父类Dao声明为泛型<T>类型,通用的代码写在父类Dao层,
* 用它的子类CustomerDAO写特有的功能 对应数据库每张表的特有功能,这样可以简写代码。
*
*
*/
public class DAO<T> {//表的共性操作的DAO
//添加一条记录
public void add(T t){
}
//删除一条记录
public boolean remove(int index){
return false;
}
//修改一条记录
public void update(int index,T t){
}
//查询一条记录
public T getIndex(int index){
return null;
}
//查询多条记录
public List<T> getForList(int index){
return null;
}
//泛型方法:里面有不确定性,在加上<E>看成是形参
//举例:获取表中一共有多少条记录?获取最大的员工入职时间?
public <E> E getValue(){
return null;
}
}
CustomerDAO类:
package com.atguigu.java;
/**
* @author shkstart
* @create 2019 上午 11:55
*/
public class CustomerDAO extends DAO<Customer>{//只能操作某一个表的DAO
}
Customer类:
package com.atguigu.java;
/**
* @author shkstart
* @create 2019 上午 11:54
*/
public class Customer { //此类对应数据库中的customers表
}
4.6 泛型在继承方面的体现
package com.atguigu.java1;
import com.atguigu.java.Person;
import org.junit.Test;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
*
* 1. 泛型在继承方面的体现
*
*
* 2. 通配符的使用
*
* @author shkstart
* @create 2019 下午 2:13
*/
public class GenericTest {
/*
1. 泛型在继承方面的体现
虽然类A是类B的父类,但是G<A> 和G<B>二者不具备子父类关系,二者是并列关系。
补充:类A是类B的父类,A<G> 是 B<G> 的父类
*/
//情况一:虽然类A是类B的父类,但是G<A> 和G<B>二者不具备子父类关系,二者是并列关系。
@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);
show1(list2);
}
public void show1(List<String> list){
}
public void show(List<Object> list){
}
//情况二:类A是类B的父类,A<G> 是 B<G> 的父类
@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.7 泛型通配符的使用
GenericTest类:
package com.atguigu.java1;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
*
* 1. 泛型在继承方面的体现
*
*
* 2. 通配符的使用
*
* @author shkstart
* @create 2019 下午 2:13
*/
public class GenericTest {
/*
2. 通配符的使用
通配符:?
类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之外。
// list.add("DD");
// list.add('?');
list.add(null);
//获取(读取):允许读取数据,读取的数据类型为Object。
Object o = list.get(0);
System.out.println(o);
}
public void print(List<?> list){
Iterator<?> iterator = list.iterator();
while(iterator.hasNext()){
//不管传入的是啥类型都可以使用Object来接收
Object obj = iterator.next();
System.out.println(obj);
}
}
/*
3.有限制条件的通配符的使用。
? extends A:
G<? extends A> 可以作为G<A>和G<B>的父类,其中B是A的子类
? super A:
G<? super A> 可以作为G<A>和G<B>的父类,其中B是A的父类
*/
@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);//虽然list3是Student 但是用list1调用的话时list1的类型 person,person是Student的父类,所以这里是多态形式接收。
//编译不通过
//Student s = list1.get(0);//不能用Student类型接收
list2 = list4;
Object obj = list2.get(0);//首先考虑赋值给谁就用谁的类型来接收,其次因为list2是子类,下面的有可能是父类(有可能是平级,有可能是父类),比下面还大的只能是Object来接收。
编译不通过
// Person obj = list2.get(0);//list4有可能是list2的父类,所以接收只能是比list4还大的类型
//写入数据:只要理解2种方式父子类的关系就明白了
//编译不通过 list1代表[-∞,Person] 此时Person是最大的类,万一list1里面的类型比你new的Student类还小,此时把大类型Student赋值给小类型当然报错。
// list1.add(new Student());
//编译通过 list2代表[Person,+∞],此时Person是最起码是他最小的类,你这里new它的最小类person的子类当然能赋值给父类。
list2.add(new Person());
list2.add(new Student());
}
}
Person类:
package com.atguigu.java1;
public class Person {
}
class类:
package com.atguigu.java1;
/**
* @author shkstart
* @create 2019 下午 2:42
*/
public class Student extends Person {
}
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方法,所以可以直接输出。
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(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);
}
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!