泛型是什么
泛型,指将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式 ,在创建时不传入具体的参数类型,而是在实例化时才传入。
在Java中这种方法最常见在各种集合类的定义中
用处1 在编译时检查传入类型,保证传入的类型统一
// 不使用泛型的时候,各种类型都可以传入
List aList = new ArrayList();
aList.add("123");
aList.add(234);
System.out.println(aList);
// 写上String作为泛型参数传入,此时传入234就会报错
List<String> aList = new ArrayList<>();
aList.add("123");
aList.add(234);
System.out.println(aList);
用处2 在从数组中取出元素时取消强转
// 报错,因为没有添加泛型,从集合中取出的类为Object,不能使用String的split方法
List aList = new ArrayList();
aList.add("abc");
aList.add("dbe");
System.out.println(aList.get(0).split());
// 添加泛型,在提取时会自动转换为泛型参数,不用强转
List<String> aList = new ArrayList<>();
aList.add("abc");
aList.add("dbe");
System.out.println(aList.get(0).toUpperCase());
注意:泛型参数可以不传入,默认为Object类;传入的参数必须是引用类型,不能是简单类型,如果直接传简单类型会自动装箱为引用类,如果有多个参数,用逗号分割<String,Integer>
泛型类
在类名称之后,写尖括号,中间写上标识符(随意,T,K什么都可以的),此时类中可以使用标识符来指代这个传入的类型
如果泛型类不传入参数的话,默认为Object类,泛型只在编译阶段生效
List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();
Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();
if(classStringArrayList.equals(classIntegerArrayList)){
System.out.println("泛型测试,类型相同");
}
通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
public class Test
{
public static void main( String[] args )
{
Generic<String> generic1 = new Generic<>("some");
Generic generic2 = new Generic<>("some");
System.out.println(generic1.getKey()); // 这里是String类
System.out.println(generic2.getKey()); // 这里是Object类
}
}
class Generic<T>{
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
}
使用方法与泛型类相似,需要注意的是在类实现接口时,需要一起声明泛型
package org.fssx;
public class App
{
public static void main( String[] args )
{
GenericImpl impl1 = new GenericImpl();
System.out.println(impl1.doSOme("123"));
GenericImpl<String> impl2 = new GenericImpl<>();
System.out.println(impl2.doSOme("123"));
}
}
interface Generic<T>{
T doSOme(T key);
}
class GenericImpl<T> implements Generic<T>{
@Override
public T doSOme(T key) {
return key;
}
}
泛型方法
注意:并不是泛型类里的使用了泛型参数的方法就是泛型方法,真正的泛型方法是定义在方法修饰符和方法名中间
package org.fssx;
public class App {
public static void main( String[] args ) {
Generic<Integer> generic1 = new Generic<>();
generic1.doSome(123);
}
}
class Generic<T>{
// 泛型方法,定义了一个泛型参数T,虽然与泛型类的参数标识相同,但是却不相同
public <T> void doSome(T value){
System.out.println(value);
}
}
泛型通配符
Integer是Number的子类,但是在泛型类作为参数传入时,Generic并不能作为Generic的子类传入
package org.fssx;
public class App {
public static void main( String[] args ) {
Generic<Integer> generic1 = new Generic<>(123);
showKeyValue(generic1); // 编译报错,因为类型不兼容
Generic<Number> generic2 = new Generic<Number>(456);
showKeyValue(generic2);
}
public static void showKeyValue(Generic<Number> obj){
System.out.println(obj.getKey());
}
}
class Generic<T>{
private T key;
public T getKey() {
return key;
}
public Generic(T key) {
System.out.println(key);
}
}
不过可以用?来代替泛型实参(此处?是类型实参,而非类型形参)
public static void showKeyValue(Generic<?> obj){
System.out.println(obj.getKey());
}
泛型上下界
在需要对泛型参数限制的时候可以使用上下界,传入的参数必须是指定类型的子类型
在上面的例子中用?代理了具体的泛型实参,但是这样的话所有的类型都可以传入,String类也是可以的,但是我们只想要让Number的子类传入,这时候可以使用泛型上下界来对传入参数的范围进行限制,约束为Number的子类
Generic<String> generic3 = new Generic<>("123");
showKeyValue(generic3); // 报错,因为String类不是Number的子类,所以编译失败
public static void showKeyValue(Generic<? extends Number> obj){
System.out.println(obj.getKey());
}