泛型学习笔记
集合是为了解决数组某些存储限制的特点出现的,但是数组有一个优点就是存放的数据的类型是确定的,但是集合容器在声明阶段存入的对象是什么类型是不确定的,所以在JDK1.5之前只能把元素的类型设计为Object类型,JDK1.5后,出现了泛型。这个时候除了对象的类型是不确定的,存储方式和如何管理等是确定的,因此把元素的类型设计成一个参数,这个类型参数叫做泛型。如:Collection< E>,List< E>,ArrayList< E>…这个< E>就是类型参数,即泛型。Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException,同时代码更加简洁。
- 集合中没有泛型时,任何类型都可以添加进集合中:类型不安全。读取数据的时候,因为类型不同可能需要强制转换,强制转换可能报异常。(ClassCastException)
- 集合中有泛型时,只有指定类型的对象可以添加到集合中:类型安全。读取出来的对象也不需要强制转换:便捷。
什么是泛型
- 泛型就是允许在定义类,接口时,通过一个标识表示类中某个属性的类型或者某个方法的返回值类型及参数类型。这个类型参数在使用的时候才确定。(即传入实际的类型参数(类型实参))
- 泛型允许我们在创建集合时再指定集合中元素的类型,如:List< String>,表明该List只能存放String类型的对象。
泛型使用时的注意点
- 泛型类可能有多个类型参数,此时应该将多个参数放在同一对<>内,并用“,”隔开。
- 泛型类的构造器并没有在原来的基础上增加一对<>。
- 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
- 泛型不同的引用不能相互赋值。(如,在编译时,尽管ArrayList< String>和ArrayList< Integer>是两种类型,但是,在运行时,只有一个ArrayList被加载到JVM中。)
- 泛型如果不指定确定的类型参数,那么均按照Object类型处理,但不等价于Object类型。(不使用则已,一使用就不要停^ . ^)
- 如果泛型类是一个接口或者抽象类,则不可创建泛型对象。
- JDk1.7之后,泛型的操作得到简化,如ArrayList< String> list = new ArrayList<类型实参可省略>();
- 泛型的指定中不可以用基本数据类型,如果是基本数据类型,应该使用对应的包装类。
- 在类/接口上声明泛型,在本类或者本接口中即可代表某种类型,可以作为非静态属性的类型,非静态方法的返回值类型,非静态方法的参数类型,但是在静态方法中不能使用类的泛型。若要在静态方法中使用泛型,该类型实参的标识应该区别于类的标识。
- 异常类不能是泛型的。
- 父类有泛型,子类可以选择保留泛型(全部保留,部分保留)也可选择指定泛型类型也可不要泛型,子类除了可以指定或保留父类的泛型,还可以增加自己的泛型。示例:
public class Father <T,E>{
}
// 子类不保留父类的泛型
public class Son<A> extends Father{// 等价于class Son extends Father<Object,Object>{}
}
public class Son<A> extends Father<String,Integer>{
}
// 子类部分保留父类的泛型
public class Son<A,T> extends Father<T,String>{
}
// 子类全部保留父类的泛型
public class Son<A,T,E> extends Father<T,E>
泛型在继承方面的体现
- 类A是类B的父类,但是G< A>和G< B>二者不具备父子关系,二者是并列关系。
- 类A是类B的父类,A< G>是B< G>的父类。
public class GenericTest {
@Test
public void test1(){
ArrayList<List> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
list1 = null;
list2 = null;
// list1 = list2; 编译错误
List<String> list3 = null;
ArrayList<String> list4 = null;
list3 = list4; // 编译通过
}
}
自定义泛型结构
- 泛型方法:在方法中出现泛型的结构,泛型的参数与类的泛型参数没有任何关系。
- 可以声明为static,因为泛型方法的参数是在调用方法时才确定的,并非在实例化所在类时确定的。
- 方法可以泛型化,不管此时方法所在的类是不是泛型类。
- 泛型方法的格式:(在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型)
public <E> void say(int id,E e){
}
泛型应用场景举例
通过DAO类的方法,实现对User的操作。
DAO类:
public class DAO<T> {
// 注意,此时map只是作为一个属性,并没有赋值,即没有创建实际的HashMap容器,在
private Map<String,T> map;
// 保存T类型的对象到Map成员中
public void save(String id,T entry){
map.put(id, entry);
}
// 从Map中获取id对应的对象
public T get(String id){
return map.get(id);
}
// 替换map中key为id的内容,改为entry对象
public void update(String id,T entry){
// 如果该key存在,则修改key对应的value,key不存在,则不修改
if (map.containsKey(id)){
map.put(id, entry);
}
}
// 返回map对象中存放的所有T对象
public List<T> list(){
// 创建一个List存放map的值
List<T> List = new ArrayList<>();
// 获取map所有的key构成的Set集合
Set<String> keySet = map.keySet();
// 通过迭代器,根据每一个key的值去调用get()获取map的值并存放在List中
Iterator<String> iterator = keySet.iterator();
while (iterator.hasNext()){
List = (ArrayList<T>) map.get(iterator.next());
}
return List;
}
// 删除指定id的对象
public void delete(String id){
map.remove(id);
}
public Map<String, T> getMap() {
return map = new HashMap<String,T>();
}
}
User类(JavaBean):
package com.solve.exer;
/**
* @Author:xiezr
* @Creat:2021-07-16 22:06
*/
public class User {
private int id;
private int age;
private String name;
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public User() {
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
// User要添加到Map中,所以重写hashCode()和equals()
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (id != user.id) return false;
if (age != user.age) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + age;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Test类:
package com.solve.exer;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* @Author:xiezr
* @Creat:2021-07-16 22:37
*/
public class Test {
public static void main(String[] args) {
User u1 = new User(101, 25, "常威");
User u2 = new User(102, 5, "来福");
User u3 = new User(103, 4, "旺财");
User u4 = new User(104, 999, "乌鸦坐飞机");
DAO<User> dao = new DAO<>();
Map<String, User> map = dao.getMap();
dao.save("221",new User(105,666,"龙卷风摧毁停车场"));
dao.save("222",u1);
dao.save("223",u2);
dao.save("224",u3);
dao.save("225",u4);
dao.delete("225");
// 遍历map
Set set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object o = map.get(iterator.next());
System.out.println(o);
}
// User user = dao.get("224");
// System.out.println(user);
//
// dao.update("224",new User(103,555,"一刀999"));
// System.out.println(dao.get("224"));
}
}
通配符
通配符:<?>
有限制的通配符:
- <? extends Number>:只允许泛型为Number及Number的子类的引用调用。(<=)
- <? super Number>:只允许泛型为Number及Number的父类的引用调用。(>=)
- <? extends Comparable>:只允许泛型为实现Comparable接口的实现类调用。
写操作
@Test
public void test2(){
Collection<?> collection = new ArrayList<>();
collection.add(new Object()); // 编译错误
}
因为我们不知道collection集合存放的元素是什么类型的,我们无法向其中添加对象,add(E e)有类型参数E作为集合的元素类型,我们传给add()的参数必须是E类型或是E类型的子类,但是此时集合collection的类型未知,即E也是未知类型,所以我们无法传进去任何东西。
例外:null可以传进去,他是所有类型的成员。
collection.add(null); // null可以传进去
读操作
虽然无法调用add()往collection里面添加元素,但是可以通过get()并获取其返回值,返回值是一个未知类型,但是总是一个对象Object。
@Test
public void test2(){
Collection<?> collection = new ArrayList<>();
ArrayList<String> list1 = new ArrayList<>();
list1.add("背过手");
list1.add("初学者");
list1.add("动物世界");
list1.add("狐狸");
// get()是List接口增加的,Collection接口没有,所以转换一下
List<?> list = new ArrayList<>(collection);
// 把list1赋值给list
list = list1;
System.out.println(list.get(0));
System.out.println(list.get(1));
}