将一个对象放入集合中,集合不会记住这个对象的具体类型(向上转型Object tmp = new Date();)当再次从集合中取出对象时,该对象的编译期类型变成了Object类型,但是其运行时类型仍然为原始的类型
public static void main(String[] args) {
List list = new ArrayList();
list.add("123");
list.add(123);// 集合中不能存放原生类型,这里会涉及自动装箱操作
list.add(new Date());
for (int i = 0; i < list.size(); i++) {
Object tmp = list.get(i);
Date dd = (Date) tmp;// 没有语法报错
System.out.println(dd);//这里需要调用Date类型的getYear方法,所以需要进行窄化操作
}
// 运行时java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.Date
}
报错原因:进行强制类型转换之前没有进行类型判定
List list = new ArrayList();
list.add("123");
list.add(123);// 集合中不能存放原生类型,这里会涉及自动装箱操作
list.add(new Date());
for (int i = 0; i < list.size(); i++) {
Object tmp = list.get(i);
if (tmp != null && tmp instanceof Date) {
Date dd = (Date) tmp;// 没有语法报错
System.out.println(dd.getYear() + 1900);
}
}
- 集合对象没有记录元素的具体类型,获取数据时需要进行类型转换,很容易出现运行时异常ClassCastException
- 存储数据时没有进行类型判定
需要一种方法实现集合能够记住集合中元素的类型,编译期能够进行合法类型判断,获取时不需要进行类型转换:
泛型:可以将运行时的类型检查搬到编译期实现,直接获取指定类型数据避免强制类型转换操作
public static void main(String[] args) {
List<Date> list = new ArrayList<>();
// list.add("123");编译期会报错,因为集合中要求只能存放Date类型数据
list.add(new Date());
for (int i = 0; i < list.size(); i++) {
Date dd = list.get(i);
System.out.println(dd.getYear() + 1900 + ":" + dd.getMonth() + 1);
}
}
泛型
泛型是 JDK 1.5 引入的一种机制,就是将数据类型参数化,作为一种类型安全机制而产生的。
泛型机制就是将类型检查从运行时提前到编译期,使用范型编写的代码比杂乱的使用Object,并需要再执行窄化操作处理的机制具备更好的可读性和安全性。
泛型在本质上就是类型的参数化,
泛型的定义
public interface List<E> extends Collection<E> {//这里<>中的内容就是类型参数,一般建议使用全大写的方式进行定义,例 如 T、E、ID 之类的形式
boolean add(E e);//定义 add 方法,要求传入的数据类型为 E
E get(int index);//E 表示获取的数据类型就是定义接口时指定的类型
}
调用
List<String> list = new ArrayList<String>();//就是将String传递给E,用于替代定义中的E
String str = list.get(0);
如果在调用时不进行声明,则系统默认为Object
Lsit<Date> list = new ArrayList<>();//使用菱形语法,从JDK1.7开始支持泛型推导
list.add("123");//编译时就进行语法报错,因为编译期会进行类型检查,"123"不是Date类型
list.add(123);//语法报错
list.add(new Date());//编译通过,类型合法
典型应用
案例:获取两个整数中的较大值
Integer max(Integer a,Integer b){
return a>b?a:b;
}
如果需要比较的不是Integer类型,而是double或者float类型,就需要再写max方法。引入泛型的目的就是在于定义max方法时可以不确定参数a和参数b的数据类型,而是等到调用的时候再确定参数的具体数据类型,这样只需要定义一个max方法即可,从而降低编程的工作量
对象比较Comparable接口
java预定义的接口
public interface Comparable<T> {
public int compareTo(T o);
}
这里使用了泛型<T>
,表示用于规范类的可比较性
compareTo(T o)当前类型对象比较时需要返回一个 int 整数,当当前对象大于参数 o 时返回一个正数,小于时返回一个负整数,等于返回为 0,进行比较的参数 o 必须是 T 类型
例如要求整数类型是可比较的
public final class Integer extends Number implements Comparable<Integer>, Constable, ConstantDesc {}
对应的方法实现
public int compareTo(Integer anotherInteger){
return compare(this.value,anotherInteger.value);
}
调用另外的方法
public static int compare(int x, int y){
return (x,y) ? -1 : ((x == y) ? 0 : 1);
}
使用泛型的方式定义max方法
public class MyTest{
public static<T extends Comparable<T>> T max(T t1,T t2){//静态方法中直接使用泛型的语法为<T>T,定义泛型时T extends Comparable,表示传入的类型必须实现了Comparable接口,否则编译错误
return t1.compareTo(t2)>0?t1:t2;
}
}
如果进行整型数据比较,需要先确定Integer必须实现了Comparable接口
Integer kk=MyTest.max(12,15);
如果是double类型数据,要先确定Double必须实现了Comparable接口
Double dd=MyTest.max(12.12,13.);
public class Test3 {
public static void main(String[] args) {
Integer kk = MyTest.max(12, 15);
System.out.println(kk);
double dd = MyTest.max(12.34, 55.);
System.out.println(dd);
Person p1 = new Person(1234.56);
Person p2 = new Person(345.67);
Person pp = MyTest.max(p1, p2);
System.out.println(pp);
}
}
class Person implements Comparable<Person> {
private double salary;
public Person(double salary) {
super();
this.salary = salary;
}
@Override
public int compareTo(Person o) {
double res = this.salary - o.salary;
if (Math.abs(res) < 1e-6)
return 0;
else if (res > 0)
return 1;
else
return -1;
}
@Override
public String toString() {
return "Person [salary=" + salary + "]";
}
}
在泛型出现之前,只有一种变通的方法就是将参数类型定义为Object,这种方法不能保证类型安全。泛型弥补了Object 这种做法所缺乏的类型安全,也简化了过程
使用泛型的好处
解决类型安全性的隐患,泛型类或者接口在取出对象时不需要再进行向下类型转换,因为可以认为存储的时候就是这种类型。泛型的使用让安全问题再编译时就报错,而不是运行时报错,这样方便及时准确的发现问题。
- 可读性,从字面上就可以判断集合中的内容类型
- 类型检查,避免插入非法类型的数据
- 获取数据时不再需要强制类型转换
泛型类
泛型类也叫做参数化类型,就是具有一个或者多个类型参数的类,一个泛型类可以有多个泛型声明,所有的泛型声明都应该在<>内部。
在当前类中 T 就是一个类型的说明,可以用在说明任何实例方法中的局部变量、方法的形参以及方法的返回值,类的成员变量;但是类型 T 不能直接使用在静态方法中
public class Generic<T>{ //<>中包含的全大写的名称就是泛型:形式参数
private T name; //在类中就可以使用使用泛型名称当作具体类型使用
public T getName(){ //方法可以直接使用泛型
return name;
}
public void setName(T name){
this.name=name;
}
}
使用
Generic<String> en=new Generic<>(); //使用时在具体确定 T 对应的类型为 String,第二处<>中没有内容,叫做泛型推导
en.setName("yanjun"); //en.setName(123)语法报错,因为 123 不是 String 类型
System.out.println(en.getName());
//注意传递给泛型参数的类型必须时类类型,不能使用 int 或者 char 之类的简单类型
如果定义了泛型类,但是引用时不声明泛型对应的类型值,则系统识别为 Object 类型
Generic en=new Generic();
en.setName("yanjun"); //有警告信息,但是语法正确,因为 String 也是 Object 类型
en.setName(123); //有警告信息,但是编译可以通过。因为 123 经过自动装箱操作后,可以识别为 Object 类型
带多个类型参数的泛型类
如果引用多个类型,可以使用逗号作为分隔符,例如<S,D>
类型参数名称可以使用任意字符串,但是一般建议使用有代表含义的单个字符,以便于和普通类型名称进行区分。
例如 T 代表 type,源数据 S,目标数据 D,子元素类型 E
注意问题
没有使用泛型时,只要是对象,不管是什么类型的对象,都可以存储到同一个集合中
List list=new ArrayList();
list.add(new Date());
list.add(123);
list.add(123.456);
list.add(new Date());
系统是按照 Object 识别类型。如果在 JDK1.5 之后还使用将各种类型放入到同一个集合中的写法,编译器会报一个 unChecked 警告信息
使用泛型集合时,可以将一个集合中的所有元素限定为一个特定类型,这样集合中就只能存储特定的类型的对象,这样比较安全;并且获取集合中存储的数据时,编译器也直到这个元素的类型,不需要进行窄化处理,这样使用也比较方便
List<Integer> list=new ArrayList<>(); 第 2 个类型可以不写,JDK1.7 引入的泛型推导
list.add(123.456) 编译期就可以发现这个错误,类型不合法
**记住:**Collection和 Collection是两个没有任何关系的参数化类型接口,不存在什么谁可以赋值给谁的可能
public class Test{
public static void mian(String[] args){
Collection<Object> coll = new ArrayList<String>();
}
}
集合类中的泛型
List<String> list=new ArrayList<>();
Map<String,Object> map=......;
List<Map<String,Object>> list=....
泛型只能使用引用类型,而不能使用基本类型,例如 List是错误
最佳软件实践:保持良好的编程风格,尽量使用泛型
有界类型
在实际应用中可能需要对传递的类型参数的具体类型进行限制。
public class MyClass {} //定义的泛型类要求传入的具体类型必须是 Number 的子类,也就是要求传入的具体类型必须是数值型。这里可以传入 Integer、Double 等类型,但是不能传入 String
java 中提供了有界类型,在指定一个类型参数时,可以指定一个上界,声明所有的实际类型都必须时这个超类的直接或者间接子类。
需求:要求对数值类型的不确定个数的数据进行累加
如果使用泛型定义,但是不使用上界约束
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AyVUmyfX-1646974081599)(C:\Users\阿白\AppData\Roaming\Typora\typora-user-images\image-20220217195827718.png)]
使用上界约束
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D8tFiLoL-1646974081600)(C:\Users\阿白\AppData\Roaming\Typora\typora-user-images\image-20220217195925244.png)]
注意:这里的关键字 extends 不是表示继承,只是用于说明传入的类型必须是 Number 类型或者 Number 类型的后代类型
调用方法 1:
Generic g=new Generic<>();
//注意传入 T 对应的类型为 Long
double sum=g.sum(1L, 2L, 3L, 4L);
System.out.println(sum);
可以给 T 传递类型 Long,是因为 Long 是 Number 的子类型,如果更换其它类型,则必须判断传入的类型是否为 Number 的后代类型
Generic<String> g=new Generic<>();
//编译报错,因为传入的 String 类型,不是 Number 类型的后代类型
调用方法 2:
Generic<Number> g=new Generic<>();
Double sum=g.sum(1, 2, 3, 4L); //因为 Integer 和 Long 类型都是 Number 的子类型
接口和类都可以作为泛型的上界,当使用接口作为上界时,关键字还是 extends,而不是 implements。同时允许一个参数类型有多个限界,限界类型可以使用&符号分割。如果使用只使用类型作为上界,则只能定义一个类型上界;由于 java 的单根继承体系的要求,所以不能使用同时使用多个类作为类型上界,即使是父子类型也不能定义。允许使用一个类和多个接口或者多个接口
语法错误
public class Generic<T extends Number & Integer>{}
语法正确
public class Generic<T extends Number & Comparable<T> & Serializable & Cloneable>{}
接口 Comparable
如果一个类实现了 Comparable 接口,则表示当前类型的对象是可比较大小的,也就是说可以进行排序。实现了Comparable 接口的类支持排序,也就是可以使用工具类 Collections 对集合对象进行排序
Comparable 接口中定义一个比较方法 compareTo(T obj),返回的 int 类型数据用于表示大小
public class Pig implements Comparable<Pig> {
private Long id;
private double weight;
public int compareTo(Pig pig){
double res=this.weight-pig.weight;
if(Math.abs(res)<1e-6) return 0;
else if(res>0) return 1;
else return -1;
}
}
使用
List<Pig> list=new ArrayList<>();
Random r=new Random();
for(int i=0;i<10;i++){
Pig tmp=new Pig(1L+i, r.nextDouble()*480+20);
list.add(tmp);
}
Collections.sort(list);
for(Pig p:list) System.out.println(p);
接口 Comparator
Comparator 是比较器接口。如果类本身不支持排序比较,即实现 Comparable 接口,则可以建一个类型的比较器专门用于排序比较。
例如 Pig 类没有实现 Comparable 接口,则使用 Collections.sort(list)则会报错。报错的原因是 Collection 工具类中的方法定义
public static <T extends Comparable <? super T >> void sort (List<T> list){
list.sort(null);
}
在方法上已经声明了要求传入的 T 类型必须实现了 Comparable 接口
根据开闭原则不修改源代码,可以额外引入比较器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1SmzbaPA-1646974081601)(C:\Users\阿白\AppData\Roaming\Typora\typora-user-images\image-20220217200755551.png)]
参数有 2 个,第一个参数为 List,第二个参数是 Comparator,? super T 表示传入的类型必须是 T 的超类
Comparator 比较器接口的定义,接口上的注解@FunctionalInterface 声明是一个函数式接口,可以使用 lambda表达式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WM0a3X89-1646974081601)(C:\Users\阿白\AppData\Roaming\Typora\typora-user-images\image-20220217200823908.png)]
具体编码实现
List<Pig> list = new ArrayList<>();
Random r = new Random();
for (int i = 0; i < 10; i++) {
Pig tmp = new Pig(1L + i, r.nextDouble() * 480 + 20);
list.add(tmp);
}
Comparator<Pig> c= (obj1,obj2)->{
double res = obj1.getWeight() - obj2.getWeight();
if (Math.abs(res)<(1e - 6)) {
return 0;
}else if (res > 0) {
retrun 1;
}else {
return -1;
}
};
Collectctions.sort(list,c); list.forEach(Syetem.out::println);
Comparable 和 Comparator 接口比较
Comparable 接口是排序接口,如果一个类实现了 Comparable 接口就意味着该类型的对象是可比较的,而Comparator 接口是比较器,如果需要控制某个类的次序,可以临时建议一个该类的比较器进行排序
可以将 Comparable 当作内部比较器,而 Comparator 相当于外部比较器
通配符参数
通配符参数一般用于方法中接收参数类型的定义
void doSomething(Status<?> ob)表示传入参数是任意的 Status 类型,其中?表示一个不确定的类型,它的具体值会在调用时才能确定下来
public class Test{
public void pp(List<?> list){ 传入的具体实参可以时 List 的任意类型
list.add(123); //语法报错
}
}
代码
public class Test1 {
public void pp(List<?> list) {
//list.add("123");
//这里使用list时不能涉及list中元素的类型,否则报错
for (Object tmp : list)
System.out.print1n(tmp);
System.out.print1n(list.size());
list = new LinkedList<Date>();
//list.add(new Date());报错, 因为编译器识别的类型是不确定的类型
System.out.print1n(list.size());
//语法细节
//List<Object> alist=new ArrayList<String>();//语法错误
List<?> alist=new ArrayList<String>();
//alist.add("bbbb");语法报错
//alist. add(new object());语法报错
}
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("999");
Test1 t1 = new Test1();
t1.pp(1ist);
}
注意:List<?>
中的?表示可以传入任意类型,到底?是什么类型,只有运行时才能确定,所以List<?> list=new ArrayList<String>()和 List<?> list=new ArrayList<Integer>()
都可以,但是试图添加元素则会出现问题
基础语法
<? extends E>
上限通配符,用以类型的上限
public class B1<T>{
public void pp(List<? extends T> list){} 表示 list 中的元素类型必须是 T 类型或者 T 类型的后代类型
}
public class Test2<T extends Comparable<T>> {
public void sort(List<? extends T> list) {
for(inti=0;i<list.size()-1;i++){
System . out . print1n(i +“:"+ (i+1) + ":" + list.get(i).compareTo(list.get(i + 1)));
} //这里可以直接调用compareTo方法,是因为T extends Comparable<T>
}
public static void main(String[] args) {
Test2<A> t2 = new Test2<>();
//List<A> list=new ArrayList<B>(); 语法报错
List<B> list=new ArrayList<>();
list.add(new B());
list. add(new B());
t2. sort(list);
//传入的lis中元素的类型是A的子类型
}
}
class A implements Comparable<A> {
public int compareTo(A o) {
return θ;
}
}
class B extends A{
}
<? super T>下限统配符,用于限制类型的下限 ```java public class B1{ public void pp(List<? super T> list){} 表示 list 中的元素类型必须是 T 类型或者 T 类型的祖先类型 } ``` 类型统配符写法 Box<? extends Number>其中?表示可以是 Number 类型或者 Number 的子类型,例如Integer、Double 之类的类型 类型通配符写法为 Box<? super Integer>其中?表示可以是 Integer 类型或者 Integer 类型的父类型,例如Number 类型 ```java public class Test3 { public static void main(String[] args) { List list1 = new ArrayList<>(); List<? super Apple> list2 = list1; List list3=new Arraylist<>(); list2=list3; } } //水果类 class Fruit { } class Apple extends Fruit { } ``` ### 小结泛型类 **定义泛型类** public class MyClass