☆泛型在集合、比较器中的使用
1.什么是泛型?
允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值或参数的类型。
这个类型参数将在使用时(例如,继承或实现这个接口、创建对象或调用方法时)确定(即传入实际的类型参数,也称为类型实参)
<E> 泛型参数 只能是引用数据类型
2.在集合中使用泛型之前可能存在的问题
问题1:类型不安全。因为此时add()的参数是Object类型,意味着任何类型的对象都可以添加成功
问题2:需要使用强转操作,繁琐。还有可能导致ClassCastException异常。
3.在集合、比较器中使用泛型(重点)
//在集合中使用泛型的例子
@Test
public void test2(){
List<Integer> list =new ArrayList<Integer>()
list.add(78);
list.add(76);
list.add(66);
list.add(99);
//编译报错,保证类型的安全
list.add("AA");
Iterator<Integer>iterator =list.iterator();
while(iterator.hasNext()){
//因为添加的都是Integer类型,避免了强转操作
Integer i= iterator.next();
int score =i;
System.out.println(score);
}
}
/*
*泛型在Map中使用的例子
*/
@Test
public void test3(){
//HashMap<String,Integer> map = new HashMap<String,Integer>();
//jdk7的新特性
HashMap<String,Integer> map = new HashMap<>();//类型推断
map.put("Tom",67);
map.put("Jerry",87);
map.put("Rose",99);
//Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
//Iterator<Map,Entry<String, Integer>> iterator = entrySet.iterator();
while(iterator.hasNext()){
Map.Entry<String,Integer> entry= iterator.next();
String key =entry.getKey();
Integer value =entry.getValue();
System.out.println(key +"--->"+ value);
}
jdk10新特性 编译器知道类型 为了更好的可读性 写成var
4.使用说明
集合框架在声明接口和其实现类时,使用了泛型(jdk5.0),在实例化集合对象时如果没有使用泛型,则认为操作的是Object类型的数据。
如果使用了泛型,则需要指明泛型的具体类型。一旦指明了泛型的具体类型,则在集合的相关的方法中,凡是使用了类的泛型的位置,都替换为具体的泛型类型
在比较器中使用的例题
//自然排序
public class Employee implements Comparable<Employee> {
// 按照name从低到高排序
@Override
public int compareTo(Employee o) {
//String重写过compareTo可以直接用
return this.name.compareTo(o.name);
}
}
//定制排序Comparator
Comparator<Employee> comparator = new Comparator<Employee>() {
@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();
//方式二:MyDate实现Comparable
return b1.compareTo(b2);
}
// return 0;
throw new RuntimeException("传入的数据类型不一致!");
}
};
TreeSet<Employee> set = new TreeSet<>(comparator);
public class MyDate implements Comparable<MyDate>{
// 按照生日从小到大排序
@Override
public int compareTo(MyDate o) {
int yearDistince = this.getYear() - o.getYear();
if (yearDistince != 0) {
return yearDistince;
}
int monthDistince = this.getMonth() - o.getMonth();
if (monthDistince != 0) {
return monthDistince;
}
return this.getDay() - o.getDay();
}
}
// 使用集合的removeIf方法删除偶数,为Predicate接口指定泛型<Integer>
list.removeIf(new Predicate<Integer>() {
@Override
public boolean test(Integer value) {
return value % 2 == 0;
}
});
//以后这里会用lambda表达式写 更简单
自定义泛型
1.自定义泛型类\接口
本质:有不确定的类型
1.1 格式
泛型参数:T、E 【Type、 Element啥的】
泛型参数指定的类型 只能是引用数据类型
class 类名<T>{
//声明了类的泛型参数以后,就可以在类的内部使用此泛型参数。
T t;//表示元素t的类型为T
}
//泛型参数可以有多个 看有多少不确定的
interface 接口名<T1,T2>{
}
//实例化时,就可以指明类的泛型参数的类型
Order order =new Order(); //没用泛型 向下兼容 5.0以前没泛型
Object obj=order.getT(); //不指定 不确定 按Object算
//泛型参数在指明时,是不可以使用基本数据类型的!但是可以使用包装类替代基本数据类型。
//0rder<int> order1=new 0rder<>();
0rder<Integer>order2=new rder<>();//①在实例化时,可以指明类的泛型参数的具体类型!
Integer t= order2.getT();
因为类型不确定 才用的泛型 父、子类 类型确定与否 排列组合下来共有种情况
都不用泛型
//继承泛型类的子类的一些情况
public class SubOrder1 extends 0rder{}//不是泛型类 没有<> 没有不确定的类型
//实例化Sub0rder1
SubOrder1 sub1 = new SubOrder1();
//SubOrder1<Integer>sub2 = new SubOrder1<>();//因为Sub0rder1不是泛型类,此处编译错误
//②在提供子类时 可以指明具体类型
public class Sub0rder2 extends 0rder<Integer>{}//没有不确定的类型 没有参数存在 不是泛型类
public class SubOrder3<T> extends 0rder<T>{}//完全延续父类的 不确定的 泛型参数 是泛型类
SubOrder3<String> sub2 = new SubOrder3<>();
String t1 = sub2.getT();
sub2.show("AA") ;
public class SubOrder4<E>extends 0rder<Integer>{//父类的确定 但子类自己还有不确定类型 是泛型类
E e;
}
public class SubOrder5<T,E>extends 0rder<T>{//父类不确定之余 子类还有 是泛型类
E e;
public SubOrder5(T t,int orderId,E e){
super(t, orderId);
this.e = e;
}
}
SubOrder5<String,Integer> sub4 = new SubOrder5<>();
String t3 = sub4.getT();
Integer e1 = sub4.getE();
//在为泛型类指明子类时 可以指明类型 也可以不指明
1.2 使用说明
① 在声明完自定义泛型类以后,可以在类的内部(比如:属性、方法、构造器中)使用类的泛型。
② 在创建自定义泛型类的时,可以指明泛型参数类型。一旦指明,内部凡是使用类的泛型参数的位置,都具体化为指定的类的泛型类型
③ 如果在创建自定义泛型类的对象时,没有指明泛型参数类型,那么泛型将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。【泛型在继承上的体现】
经验:泛型要使用 一路都用。要不用,一路都不要用。
④泛型的指定中必须使用引用数据类型。不能使用基本数据类型,此时只能使用包装类替换。
⑤ 除创建泛型类对象外,子类继承泛型类时、实现类实现泛型接口时,也可以确定泛型结构中的泛型参数 2
如果在给泛型类提供子类时,子类也不确定泛型的类型,则可以继续使用泛型参数T。3
还可以在现有的父类的泛型参数的基础上,新增泛型参数。4 5
1.3 注意点
- 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。例如:
<E1, E2, E3>
JDK7.0
开始,泛型的简化操作:ArrayList<Fruit> flist = new ArrayList<>();
类型推断 等号右边的尖括号里可以不写- 如果泛型结构是一个接口或抽象类,则不可以创建泛型类的对象。
- 不能使用
new E[]
,但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList
源码中声明:Object[] elementData
,而非泛型参数类型数组。 - 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,但不可以在静态方法中使用类的泛型。【静态方法加载时 泛型的类型还不确定 】
- 异常类不能是带泛型的。
2.自定义泛型方法
2.1 问题:在泛型类的方法中,使用了类的泛型参数。那么此方法是泛型方法吗?
no 和类没关系
2.2 格式
权限修饰符 <T>返回值类型 方法名(形参列表){
//通常声明了泛型方法后 在形参列表 或返回值类型 的位置会出现泛型参数T
}
2.3 举例
//自定义泛型方法 <E> E 声明 不是有个类叫E 是参数为E 要在返回值类型前
//方法声明里有 <E>这个符号 就是泛型方法
public static <E> E method(E e){
return null;
}
//该方法被调用时 类型确定
2.4说明
- 声明泛型方法时,一定要添加泛型参数
- 泛型参数需要在方法调用时,指明其具体的类型
- 泛型方法可以根据需要声明为static的 【反正调用的时候会指明就行】
- 泛型方法所属的类是否是一个泛型类,都可以
和所属类没有关系 只要定义方法时 类型有不确定 就可以整成泛型方法
泛型的应用举例
DAO:data(base)access object。内部封装了操作数据库相关表的增删改査操作。(CRUD)
ORM思想(object relational mapping)
数据库中的一个表 与 Java中的一个类对应
表中的一条记录 与 Java类的一个对象对应
表中的一个字段(或列)与 Java类的一个属性(或字段)对应
//增删改查操作类似 但操作的表类型不一样 不确定
public class DAO<T> {
//增
public void insert(T bean){
//通过相应的sql语句,将bean对象的属性值写入到数据表中。
}
//在继承的子类里指明
public class CustomerDAO extends DAO<Customer>{}//子类不是泛型类
CustomerDAO daol=new CustomerDAO();
dao1.insert(new Customer());
Customer customer = dao1.queryForInstance( id:1)
//定义独立的泛型方法
//比如:查询表中的记录数(返回值类型E:Long类型)
public <E> E getValue(string sql){
return null;
}
例题
public void save(String id,T entity){ //保存T 类型的对象到 Map 成员变量中
if(!map.containsKey(id)){//不包含 才添加 和修改的put区分开 那个包含时才修改
map.put(id,entity);
}
}
public List<T> list(){ //返回 map 中存放的所有 T 对象
//错误的:
Collection<T> values = map.values();
System.out.println(values.getClass());
return(List<T>)values;//不是什么多态 强制转换会出错 报ClassCastException异常
//正确的:(方式一)
Collection<T> values = map.values();
ArrayList<T> list = new ArrayList<>();
list.addAll(values);
return list;
//正确的:(方式2)
Collection<T> values = map.values();
ArrayList<T> list = new ArrayList<>(values);
}
public classStudent<T>{//属性的类型不确定
4 usages
private String name;4 usages
private T score;//成绩
}
泛型在继承上的体现
//区别于 基于继承性的 多态的使用
ArrayList<Object> list1 = null;
ArrayList<String> list2 = new ArrayList<>()
list1 = list2;
/*
* 反证法:
* 假设list1 = list2是可以的。
* list2.add("AA");
*
* list1.add(123);
*
*String str = list2.get(1);//相当于取出的123赋值给了str,错误的。
*/
-
类
SuperA
是类A
的父类,则G<SuperA>
与G<A>
的关系:G<SuperA>
和G<A>
是并列的两个类,没有任何子父类的关系。
比如:ArrayList<Object>
、ArrayList<String>
没有关系。 -
类
SuperA
是类A
的父类或接口,SuperA<G>
与A<G>
的关系:SuperA
与A 有继承或实现的关系,即A 的实例可以赋值给SuperA 类型的引用(或变量) 比如:List
与ArrayList
List<String> list1 = null;
ArrayList<String> list2 = new ArrayList<>();
list1 = list2;//泛型里一样 可以用多态
通配符的使用
似乎加了【确定】泛型以后通用性差了一些
ArrayList<Object> list1 = null;
ArrayList<String> list2 = new ArrayList<>()
//上面两个类并行不能使用多态
//使用通配符 ?
List<?> list = null;
List<0bject> list1 = null;
List<String> list2 = null;
list = List1;
list = list2;
//List作为list1 list2的共同父类?
//有通配符时可以正常读 但不能写
List<?> list = null;
List<String> list1 = new ArrayList<>()
list1.add("AA");
list = list1;
//读取数据(以集合为例说明)
0bject obj = list.get(0);
System.out.println(obj);
//写入数据(以集合为例说明) 失败
list.add("BB");
//特例:可以将null写入集合中
list.add(null);
//遍历读取
public void method(List<?> list){
for(object obj : list){
System.out.println(obj);
}
}
-
通配符:
?
?的使用
。以集合为例:可以读取数据、不能写入数据(例外:null)? extends A
。以集合为例:可以读取数据、不能写入数据(例外:null)?super A
。以集合为例:可以读取数据、可以写入A类型或A类型子类的数据(例外:null) -
使用说明:
举例:ArrayList G\ 可以看做是G<A>类型的父类,即可以将G<A>的对象赋值给G <?>类型的引用(或变量)
-
读写数据的特点(以集合ArrayList为例说明)
读取数据:允许,读取的值的类型为0bject类型
写入数据:不允许,特例:写入null值。
-
有限制条件的通配符
List<? extends A>
:可以将List或List赋值给List<?extends A>。其中B类是A类的子类。【看作<=】List<? super A>
:可以将List或List赋值给List<?extends A>。其中B类是A类的父类【看作>=】
List<?extends Father> list = null; List<Object> list1 = null; List<Father> list2 = null; List<Son> list3 = null; list = list1;//赋值失败 list = list2; list = list3;
-
有限制条件的统配符的读写操作(难,了解)
技巧:开发中,遇到了带限制条件的通配符,在赋值时,如果没报错,那就正常使用。
如果报错了,知道不能这样写。改改!List<?extends Father> list = null; List<Father> listl = new ArrayList<>(); list1.add(new Father()); list = list1; //读取数据:可以的 Father father = list.get(0); //写入数据:不可以的。[-∞,+∞] ---->[-∞,Father] //例外:null list.add(null); list.add(new Father());//这么写有可能倒反天罡 list.add(new Son()); List<? super Father> list = null;List<Father> list1 = new ArrayList<>(); List1.add(new Father()); list = list1; //读取数据:可以的 赋给铁定比自己大的 Object obj = list.get(0); //写入数据:可以将Father及其子类的对象添加进来List.add(null); list.add(new object());//不行 list.add(new Father()); list.add(new Son());
下往上赋是多态 反过来不行
题
Java
的泛型是什么?有什么好处和优点?JDK
不同版本的泛型有什么区别?(软**动力)
泛型,是程序中出现的不确定的类型。
以集合类举例:把一个集合中的内容限制为一个特定的数据类型,这就是generic
背后的核心思想。[学数组 元素类型确定]
JDK7.0
新特性:ArrayList<String> list = new ArrayList<>(); // 类型推断 //后续版本的新特性: Comparator<Employee> comparator = new Comparator<>() {}; // 类型推断