泛型及其具体操作

泛型

泛型是一种类型机制,其本质是将数据类型参数化,作为一种类型安全机制而产生的。
将类型检查从运行时提前到了编译期,使用泛型编写的代码比杂乱的使用 Object,并在需要时再执行窄化处理的机制具备更好的可读性和安全性。

泛型的定义

public interface List<E> extends Collection<E> { //这里<>中的内容就是类型参数,一般建议使用全大写的方式进行定义,例如 T、E、ID 之类的形式。上述为接口泛型
boolean add(E e); //定义 add 方法,要求传入的数据类型为 E
泛型的调用
List<String> list=new ArrayList<String>(); //就是将 String 传递给 E,用于替代定义中的 E
String str=list.get(0);//如果在调用时不进行声明,则系统默认为 Object

List<Date> list=new ArrayList<>(); //使用菱形语法
list.add("123");// 编译时语法报错,因为编译期会进行类型检查,"123"不是 Date 类型
list.add(123); //语法报错
list.add(new Date());// 编译通过,类型合法
Comparable接口
//java 预定义的comparable接口
public interface Comparable<T>{
	public int compareTo(T o);
}

该接口使用了泛型,表示用于规范类的可比较性
compareTo(T o)当前类型对象比较时需要返回一个 int 整数,当当前对象大于参数 o 时返回一个正数,小于时返回一个负整数,等于返回为 0,进行比较的参数 o 必须是 T 类型。
传入参数类型必须实现Comparable接口。
例如:

//使用泛型的方式定义 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; //之所以可以直接调用 compareTo 方法是因为在类型声明时已经要求必须实现Comparable 接口
	}
}
//如果进行整型数据比较,需要先确定 Integer 必须实现了 Comparable 接口
Integer kk=MyTest.max(12,15);
//Integer内部实现了comparable接口
//如果是 double 类型数据,要先确定 Double 必须实现了 Comparable 接口
Double dd=MyTest.max(12.12,13.);

//如果传入参数类类型没有实现comparable接口,则需要该类继承comparable接口,并且实现comparable接口中未实现的compareTo方法

在泛型出现之前,只有一种变通的方法就是将参数类型定义为 Object,这种方法不能保证类型安全。泛型弥补了Object 这种做法所缺乏的类型安全,也简化了过程。

使用泛型的好处

解决类型安全性的隐患,泛型类或者接口在取出对象时不需要再进行向下类型转换,因为可以认为存储的时候就是这种类型。泛型的使用让安全问题再编译时就报错,而不是运行时报错,这样方便及时准确的发现问题。

  • 可读性,从字面上就可以判断集合中的内容类型
  • 类型检查,避免插入非法类型的数据
  • 获取数据时不再需要强制类型转换

泛型类

泛型类也叫做参数化类型,就是具有一个或者多个类型参数的类,一个泛型类可以有多个泛型声明,所有的泛型声明都应该在<>内部。
在当前类中 T 就是一个类型的说明,可以用在说明任何实例方法中的局部变量、方法的形参以及方法的返回值,类的成员变量;
但是类型 T 不能直接使用在静态方法中
例:

public class Test<T>{ //<>中包含的全大写的名称就是泛型:形式参数
	private T name; //在类中就可以使用使用泛型名称当作具体类型使用
	public T getName(){ //方法可以直接使用泛型
		return name;
	}
	public void setName(T name){
		this.name=name;
	}
}
Test<String> tt=new Test<>(); //使用时在具体确定 T 对应的类型为 String,第二处<>中没有内容,叫做泛型推导
tt.setName("张三"); //tt.setName(123)语法报错,因为 123 不是 String 类型
System.out.println(tt.getName());
//传递给泛型参数的类型必须是类类型,不能使用 int 或者 char 之类的简单类型

//如果定义了泛型类,但是引用时不声明泛型对应的类型值,则系统识别为 Object 类型
Test tt1=new Generic();
tt1.setName("王五"); //有警告信息,但是语法正确,因为 String 也是 Object 类型
tt1.setName(123); //有警告信息,但是编译可以通过。因为 123 经过自动装箱操作后,可以识别为 Object 类型
多个类型参数的泛型类

如果引用多个类型,可以使用逗号作为分隔符,例如<S,D>类型参数名称可以使用任意字符串,但是一般建议使用有代表含义的单个字符,以便于和普通类型名称进行区分。
例如: T 代表 type,源数据 S,目标数据 D,子元素类型 E

集合中的泛型

没有使用泛型时,只要是对象,不管是什么类型的对象,都可以存储到同一个集合中
使用泛型集合时,可以将一个集合中的所有元素限定为一个特定类型,这样集合中就只能存储特定的类型的对象,这样比较安全;并且获取集合中存储的数据时,编译器也直到这个元素的类型,不需要进行窄化处理,这样使用
也比较方便。

List<Integer> list=new ArrayList<>(); //第 2 个类型可以不写,JDK1.7 引入的泛型推导,如果要写耶必须与前类型Integer相同
list.add(123.456);//编译期就可以发现这个错误,类型不合法

//Collection<String>和 Collection<Object>是两个没有任何关系的参数化类型接口,不存在什么谁可以赋值给谁的可能
Collection<Object> co = new ArrayList<String>();//报错


List<String> list=new ArrayList<>();
Map<String,Object> map=......;
List<Map<String,Object>> list=....

泛型只能使用引用类型,而不能使用基本类型,例如 List是错误d

有界类型

在实际应用中需要对传递的类型参数的具体类型进行限制。
有界类型即:指定一个类型参数时,可以指定一个上界,声明所有的实际类型都必须时这个超类的
直接或者间接子类。

public class MyClass<T extends Number> {}//定义类泛型
//要求传入的具体类型必须是 Number 的子类,也就是要求传入的具体类型必须是数值型。这里可以传入 Integer、Double 等类型,但是不能传入 String

例如:求对数值类型的不确定个数的数据进行累加

如果使用泛型定义,但是不使用上界约束

public class Test1<T>{
		public double sum<T...params){
			double res = 0;
			for(int i=0;i<params.length;i++)
				res=res+params[i];//报错,不请楚传入数据的类型是否可以相加
			return res;	
	}
}

使用上界约束

public class Test1<T extends Number>{
		public double sum<T...params){
			double res = 0;
			for(int i=0;i<params.length;i++)
				res=res+params[i].doubleValue();
			return res;	
	}
}

的关键字 extends 不是表示继承,只是用于说明传入的类型必须是 Number 类型或者 Number 类型
的后代类型。
测试:

//可以给 T 传递类型 Long,是因为 Long 是 Number 的子类型,如果更换其它类型,则必须判断传入的类型是否为 Number 的后代类型
	Test1<Long> g=new Test1<>(); //注意传入 T 对应的类型为 Long
	double sum=g.sum(1L, 2L, 3L, 4L);
	
	Test1<String> g1=new Test1<>(); //编译报错,因为传入的 String 类型,不是Number类型的后代类型

	Test1<Long> g2=new Test1<>(); //注意传入 T 对应的类型为 Long
	double sum=g2.sum(1, 2L, 3, 4L);//因为 Integer 和 Long 类型都是 Number 的子类型

接口和类都可以作为泛型的上界,当使用接口作为上界时,关键字还是 extends,而不implements。
同时允许一个参数类型有多个限界,限界类型可以使用&符号分割。如果使用只使用类型作为上界,则只能定义一个类型上界
由于 java 的单根继承体系的要求,所以不能使用同时使用多个类作为类型上界,即使是父子类型也不能定义。允许使用一个类和多个接口或者多个接口

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 工具类中的方法定义,在方法上已经声明了要求传入的 T 类型必须实现了 Comparable接口
在这里插入图片描述
引入额外的比较器作为内部比较器
在这里插入图片描述
第一个参数为 List,第二个参数是 Comparator,? super T 表示传入的类型必须是 T 的超类

Comparable 和 Comparator 接口比较

Comparable 接口是排序接口,如果一个类实现了 Comparable 接口就意味着该类型的对象是可比较的。
Comparator 接口是比较器,如果需要控制某个类的次序,可以临时建一个该类的比较器进行排序可以将 Comparable 当作内部比较器,而Comparator 相当于外部比较器

通配符参数

通配符参数一般用于方法中接收参数类型的定义void doSomething(Status<?> ob)表示传入参数是任意的 Status 类型,其中“?”表示一个不确定的类型,它的具体值会在调用时才能确定,ob表示传入参数的名称。
例如:
List<?>中的?表示可以传入任意类型,到底?是什么类型,只有运行时才能确定,所以
List<?> list=new ArrayList()和 List<?> list=new ArrayList()都可以,但是试图添加元素则会出现问题。

基础语法

<? extends E>上限通配符,用以类型的上限

public class B1<T>{
	public void pp(List<? extends T> list){} //表示 list 中的元素类型必须是T 型或者T类型的后代类型
}

<? super T>下限统配符,用于限制类型的下限

public class B1<T>{
	public void pp(List<? super T> list){} //表示 list 中的元素类型必须是T类型或者T类型的祖先类型
}

类型统配符写法 Box<? extends Number>其中?表示可以是 Number 类型或者 Number 的子类型,例如:Integer、Double 之类的类型。
类型通配符写法为 Box<? super Integer>其中?表示可以是 Integer 类型或者 Integer 类型的父类型,例如:Number 类型。

泛型方法

泛型方法就是带有类型参数的方法。

  • 泛型定义在泛型类上,在方法中直接使用类上声明的泛型
  • 方法定义在普通类中,需要自定义参数类型
  • 静态方法不能访问类的泛型,如果需要泛型则只能在方法上声明使用泛型
    语法规则:<声明使用的泛型> 返回值类型 方法名称(形参)
    泛型方法:
public <T> Integer pp(T id){
	return 999;
}

public static <T> void show(T t){
	System.out.println("static");
}

一个方法如果声明为泛型方法,那么它拥有一个或者多个类型参数。
不同于泛型类,类型参数只能在所修饰的方法之中使用。

泛型类的写法

泛型定义在类上,方法上引用泛型

public class MyClass<T> {
private T id;
public T pp(){}
public void bb(T t){}
}

//使用泛型
MyClass<String> mc=new MyClass<>();
mc.bb(123);//语法错误
mc.bb("123"); //语法正确

模板模式的改写:
可以写一个泛型方法,在方法调用时接受不同类型的参数,根据传递给泛型方法的参数类型,编译器适当的处理每个方法的调用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值