--泛型--

将一个对象放入集合中,集合不会记住这个对象的具体类型(向上转型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 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 extends Collection {//这里<>中的内容就是类型参数,一般建议使用全大写的方式进行定义,例 如 T、E、ID 之类的形式
boolean add(E e);//定义 add 方法,要求传入的数据类型为 E
E get(int index);//E 表示获取的数据类型就是定义接口时指定的类型
}

调用

List list = new ArrayList();//就是将String传递给E,用于替代定义中的E

String str = list.get(0);

如果在调用时不进行声明,则系统默认为Object

Lsit 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 {
public int compareTo(T o);
}

这里使用了泛型,表示用于规范类的可比较性

compareTo(T o)当前类型对象比较时需要返回一个 int 整数,当当前对象大于参数 o 时返回一个正数,小于时返回一个负整数,等于返回为 0,进行比较的参数 o 必须是 T 类型

例如要求整数类型是可比较的

public final class Integer extends Number implements Comparable, 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 max(T t1,T t2){//静态方法中直接使用泛型的语法为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 {
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{ //<>中包含的全大写的名称就是泛型:形式参数
private T name; //在类中就可以使用使用泛型名称当作具体类型使用
public T getName(){ //方法可以直接使用泛型
return name;
}

public void setName(T name){ 
	this.name=name; 
} 

}

使用

Generic 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 list=new ArrayList<>(); 第 2 个类型可以不写,JDK1.7 引入的泛型推导

list.add(123.456) 编译期就可以发现这个错误,类型不合法

**记住:**Collection和 Collection是两个没有任何关系的参数化类型接口,不存在什么谁可以赋值给谁的可能

public class Test{
public static void mian(String[] args){
Collection coll = new ArrayList();
}
}

集合类中的泛型
List 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 g=new Generic<>(); //编译报错,因为传入的 String 类型,不是 Number 类型的后代类型

调用方法 2:

Generic 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 & Serializable & Cloneable>{}

接口 Comparable
如果一个类实现了 Comparable 接口,则表示当前类型的对象是可比较大小的,也就是说可以进行排序。实现了Comparable 接口的类支持排序,也就是可以使用工具类 Collections 对集合对象进行排序

Comparable 接口中定义一个比较方法 compareTo(T obj),返回的 int 类型数据用于表示大小

public class Pig implements Comparable {

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 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§;

接口 Comparator
Comparator 是比较器接口。如果类本身不支持排序比较,即实现 Comparable 接口,则可以建一个类型的比较器专门用于排序比较。

例如 Pig 类没有实现 Comparable 接口,则使用 Collections.sort(list)则会报错。报错的原因是 Collection 工具类中的方法定义

public static <T extends Comparable <? super T >> void sort (List 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();
//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()和 List<?> list=new ArrayList()都可以,但是试图添加元素则会出现问题

基础语法

<? extends E>上限通配符,用以类型的上限 public class B1{ ​ public void pp(List<? extends T> list){} 表示 list 中的元素类型必须是 T 类型或者 T 类型的后代类型 } public class Test2
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值