泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参),泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?答案是可以使用 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);//编译报错,父类不能转为子类,需要强制转换 } }