一、复习回顾
问:集合分为两大类?
java
Collection:存储一组对象
Map:存储一组键值对(key,value),键值对又称为映射关系。
问:Collection有Set和List等子接口,那么Set和List有啥区别?
java
List:有序(可以按照索引操作),可重复
Set:无序(不能按照索引操作),不可重复
问:Set有一些常见的实现类,它们是谁?
java
HashSet:散列无规律
LinkedHashSet:按照元素添加的顺序排列
TreeSet:按照元素的大小顺序排列
问:Set的实现类有什么区别?
关于底层,后面讲Map再细说

问:Collection接口的一些方法?
1、添加
- add(E e):添加一个元素
- addAll(Collection c):添加一组对象。集合1.addAll(集合2),将集合2的元素添加到集合1中,谁.影响谁里面的元素。集合1变成两个集合的并集。

2、删除
- clear():清空
- remove(Object e):删除一个对象
- removeAll(Collection c):删除一组对象,删除两个集合的交集。集合1.removeAll(集合2),删除的是集合1中的元素,谁.删谁里面的元素。
- retainAll(Collection c):删除一组对象,删除两个集合的非交集部分,留下两个集合的交集部分。集合1.removeAll(集合2),删除的是集合1中的元素,谁.删谁里面的元素。
- removeIf(Predicate p):删除一组对象。重写Predicate 接口中的test方法,当test方法判断该元素返回true,这个元素就会被删除。
3、查询/查看
- boolean contains(Object obj):判断某个对象是不是在当前集合中
- boolean containsAll(Collection c):判断c集合中的所有元素是不是都在当前集合中,即判断c集合是不是当前集合的子集。
- boolean isEmpty():是否为空
- int size():元素个数
- Object[] toArray():把元素放到数组中
4、遍历方式
- 增强foreach循环(它底层也是使用迭代器)
- Iterator迭代器
- Iterator iterator()
问:Iterator迭代器遍历用的方法?
- boolean hasNext():判断迭代器当前位置是不是还有元素可迭代
- Object next():先返回迭代器当前位置的元素,然后迭代器向下一个元素移动。
问:迭代器在遍历集合元素的过程中,切记不要做哪些事?
不要对当前集合调用集合的add,remove等方法,进行元素个数的修改。
问:如果迭代器要删除元素,应该调用谁的什么方法?
调用迭代器的remove方法。
问:foreach里面能不能调用当前集合的add,remove等方法?能不能调用迭代器的remove方法?
不能。
不能。
结论:foreach里面,针对当前集合的add,remove等都是无法操作。
问:List接口的一些方法?
除了从Collection接口继承的方法之外,List还扩展了哪些方法?
1、添加
add(int index, Object obj):指定位置添加一个元素。
addAll(int index, Collection c):指定位置添加一组元素。
2、删除
remove(int index):删除指定位置的元素。
3、查询
Object get(int index):返回index位置的元素
List subList(int start, int end):截取[start, end)位置的一组元素
int indexOf(Object obj):返回首次出现的索引值。不存在返回-1。
int lastIndexOf(Object obj):返回末次出现的索引值。不存在返回-1。
4、修改
set(int index, Object obj):替换[index]位置的元素
replaceAll(UnarayOperator u):要重写UnarayOperator 接口的apply方法,方法的参数是元素的原值,方法的返回值是元素的新值。
问:hashCode和equals方法
-
boolean equals(Object obj):比较两个对象是不是相等。怎么比较?
- 如果重写了,通常是比较两个对象的内容/属性值
- 如果没有重写,Object类中定义的equals方法是比较对象的地址值
-
int hashCode():计算对象的身份证号
- 通常该值用于计算对象在HashSet,HashMap等与哈希有关的集合中的存储位置
hashCode方法重写有一些要求?
- 和重写equals方法选择的属性一样(快捷键Alt + insert)
- 同一个对象,属性值不变的话,调几次它们的hashCode值应该相同
- 如果equals返回true,即相同,要求它们的hashCode值也相同
- 如果equals返回false,即不同,它们的hashCode值可能相同,也可能不同。当然是最好是不同。
- 如果两个对象的hashCode值不同,两个对象equals一定是false,即不同。
重写equals方法有什么要求吗?
- 自反性:x.equals(x)一定是true
- 一致性:如果x和y两个对象的属性没有变化,多次调用x.equals(y)返回的结果要一致
- 传递性:x.equals(y)相同,y.equals(z)也相同,那么应该得出x.equals(z)也相同
- 对称性:x.equals(y)相同,反过来,y.equals(x)也相同,或者x.equals(y)不同,反过来,y.equals(x)也不同
- 非空对象与null比较一定是false:x!=null,那么x.equals(null)一定是false
java
package com.atguigu.list;
import org.junit.Test;
public class TestEmployee {
@Test
public void test1()throws Exception{
//手动重写equals或hashCode方法的问题,这里只演示equals方法。
//需求:如下3个对象,我希望比较equals都相同
Employee e1 = new Employee(1,"张三","男");//编号,姓名,性别
Employee e2 = new Employee(1,"张三","man");
Employee e3 = new Employee(1,"张三","Man");
System.out.println(e1.equals(e2));
System.out.println(e1.equals(e3));
System.out.println(e2.equals(e3));
System.out.println(e1);
System.out.println(e2);
System.out.println(e3);
}
}
java
package com.atguigu.list;
import java.util.Objects;
public class Employee {
private int id;
private String name;
private String gender;
public Employee(int id, String name, String gender) {
this.id = id;
this.name = name;
this.gender = gender;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
if (id != employee.id) return false;
if (!Objects.equals(name, employee.name)) return false;
// return Objects.equals(gender, employee.gender);
return equalsGender(gender, employee.gender);
}
private boolean equalsGender(String gender1, String gender2){
if(gender1 == gender2){
return true;
}
if(gender1!=null && gender1.equals("男")){
gender1 = "man";
}
if(gender2!=null && gender2.equals("男")){
gender2 ="man";
}
return gender1.equalsIgnoreCase(gender2);//不区分大小写比较两个单词
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (gender != null ? gender.hashCode() : 0);
return result;
}
}
二、ListIterator
2.1 ListIterator是什么
ListIterator也是一种迭代器,它是Iterator接口的子接口,而且是专门用于List系列的集合的迭代器。
2.2 ListIterator的对象如何获取
List系列的集合.listIterator()可以获取ListIterator的对象。一开始迭代器在[0]位置。
List系列的集合.listIterator(int index)也可以获取ListIterator的对象。一开始迭代器在[index]位置。
2.3 API方法
Iterator接口有的方法,ListIterator也有。(因为会继承过来)
ListIterator接口又扩展了一些方法:
-
获取元素的下标
- int nextIndex()
- int previousIndex()
-
遍历的方向
- 除了 hasNext() + next() 可以实现从左往右遍历
- 还可以用下面两个方法实现从右往左遍历
- boolean hasPrevious()
- Object previous()
-
支持的操作
- 除了remove()(这是迭代器的remove)之外
- 还增加了迭代过程中的如下方法,它们是迭代器的add和set
- add(Object obj)
- set(Object obj)
2.4 演示代码
java
package com.atguigu.list;
import org.junit.Test;
import java.util.ArrayList;
import java.util.ListIterator;
public class TestListIterator {
@Test
public void test1()throws Exception{
ArrayList list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");
ListIterator listIterator = list.listIterator();//返回的是ListIterator接口的实现类的对象
//从左往右遍历
while(listIterator.hasNext()){
int index = listIterator.nextIndex();//迭代器当前指向的元素的下标
int preIndex = listIterator.previousIndex();
Object obj = listIterator.next();
System.out.println("前一个元素的下标:" + preIndex);
System.out.println("当前元素下标:" + index + ":" + obj);
}
}
@Test
public void test2()throws Exception{
ArrayList list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");
// ListIterator listIterator = list.listIterator();//迭代器一开始在[0]位置
ListIterator listIterator = list.listIterator(list.size());//迭代器一开始在[list.size()]位置
//这里size=3,迭代器在[3]的位置
//从右往左遍历
while(listIterator.hasPrevious()){
int index = listIterator.previousIndex();
Object obj = listIterator.previous();
System.out.println("前一个元素的下标:" + index);
System.out.println("前一个元素:" + obj);
}
}
@Test
public void test3()throws Exception{
ArrayList list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");
//需求:在hello后面添加一个atguigu
/*
方式一:不用迭代器
*/
/*int index = list.indexOf("hello");//查询hello的位置
list.add(index + 1,"atguigu");//在hello的后面添加新元素atguigu
System.out.println(list);*/
/*
方式二:使用ListIterator迭代器
*/
ListIterator listIterator = list.listIterator();
while(listIterator.hasNext()){
Object obj = listIterator.next();
if("hello".equals(obj)){
listIterator.add("atguigu");
}
}
System.out.println(list);
}
@Test
public void test4()throws Exception {
ArrayList list = new ArrayList();
list.add("hello");
list.add("haha");
list.add("world");
list.add("java");
//需求:在hello的前面添加atguigu
/*
方式一:不用迭代器
*/
/* int index = list.indexOf("hello");//查询hello的位置
list.add(index ,"atguigu");//在hello的前面添加新元素atguigu,插入到hello原来的位置,然后自动把hello及其后面的元素右移
System.out.println(list);*/
/*
方式二:使用ListIterator迭代器
*/
ListIterator listIterator = list.listIterator(list.size());
while(listIterator.hasPrevious()){
Object obj = listIterator.previous();
if("hello".equals(obj)){
listIterator.add("atguigu");
}
}
System.out.println(list);
}
@Test
public void test5()throws Exception{
ArrayList list = new ArrayList();
list.add("hello");
list.add("haha");
list.add("world");
list.add("java");
//Hello,Haha,World,Java
//需求:把所有单词的首字母改为大写
ListIterator listIterator = list.listIterator();
while(listIterator.hasNext()){
Object obj = listIterator.next();
String str = (String) obj;
char first = str.charAt(0);
first = Character.toUpperCase(first);
String after = str.substring(1);//从[1]到最后
listIterator.set(first + after);//替换原来的元素
}
System.out.println(list);
}
}
三、List接口的常用实现类
3.1 实现类有哪些?
- ArrayList(最常用):动态数组
- Vector:动态数组
- LinkedList:双向链表
- Stack:栈
3.2 ArrayList和Vector的区别

3.3 动态数组与双向链表的区别
详细请看

3.4 栈等数据结构3
3.4.1 栈、队列、双端队列


3.4.2 Queue队列接口

3.4.2 Deque双端队列接口

3.4.3 演示代码
java
package com.atguigu.list;
import org.junit.Test;
import java.util.*;
public class TestListImpl {
@Test
public void test1()throws Exception{
//演示栈类型Stack
Stack stack = new Stack();
//它自己扩展的方法,为了体现它和列表的不同,突然栈的特点
stack.push("hello");//压栈
stack.push("world");
stack.push("java");
System.out.println(stack.pop());//弹出栈 java
System.out.println(stack.pop());//world
System.out.println(stack.pop());//hello
System.out.println(stack.pop());//java.util.EmptyStackException 空栈异常
}
@Test
public void test2()throws Exception {
//演示栈类型Stack
Stack stack = new Stack();
//它自己扩展的方法,为了体现它和列表的不同,突然栈的特点
stack.push("hello");//压栈
stack.push("world");
stack.push("java");
while(!stack.empty()){//非空判断
System.out.println(stack.pop());
}
}
@Test
public void test3()throws Exception{
//演示栈类型Stack
Stack stack = new Stack();
//它自己扩展的方法,为了体现它和列表的不同,突然栈的特点
stack.push("hello");//压栈
stack.push("world");
stack.push("java");
System.out.println(stack.peek());//peek偷偷看一眼,现在栈顶是谁,不敢拿
System.out.println(stack.peek());
System.out.println(stack.peek());
System.out.println(stack.peek());
}
@Test
public void test4()throws Exception{
//演示ArrayList 用作栈使用
ArrayList list = new ArrayList();
//关键点:要实现先进后出的效果
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
//出来的顺序:atguigu,java,world,hello 取走
while(list.size()>0){//有元素可取
System.out.println(list.remove(list.size()-1));
//list.size()-1是列表中最后一个元素的索引
}
System.out.println(list);//空的
}
@Test
public void test5()throws Exception{
//演示Queue接口的使用
Queue queue = new LinkedList();//这么写是希望大家知道它们的关系
queue.add("hello");
queue.add("world");
queue.add("java");
queue.add("atguigu");
//出来的顺序:hello,world,java,atguigu
System.out.println(queue.element());//hello 偷偷查看第一个元素,队头,谁带头
System.out.println(queue.element());//hello
System.out.println(queue.element());//hello
//关键点:先进先出
while(!queue.isEmpty()){//非空判断
System.out.println(queue.remove());
}
System.out.println(queue);//空的
}
@Test
public void test6()throws Exception{
Queue queue = new LinkedList();
// System.out.println(queue.element());//java.util.NoSuchElementException
System.out.println(queue.remove());//java.util.NoSuchElementException
}
@Test
public void test7()throws Exception{
Queue queue = new LinkedList();
System.out.println(queue.peek());//null
System.out.println(queue.poll());//null
}
@Test
public void test8()throws Exception{
//双端队列
Deque deque = new LinkedList();
//关键词:两头都可以进、出
deque.addFirst("hello");
deque.addLast("world");
deque.addFirst("world");
deque.addFirst("atguigu");
deque.addLast("chai");
//顺序:左边是first的话 atguigu,world,hello,world,chai
System.out.println(deque);
System.out.println(deque.removeLast());//chai
System.out.println(deque.removeFirst());//atguigu
}
}
四、Collection系列集合关系图

五、泛型
5.1 什么是泛型?
泛型:泛指某个类型,代码中看到、、等都是和泛型有关的。
5.2 泛型的好处
JDK1.5引入的泛型。
为了解决:
- 避免类型的转换,主要是向下转型
- 把很多场景的类型检查从运行时提前到编译时
泛型带来的好处:
- 简化了代码:通过减少向下转型
- 更安全:编译时就已经进行类型检查
java
package com.atguigu.generic;
import org.junit.Test;
import java.util.ArrayList;
public class TestGeneric {
@Test
public void test1NoGeneric()throws Exception{
ArrayList list = new ArrayList();
list.add("hello");
list.add("java");
list.add("world");
list.add("atguigu");
//需求:输出元素,并且求字符串元素的长度
for (Object o : list) {
String str = (String) o;
System.out.println(str+"的长度:" + str.length());
}
}
@Test
public void test1UseGeneric()throws Exception{
ArrayList<String> list = new ArrayList<String>();
list.add("hello");
list.add("java");
list.add("world");
list.add("atguigu");
//快捷键iter
for (String str : list) {//避免的向下转型
System.out.println(str+"的长度:" + str.length());
}
}
@Test
public void test2NoGeneric()throws Exception{
//给list添加元素,但是只能添加String,不能添加其他类型的对象
ArrayList list = new ArrayList();
list.add("hello");
list.add("java");
list.add("world");
list.add(1);//编译器不给提示,它不满足我们的要求
for (Object o : list) {
if(o instanceof String) {
String str = (String) o;
System.out.println(str + "的长度:" + str.length());
}
}
}
@Test
public void test2UseGeneric()throws Exception{
//给list添加元素,但是只能添加String,不能添加其他类型的对象
ArrayList<String> list = new ArrayList<String>();
list.add("hello");
list.add("java");
list.add("world");
// list.add(1);//编译器立刻给出提示,它不满足我们的要求
}
}
5.3 泛型的使用场景有哪些?
泛型可以在两个位置定义和使用?
- 在类或接口名后面定义和使用泛型,这样的类或接口称为泛型类或泛型接口
- 在方法的返回值类型的前面定义泛型,在当前方法中使用泛型,这样的方法称为泛型方法
5.4 泛型类或泛型接口
5.4.1 回忆之前学习的类或接口
java.lang.Comparable:自然比较接口,它有一个抽象方法:int compareTo(T o),这里的T代表要比较大小的对象类型,T代表Type
java.util.Comparator:定制比较器接口,它也有一个抽象方法:int compare(T o1,T o2) ,这里的T代表要比较大小的对象类型
java.util.Collection:Collection集合根接口,这里的E代表的是集合元素的类型,E代表element元素
java.util.List、Set、Queue、Deque等子接口。
java.lang.Iterable:可迭代的接口
java.util.Iterator:迭代器接口
java.util.ArrayList、Vector、LinkedList、Stack:List系列集合类型
java.util.HashSet、LinkedHashSet、TreeSet:Set系列集合的类型。
5.4.2 如何使用一个泛型类或泛型接口?
如果一个类或接口名后面声明(定义)了泛型,那么使用该类或接口时,最好指定该泛型的具体类型,否则会有警告。
注意:
- 为泛型指定具体类型时,必须指定为引用数据类型。不支持基本数据类型。
1、演示泛型类的使用
java
package com.atguigu.generic;
import org.junit.Test;
import java.util.ArrayList;
public class TestUseGenericClass {
@Test
public void test1()throws Exception{
//ArrayList<E>,这里<E>代表的是元素的类型
//存储整数
// ArrayList<int> list = new ArrayList<int>();//不支持基本数据类型
ArrayList<Integer> list = new ArrayList<Integer>();//只支持基本数据类型
list.add(1);
// list.add(1.0);//报错,它们都不能自动转为Integer对象
// list.add("hello");//报错,它们都不能自动转为Integer对象
}
@Test
public void test2()throws Exception{
//存储小数
ArrayList<Double> list = new ArrayList<Double>();
// list.add(1);//错误,1是int类型,它可以自动转为double(自动类型提升),但是不能自动转为Double(自动装箱)
list.add(1.0);
list.add((double)1);
list.add(1d);//在数字后面加d或D表示double
}
}
2、演示泛型接口的使用
(1)Comparable

java
package com.atguigu.generic;
/*
需求:要求员工类Employee实现Comparable<T>接口
T是Type单词的缩写,是要比较大小的对象的类型,
这里是两个员工对象要比较大小,T是Employee类型
员工对象默认按照id比较大小
*/
public class Employee implements Comparable<Employee>{
private int id;
private String name;
private double salary;
public Employee() {
}
public Employee(int id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", salary=" + salary +
'}';
}
@Override
public int compareTo(Employee o) {
return id - o.id;
}
}
(2)Comparator
java
package com.atguigu.generic;
import java.util.Comparator;
/*
需求:定制一个比较器,用于比较两个员工对象的大小,
要求:先按照薪资比较大小,如果薪资相同,再按照编号比较大小
实现Comparator<T>接口,这里的T代表要比较大小的对象的类型,这里是员工类
*/
public class EmployeeSalaryComparator implements Comparator<Employee> {
@Override
public int compare(Employee o1, Employee o2) {
// return o1.getSalary() - o2.getSalary();//double - double,结果是double,返回值类型是int
// return (int)(o1.getSalary() - o2.getSalary());//语法上没毛病, 15000.56 15000.42 看业务层面是否允许这样误差
/* if(o1.getSalary() > o2.getSalary()){
return 1;
}else if(o1.getSalary() < o2.getSalary()){
return -1;
}else{
return o1.getId() - o2.getId();
}*/
int result = Double.compare(o1.getSalary(), o2.getSalary());
//如果o1.getSalary() 大于 o2.getSalary(),该方法结果返回正整数
//如果o1.getSalary() 小于 o2.getSalary(),该方法结果返回负整数
//如果o1.getSalary() 等于 o2.getSalary(),该方法结果返回零
/*
Integer.compare(o1.getId(),o2.getId())比较两个int值,
//如果o1.getId() 大于 o2.getId(),该方法结果返回正整数
//如果o1.getId() 小于 o2.getId(),该方法结果返回负整数
//如果o1.getId() 等于 o2.getId(),该方法结果返回零
等价于 o1.getId() - o2.getId()
*/
return result == 0 ? Integer.compare(o1.getId(),o2.getId()): result;
}
}
(3)迭代器等其他接口
java
package com.atguigu.generic;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.function.Predicate;
public class TestGenericInterface {
@Test
public void test1()throws Exception{
ArrayList<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
//需求:使用Iterator遍历集合的元素
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String str = iterator.next();
System.out.println(str);
}
}
@Test
public void test2()throws Exception{
//根据条件删除元素
ArrayList<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
//需求:删除包含o字母的元素
/*
方式一:removeIf
*/
list.removeIf(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("o");
}
});
System.out.println(list);
}
@Test
public void test3()throws Exception{
//根据条件删除元素
ArrayList<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
//需求:删除包含o字母的元素
/*
方式二:迭代器删除
*/
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String str = iterator.next();
if(str.contains("o")){
iterator.remove();
}
}
System.out.println(list);
}
}
5.4.3 自定义泛型类或泛型接口
1、语法规范
java
【修饰符】 class 类名<泛型字母列表>{
}
【修饰符】 interface 接口名<泛型字母列表>{
}
注意:
- <泛型字母列表>,可以是1个也可以是多个字母。
- <泛型字母列表>,不推荐用单词,建议使用单个的大写字母
- 为什么不用单词?容易引起误会

需求:
声明一个泛型类Coordinate坐标,坐标类中应该包含两个属性,分别是x和y,代表经度和维度值。
这里的x和y的类型不确定,可能是Integer,Double,String等。
2、演示定义1个泛型
java
package com.atguigu.generic;
/*
声明一个泛型类Coordinate坐标,坐标类中应该包含两个属性,分别是x和y,代表经度和维度值。
这里的x和y的类型不确定,可能是Integer,Double,String等。
*/
public class Coordinate<T> {
private T x;
private T y;
//同一个对象的x,y的类型一定相同
/*public static void method(T t){//在类名后面定义的泛型,不能用于静态成员
//因为类名后面的泛型T,要确定具体类型,通常在new对象时,而静态方法和对象无关。
}*/
public Coordinate() {
}
public Coordinate(T x, T y) {
this.x = x;
this.y = y;
}
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
@Override
public String toString() {
return "Coordinate{" +
"x=" + x +
", y=" + y +
'}';
}
}
3、演示定义2个泛型
java
package com.atguigu.generic;
//T,U代表任意类型
public class ZuoBiao<T,U> {
private T x;
private U y;
//同一个对象的x,y可能类型不同,也可能相同
public ZuoBiao() {
}
public ZuoBiao(T x, U y) {
this.x = x;
this.y = y;
}
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public U getY() {
return y;
}
public void setY(U y) {
this.y = y;
}
@Override
public String toString() {
return "ZuoBiao{" +
"x=" + x +
", y=" + y +
'}';
}
}
3、演示使用自定义泛型类
java
package com.atguigu.generic;
import org.junit.Test;
public class TestCoordinate {
@Test
public void test1()throws Exception{
//创建坐标类的对象
/* Coordinate<String> c1 = new Coordinate<String>("东经35.6","北纬25.6");
Coordinate<Double> c2 = new Coordinate<Double>(35.6,25.6);*/
//从JDK1.7开始,左边写了<>中的类型,右边可以省略,但是<>不要省略
Coordinate<String> c1 = new Coordinate<>("东经35.6","北纬25.6");
//c1对象的x,y都是String类型
Coordinate<Double> c2 = new Coordinate<>(35.6,25.6);
//c1对象的x,y都是Double
System.out.println(c1);
System.out.println(c2);
}
@Test
public void test2()throws Exception{
//x是String,y是Double
ZuoBiao<String,Double> z1 = new ZuoBiao<>("东经35.6",25.6);
//z1对象的x,y分别是String和Double
ZuoBiao<String,String> z2 = new ZuoBiao<>("东经35.6","北纬25.6");
//z2对象的x,y都是String
}
}
5.5 泛型方法
5.5.1 什么情况下使用泛型方法呢?
- 当某个方法的形参类型等不确定,可以在方法的返回值类型前面加泛型的声明。而且这个泛型只在当前方法有效,和其他方法无关。
- 当某个静态方法的形参类型等不确定,可以在方法的返回值类型前面加泛型的声明。
总结:
- 如果某个类中,多个地方(多个成员变量,多个成员方法,都有未知的类型,而且它们是统一,那么就用泛型类或泛型接口)
- 如果是类或接口中单个方法某个类型未知,那么就使用泛型方法。
java
package com.atguigu.generic;
/*
Example:例子。
*/
public class Example<T> {
public void m1(T t){
//...
}
public void m2(T t){
//...
}
//以上写法,表示同一个对象的m1和m2方法的形参类型一定是一样的
//问题?如果希望同一个对象的m1和m2方法的形参类型不一样,怎么办?
//方式一:多定义几个字母
//方式二:每个方法单独自己定义泛型
}
java
package com.atguigu.generic;
public class Demo{
public <T> void m1(T t){
}
public <T> void m2(T t){
}
//以上写法,m1和m2方法的形参类型是独立
//m3的形参类型也是独立的
public static <T> void m3(T t){
}
}


5.5.2 之前的API中哪些方法是泛型方法?
java.util.Collection接口中有如下两个方法:
- Object[] toArray():不管元素什么类型,统一返回Object[]数组
- T[] toArray(T[] a):可以返回不同类型的数组
java.util.Arrays数组工具类中有一个这样的方法:
- public static List asList(T... a):将多个元素放到一个List集合中
java
package com.atguigu.generic;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class TestGenericMethod {
@Test
public void test1()throws Exception{
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
//Object[] toArray():不管元素什么类型,统一返回Object[]数组
Object[] objects = list.toArray();
//进一步遍历objects数组
for (int i = 0; i < objects.length; i++) {
String str = (String) objects[i];//还得强转,有点麻烦
}
}
@Test
public void test2()throws Exception{
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
//<T> T[] toArray(T[] a):可以返回不同类型的数组
// String[] strings = new String[list.size()];
String[] strings = new String[0];//这里长度是多少不重要,因为集合的toArray方法中只是为了得到它的类型
System.out.println(strings.getClass());//它里面用的是这个信息,数组的类型
String[] array = list.toArray(strings);
//进一步遍历array数组
for (int i = 0; i < array.length; i++) {
System.out.println(array[i] +",长度:" + array[i].length());
}
}
@Test
public void test3()throws Exception{
List<String> stringList = Arrays.asList("hello", "java", "world");
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
//顺便多说一句,asList方法返回的集合类型不是咱们上午学的ArrayList,Vector等类型,而是Arrays里面的一个内部类类型
System.out.println(stringList.getClass());//class java.util.Arrays$ArrayList
//而且这个内部类的集合,不支持添加元素,删除元素等操作
stringList.add("chai");//java.lang.UnsupportedOperationException不支持该操作异常
}
}
5.6 定义泛型时,能不能限定范围?
5.6.1 泛型上限
定义泛型时,能不能限定范围?
答案:可以,可以限定上限。
语法格式:
java
<T extends 上限> :这里的extends表示 T类型 <= 上限类型
子类 < 父类
实现类 < 父接口
子接口 < 父接口
<T extends 上限1 & 上限2 & 上限3>:这里&,表是要同时满足
T要同时 <= 上限1,上限2,上限3,而且相当于T同时继承或实现它们
上限1,上限2,上限3这些上限中,只能出现1个是类,其余只能是接口,而且类要在最左边,接口在后面。
1、演示一个上限
需求:声明一个学生类,这个学生类与我们以往声明的学生类不一样,它的属性有姓名和成绩,但是这个成绩,可能是Integer,Double,BigInteger等数字类型,但是不能是String等其他类型。
java
package com.atguigu.generic;
/*
需求:声明一个学生类,这个学生类与我们以往声明的学生类不一样,它的属性有姓名和成绩,
但是这个成绩,可能是Integer,Double,BigInteger等数字类型,但是不能是String等其他类型。
*/
public class Student <T extends Number>{
private String name;
private T score;
public Student() {
}
public Student(String name, T score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public T getScore() {
return score;
}
public void setScore(T score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
java
package com.atguigu.generic;
import org.junit.Test;
import java.util.ArrayList;
public class TestStudent {
@Test
public void test1()throws Exception{
Student<Integer> s1 = new Student<>();
Student<Double> s2 = new Student<>();
// Student<String> s3 = new Student<>();//报错,因为String不满足 String <= Number条件
}
}
2、演示多个上限
java
package com.atguigu.generic;
/*
需求:声明一个学生类,这个学生类与我们以往声明的学生类不一样,它的属性有姓名和成绩,
但是这个成绩,可能是Integer,Double,BigInteger等数字类型,但是不能是String等其他类型。
同时成绩的类型,必须实现Comparable接口,Cloneable接口等
*/
public class XueSheng <T extends Number & Comparable & Cloneable>{
}
java
package com.atguigu.generic;
import org.junit.Test;
public class TestXueSheng {
@Test
public void test1()throws Exception{
// XueSheng<Integer> x = new XueSheng<Integer>();
//错误,因为Integer没有实现Cloneable接口
}
}
5.6.2 泛型擦除
泛型的擦除:一个泛型类或泛型接口在使用时,不去指定它的泛型,就称为泛型擦除。
泛型擦除后,泛型按什么类型处理呢?
如果泛型擦除了,默认按照第一个上限处理,如果没有指定上限,上限就是Object。
java
@Test
public void test2()throws Exception{
//Student<T extends Number>,本来应该指定T的类型,现在不指定了,称为泛型擦除
Student s1 = new Student();
Number score = s1.getScore();
}
@Test
public void test3()throws Exception{
//ArrayList<E>,本来要指定E的类型,现在不指定了,称为泛型擦除
ArrayList list = new ArrayList();
list.add("hello");
}
5.7 泛型通配符(了解)
5.7.1 什么情况下使用泛型通配符
当使用一个已经声明好的泛型类或泛型接口,例如:Collection、ArrayList,Comparable等,用它们声明方法的形参,或变量时,仍然无法确定,该指定为什么类型,就可以使用通配符<?>。
5.7.2 使用泛型通配符有几种形式
答案:使用形式有3种,以ArrayList
- ArrayList<?>:这里?表示任意类型
- ArrayList<? extends 上限>:这里的?表示 <= 上限的任意类型
- ArrayList<? super 下限>:这里的?表示 >= 下限的任意类型
java
package com.atguigu.wild;
import org.junit.Test;
import java.util.ArrayList;
public class TestWildCard {
@Test
public void test1()throws Exception{
//调用m1方法,可以传入的集合是什么样的呢?
ArrayList<String> list1 = new ArrayList<>();
list1.add("hello");
list1.add("world");
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(666);
list2.add(888);
ArrayList<Object> list3 = new ArrayList<>();
list3.add("尚硅谷");
list3.add(666);
m1(list1);
m1(list2);
m1(list3);
}
public void m1(ArrayList<?> list){
for (Object o : list) {
System.out.println(o);
}
//编译时无法确定?是啥类型,编译器认为它添加什么都是有风险的,全部报错
/* list.add("hello");
list.add(1);
list.add(1.0);*/
list.add(null);
}
@Test
public void test2()throws Exception{
//调用m2方法,可以传入的集合是什么样的呢?
ArrayList<String> list1 = new ArrayList<>();
list1.add("hello");
list1.add("world");
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(666);
list2.add(888);
ArrayList<Object> list3 = new ArrayList<>();
list3.add("尚硅谷");
list3.add(666);
// m2(list1);//错误, list1的泛型<String>不满足 String <= Number条件
m2(list2);//对
// m2(list3);//错误, list3的泛型<Object>不满足 Object <= Number条件
}
public void m2(ArrayList<? extends Number> list){
for (Number number : list) {
System.out.println(number);
}
}
@Test
public void test3()throws Exception{
//调用m3方法,可以传入的集合是什么样的呢?
ArrayList<String> list1 = new ArrayList<>();
list1.add("hello");
list1.add("world");
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(666);
list2.add(888);
ArrayList<Object> list3 = new ArrayList<>();
list3.add("尚硅谷");
list3.add(666);
// m3(list1);//错误, list1的泛型<String>不满足 String >= Number条件,String和Number没关系
// m3(list2);//错误, list2的泛型<Integer>不满足 Integer >= Number条件,Integer<Number的关系
m3(list3);//对
}
public void m3(ArrayList<? super Number> list){
for (Object o : list) {
System.out.println(o);
}
}
@Test
public void test4()throws Exception{
//演示m4方法调用
//调用m4方法,可以传入的集合是什么样的呢?
ArrayList<String> list1 = new ArrayList<>();
list1.add("hello");
list1.add("world");
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(666);
list2.add(888);
ArrayList<Object> list3 = new ArrayList<>();
list3.add("尚硅谷");
list3.add(666);
// m4(list1);//错误,m4方法的形参已经明确是ArrayList<Object>,那么实参的泛型只能是<Object>
// m4(list2);
m4(list3);
}
public void m4(ArrayList<Object> list){
for (Object o : list) {
System.out.println(o);
}
}
@Test
public void test5()throws Exception{
//调用m5方法,可以传入的集合是什么样的呢?
ArrayList<String> list1 = new ArrayList<>();
list1.add("hello");
list1.add("world");
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(666);
list2.add(888);
ArrayList<Object> list3 = new ArrayList<>();
list3.add("尚硅谷");
list3.add(666);
m5(list1);
m5(list2);
m5(list3);
}
public void m5(ArrayList list){//啥都不写,泛型擦除,唯一的小瑕疵,有警告
for (Object o : list) {
System.out.println(o);
}
}
@Test
public void test6()throws Exception{
//调用m6方法,可以传入的集合是什么样的呢?
ArrayList<String> list1 = new ArrayList<>();
list1.add("hello");
list1.add("world");
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(666);
list2.add(888);
ArrayList<Object> list3 = new ArrayList<>();
list3.add("尚硅谷");
list3.add(666);
m6(list1,"chai");
m6(list2,1);
m6(list3,1.0);
}
public <T> void m6(ArrayList<T> list,T t){
for (T o : list) {
System.out.println(o);
}
list.add(t);
}
public void m7(ArrayList<?> list1, ArrayList<?> list2){//这里?独立的
}
public <T> void m8(ArrayList<T> list1, ArrayList<T> list2){//这里T是同一种类型
}
}
m4(list3);
}
public void m4(ArrayList<Object> list){
for (Object o : list) {
System.out.println(o);
}
}
@Test
public void test5()throws Exception{
//调用m5方法,可以传入的集合是什么样的呢?
ArrayList<String> list1 = new ArrayList<>();
list1.add("hello");
list1.add("world");
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(666);
list2.add(888);
ArrayList<Object> list3 = new ArrayList<>();
list3.add("尚硅谷");
list3.add(666);
m5(list1);
m5(list2);
m5(list3);
}
public void m5(ArrayList list){//啥都不写,泛型擦除,唯一的小瑕疵,有警告
for (Object o : list) {
System.out.println(o);
}
}
@Test
public void test6()throws Exception{
//调用m6方法,可以传入的集合是什么样的呢?
ArrayList<String> list1 = new ArrayList<>();
list1.add("hello");
list1.add("world");
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(666);
list2.add(888);
ArrayList<Object> list3 = new ArrayList<>();
list3.add("尚硅谷");
list3.add(666);
m6(list1,"chai");
m6(list2,1);
m6(list3,1.0);
}
public <T> void m6(ArrayList<T> list,T t){
for (T o : list) {
System.out.println(o);
}
list.add(t);
}
public void m7(ArrayList<?> list1, ArrayList<?> list2){//这里?独立的
}
public <T> void m8(ArrayList<T> list1, ArrayList<T> list2){//这里T是同一种类型
}
}