泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参),泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?答案是可以使用 Java泛型。使用 Java泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序
如果说Java的重写和重载是对方法的实现进行多态扩展,那么Java泛型就是对方法的参数类型进行多态化扩展。
泛型优势
一种约束—规范类型(常用名字:E = Elememt、T = Type、K = Key、V = Value),可以说任何类型都可以当做参数化的泛型。包括泛型类、泛型接口、泛型方法,它们的成员变量以及参数都可以使用泛型。为什么要使用泛型?
1,使用泛型可以避免过多的方法方法重载,更广泛的说,多种数据类型执行相同的代码时可以实现代码复用
public class NeedGeneric1 {
private static int add(int a, int b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static float add(float a, float b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static double add(double a, double b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static <T extends Number> double add(T a, T b) {
System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
return a.doubleValue() + b.doubleValue();
}
public static void main(String[] args) {
//不使用泛型,add需要三个重载方法
NeedGeneric1.add(1, 2);
NeedGeneric1.add(1f, 2f);
NeedGeneric1.add(1d, 2d);
//使用泛型,add只需要一个泛型方法
NeedGeneric1.add(Integer.valueOf(1), Integer.valueOf(2));
NeedGeneric1.add(Float.valueOf(1), Float.valueOf(2));
NeedGeneric1.add(Double.valueOf(1), Double.valueOf(2));
}
}
2,泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型),将类型的明确工作提前到对象的创建以及方法调用。防止程序不安全泛型 :将类型的明确工作提前到对象的创建以及方法调用,防止程序不安全
public class NeedGeneric1 {
static class C{
}
public static void main(String[] args) {
List list=new ArrayList();
list.add("A");
list.add("B");
list.add(new C());
list.add(100);
//1.当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,该对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。
//2.因此取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现“java.lang.ClassCastException”异常。
for (int i = 0; i < list.size(); i++) {
String value= (String) list.get(i);
System.out.println(value);
}
}
}
以上代码执行为:
Exception in thread "main" java.lang.ClassCastException:
packageB.NeedGeneric1$C cannot be cast to java.lang.String
at packageB.NeedGeneric1.main(NeedGeneric1.java:20)
A
B
如果我们使用泛型的话,将运行期的错误提前到编译期,提前发现问题
泛型的使用
泛型支持泛型类、泛型接口以及泛型方法的调用,接下来详细说说这三种使用方式。
泛型方法
可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。下面是定义泛型方法的规则:
- 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前
- 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
- 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
- 泛型方法体的声明和其他方法一样。
注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等),以下是一个泛型的定义:
public class GenericMethodTest
{
// 泛型方法 printArray
public static <E> void printArray( E[] inputArray )
{
// 输出数组元素
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
public static void main( String args[] )
{
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
System.out.println( "整型数组元素为:" );
printArray( intArray ); // 传递一个整型数组
System.out.println( "\n双精度型数组元素为:" );
printArray( doubleArray ); // 传递一个双精度型数组
System.out.println( "\n字符型数组元素为:" );
printArray( charArray ); // 传递一个字符型数组
}
}
返回结果为:
整型数组元素为:
1 2 3 4 5
双精度型数组元素为:
1.1 2.2 3.3 4.4
字符型数组元素为:
H E L L O
泛型类
泛型类的定义方式如下,一般用作变化属性的定义。
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("字符串泛型"));
System.out.printf("整型值为 :%d\n\n", integerBox.get());
System.out.printf("字符串为 :%s\n", stringBox.get());
}
}
这样定义好一个泛型类,就可以有多种当前类的类型实现方式,结果为:
整型值为 :10
字符串为 :字符串泛型
泛型接口
定义一个泛型接口,可以实现方法和参数类型的双扩展:
public interface GenericIntercace<T> {
T getData();
}
接口的实现方式有两种,一种是实现类没有明确数据类型
public class ImplGenericInterface1<T> implements GenericIntercace<T> {
@Override
public T getData() {
return 10;
}
public static void main(String[] args) {
ImplGenericInterface1<String> implGenericInterface1 = new ImplGenericInterface1<>(); //具体使用时再实例化,抽象类的概念
System.out.println(implGenericInterface1.getData());
}
}
另外一种为实现类已经明确数据类型
public class ImplGenericInterface2 implements GenericIntercace<String> {
@Override
public String getData() {
return "Generic Interface2";
}
public static void main(String[] args) {
ImplGenericInterface2 implGenericInterface2 = new ImplGenericInterface2();
System.out.println(implGenericInterface2.getData());
}
}
泛型实例
了解了泛型类和泛型方法后可以看这样一个示例,dog继承自animal,可以看到在泛型类和泛型方法中有不同的表现。
public class GenericMethod3 {
static class Animal {
@Override
public String toString() {
return "Animal";
}
}
static class Dog extends Animal {
@Override
public String toString() {
return "Dog";
}
}
static class Fruit {
@Override
public String toString() {
return "Fruit";
}
}
static class GenericClass<T> {
public void show01(T t) {
System.out.println(t.toString());
}
public <T> void show02(T t) {
System.out.println(t.toString());
}
public <K> void show03(K k) {
System.out.println(k.toString());
}
}
public static void main(String[] args) {
Animal animal = new Animal();
Dog dog = new Dog();
Fruit fruit = new Fruit();
GenericClass<Animal> genericClass = new GenericClass<>();
System.out.println("泛型类在初始化时限制了参数类型=========");
genericClass.show01(dog);
//genericClass.show01(fruit); //编译报错,fruit类型不正确
System.out.println("泛型方法的参数类型在使用时指定=========");
genericClass.show02(dog);
genericClass.show02(fruit);
genericClass.show03(animal);
genericClass.<Animal>show03(dog);
genericClass.show03(fruit);
//genericClass.<Dog>show03(animal);//编译报错,父类不能转为子类,需要强制转换
}
}
泛型规范
包括泛型类型的一些限定以及泛型的一些类型限定、使用规范和继承规范
泛型类型限定
有时,类或方法需要对类型变量加以约束,否则传入类型可能不一定满足泛型使用条件:
- 对类的限定:
public class TypeLimitForClass<T extends List & Serializable>{}
- 对方法的限定:
public static<T extends Comparable<T>>T getMin(T a, T b) {}
其中对方法的限定表示,泛型参数类型必须实现了Comparable接口,否则就没有办法进行比较了
public class ArrayAlg {
public static <T extends Comparable> T min(T[] array){
if (array == null || array.length == 0){
return null;
}
T smallest = array[0];
for (int i=0;i<array.length;i++){
if (smallest.compareTo(array[i])>0){
smallest = array[i];
}
}
return smallest;
}
}
上述代码中的限制了用于实例化类型参数T的类型,必须是实现Comparable接口(只含有compareTo方法的标准接口)的类。如果没有对T进行限制,那么无法确保实例化T的类型具有compareTo方法。
对于类的限定中:一个类型变量可以有多个限定,例如:
<T extends Comparable & Serializable , U extends Comparable>//限定类型使用 “&”分隔,而“,”用于分隔类型参数。
在Java中,一个类可以实现多个接口,但只能有一个父类,所以在类型参数的限定中,可以有多个接口,但只能有一个类。
<T extends 接口1 & 接口2 & ... & 接口n & 类型1>
泛型使用规范
泛型有如下的使用规范,使用的时候需要注意:
- 不能实例化泛型类
- 静态变量或方法不能引用泛型类型变量,但是静态泛型方法是可以的,动态变量和方法可以直接引用泛型类型变量
- 基本类型无法作为泛型类型,例如int必须转为integer
- 无法使用instanceof关键字或==判断泛型类的类型
- 泛型类的原生类型(反射获取的类对象)与所传递的泛型无关,无论传递什么类型,原生类是一样的
- 泛型数组可以声明但无法实例化,只能具体的new一个数组
- 泛型类不能继承Exception或者Throwable
- 不能捕获泛型类型限定的异常但可以将泛型限定的异常抛出
以上就是泛型的使用规范。其实 泛型仅仅是java的语法糖,它不会影响java虚拟机生成的汇编代码,在编译阶段,虚拟机就会把泛型的类型擦除,还原成没有泛型的代码,顶多编译速度稍微慢一些,执行速度是完全没有什么区别的.
泛型的继承规则
泛型有如下三个继承规则:
- 对于泛型参数是继承关系的泛型类之间是没有继承关系的
- 泛型类可以继承其它泛型类,例如:
public class ArrayList<E> extends AbstractList<E>
- 泛型类的继承关系在使用中同样会受到泛型类型的影响
通过代码实现如下:
//继承泛型类
private static class SubGenericInherit<T> extends GenericInherit<T> {
}
//父类泛型类
public class GenericInherit<T> {
private T data1;
private T data2;
public T getData1() {
return data1;
}
public void setData1(T data1) {
this.data1 = data1;
}
public T getData2() {
return data2;
}
public void setData2(T data2) {
this.data2 = data2;
}
public static <V> void setData2(GenericInherit<Father> data2) {
}
public static void main(String[] args) {
//Son 继承自 Father,泛型参数是继承关系
Father father = new Father();
Son son = new Son();
//泛型父类传递不同类型参数,一个是父类,一个是子类
GenericInherit<Father> fatherGenericInherit = new GenericInherit<>();
GenericInherit<Son> sonGenericInherit = new GenericInherit<>();
//泛型子类传递不同类型参数,一个是父类,一个是子类
SubGenericInherit<Father> fatherSubGenericInherit = new SubGenericInherit<>();
SubGenericInherit<Son> sonSubGenericInherit = new SubGenericInherit<>();
/**
* 对于传递的泛型类型是继承关系的泛型类之间是没有继承关系的
* GenericInherit<Father> 与GenericInherit<Son> 没有继承关系
* Incompatible types.
*/
father = new Son();
//fatherGenericInherit=new GenericInherit<Son>(); //编译报错
/**
* 泛型类可以继承其它泛型类,例如: public class ArrayList<E> extends AbstractList<E>
*/
fatherGenericInherit=new SubGenericInherit<Father>();
/**
*泛型类的继承关系在使用中同样会受到泛型类型的影响
*/
setData2(fatherGenericInherit);
// setData2(sonGenericInherit); //泛型父类的setData2方法要求传递一个泛型父类对象
setData2(fatherSubGenericInherit);
// setData2(sonSubGenericInherit);
}
通配符泛型
泛型中可以使用通配符来解决集合的不变性,先来解释下集合的不变性和数组的协变性: 假设有一个函数 fun(Animal animal)
,如果我们传入一个Dog d
对象进去,编译器是不会报错的,这是多态的概念; 假设有一个函数 fun(Animal[] animals)
,如果我们传如一个Dog[] dogs
数组进去,编译器也不会报错,这就是数组的协变性;假设有一个函数 fun(List<Animal> animal)
,如果我们传如一个List <Dog> dog
集合进去,编译器就会报错了,这就是集合泛型的不变性;那么该怎么办呢?我们可以将泛型改成这样
fun (List <? extends Animal> animal)
,这样之后,当我们再传入一个List dog 集合进去,编译器就就不会报错了。也就是说可以传入包含Animal的子类的List了。
这里就用到了通配符的概念,通配符有三种指定方式:
<? extends Parent>
指定了泛型类型的上届<? super Child>
指定了泛型类型的下届<?>
指定了没有限制的泛型类型
其实通配符解决的是同一泛型类中的泛型参数的继承关系,接下来以此下图的继承关系为例来介绍下:
代码逻辑如下:
public class GenericByWildcard {
private static void print(GenericClass<Fruit> fruitGenericClass) {
System.out.println(fruitGenericClass.getData().getColor());
}
/**
* 问题的提出,因为泛型参数的继承关系在使用中并不表示泛型类的继承关系,而且还得遵守泛型规则,所以Orange传进去不认。
*/
private static void use() {
GenericClass<Fruit> fruitGenericClass = new GenericClass<>();
print(fruitGenericClass);
GenericClass<Orange> orangeGenericClass = new GenericClass<>();
//类型不匹配,可以使用<? extends Parent> 来解决
//print(orangeGenericClass);
}
/**
* <? extends Parent> 指定了泛型类型的上届,子类可以正常使用
*/
private static void printExtends(GenericClass<? extends Fruit> genericClass) {
System.out.println(genericClass.getData().getColor());
}
public static void useExtend() {
GenericClass<Fruit> fruitGenericClass = new GenericClass<>();
printExtends(fruitGenericClass);
GenericClass<Orange> orangeGenericClass = new GenericClass<>();
printExtends(orangeGenericClass);
GenericClass<Food> foodGenericClass = new GenericClass<>();
//Food是Fruit的父类,超过了泛型上届范围,类型不匹配
//printExtends(foodGenericClass);
//表示GenericClass的类型参数的上届是Fruit
GenericClass<? extends Fruit> extendFruitGenericClass = new GenericClass<>();
Apple apple = new Apple();
Fruit fruit = new Fruit();
/*
* 道理很简单,? extends X 表示类型的上界,类型参数是X的子类,那么可以肯定的说,
* get方法返回的一定是个X(不管是X或者X的子类)编译器是可以确定知道的。
* 但是set方法只知道传入的是个X,至于具体是X的那个子类,不知道。
* 总结:主要用于安全地访问数据,可以访问X及其子类型,并且不能写入非null的数据。
*/
// extendFruitGenericClass.setData(apple);
// extendFruitGenericClass.setData(fruit);
fruit = extendFruitGenericClass.getData();
}
/**
* <? super Child> 指定了泛型类型的下届
*/
public static void printSuper(GenericClass<? super Apple> genericClass) {
System.out.println(genericClass.getData());
}
public static void useSuper() {
GenericClass<Food> foodGenericClass = new GenericClass<>();
printSuper(foodGenericClass);
GenericClass<Fruit> fruitGenericClass = new GenericClass<>();
printSuper(fruitGenericClass);
GenericClass<Apple> appleGenericClass = new GenericClass<>();
printSuper(appleGenericClass);
GenericClass<HongFuShiApple> hongFuShiAppleGenericClass = new GenericClass<>();
// HongFuShiApple 是Apple的子类,达不到泛型下届,类型不匹配
//printSuper(hongFuShiAppleGenericClass);
GenericClass<Orange> orangeGenericClass = new GenericClass<>();
// Orange和Apple是兄弟关系,没有继承关系,类型不匹配
// printSuper(orangeGenericClass);
//表示GenericClass的类型参数的下界是Apple
GenericClass<? super Apple> supperAppleGenericClass = new GenericClass<>();
supperAppleGenericClass.setData(new Apple());
supperAppleGenericClass.setData(new HongFuShiApple());
/*
* ? super X 表示类型的下界,类型参数是X的超类(包括X本身),
* 那么可以肯定的说,get方法返回的一定是个X的超类,那么到底是哪个超类?不知道,
* 但是可以肯定的说,Object一定是它的超类,所以get方法返回Object。
* 编译器是可以确定知道的。对于set方法来说,编译器不知道它需要的确切类型,但是X和X的子类可以安全的转型为X。
* 总结:主要用于安全地写入数据,可以写入X及其子类型。
*/
//supperAppleGenericClass.setData(new Fruit());
//get方法只会返回一个Object类型的值。
Object data = supperAppleGenericClass.getData();
}
/**
* <?> 指定了没有限定的通配符
*/
public static void printNonLimit(GenericClass<?> genericClass) {
System.out.println(genericClass.getData());
}
public static void useNonLimit() {
GenericClass<Food> foodGenericClass = new GenericClass<>();
printNonLimit(foodGenericClass);
GenericClass<Fruit> fruitGenericClass = new GenericClass<>();
printNonLimit(fruitGenericClass);
GenericClass<Apple> appleGenericClass = new GenericClass<>();
printNonLimit(appleGenericClass);
GenericClass<?> genericClass = new GenericClass<>();
//setData 方法不能被调用, 甚至不能用 Object 调用;
//genericClass.setData(foodGenericClass);
// genericClass.setData(new Object());
//返回值只能赋给 Object
Object object = genericClass.getData();
}
}
下列哪些继承关系是正确的?
class A {}
class B extends A {}
class C extends A {}
class D extends B {}
Which four statements are true ?
A,The type List<A>is assignable to List. //true
B,The type List<B>is assignable to List<A>.
C,The type List<Object>is assignable to List<?>. //true
D,The type List<D>is assignable to List<?extends B>.//true
E,The type List<?extends A>is assignable to List<A>.
F,The type List<Object>is assignable to any List reference.
G,The type List<?extends B>is assignable to List<?extends A>.//true
判断的时候有如下准则:
- 只看尖括号里边的,明确点和范围两个概念 ,List既是点也是范围,当表示范围时,表示最大范围,List<?>和List 是相等的,都代表最大范围和最大点
- 如果尖括号里的是一个类,那么尖括号里的就是一个点,比如List,List,List
- 如果尖括号里面带有问号,那么代表一个范围,<? extends A>代表小于等于A的范围,<? super A>代表大于等于A的范围,<?>代表全部范围
- 尖括号里的所有点之间互相继承都是错,除非是俩相同的点,因为泛型参数继承关系不代表泛型类有基础关系
- 尖括号小范围赋值给大范围,对,大范围赋值给小范围,错。如果某点包含在某个范围里,那么可以赋值,否则,不能赋值
以上就是在有通配符的情况下的泛型继承关系。
泛型原理
虚拟机是如何实现泛型的,其实泛型在Java里更像是一种语法糖。Java泛型是Java1.5之后才引入的,为了向下兼容。Java采用了C++完全不同的实现思想。Java中的泛型更多的看起来像是编译期用的,Java中泛型在运行期是不可见的,会被擦除为它的上级类型。如果是没有限定的泛型参数类型,就会被替换为Object.
GenericClass<String> stringGenericClass=new GenericClass<>();
GenericClass<Integer> integerGenericClass=new GenericClass<>();
CJava进行了类型擦除之后统一改为GenericClass<Object>