泛型
泛型(Generic)是Java编程语言的强大功能。
泛型的本质是参数化类型,在不创建新的类型的情况下,通过泛型指定某一个类、方法或接口成不同类型。将类型由原来的具体类参数化,此时的参数可以称为类型形参
public class Test1 {
/**
* @param args
* ArrayList<Integer>,ArrayList被指定了一个Integer的类型形参,所以此时的ArrayList只能添加Integer的对象
* @throws Exception
*/
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<>();
list.add(111);
list.add(222);
System.out.println(list);
}
}
泛型的优点
1.在编译时进行更强的类型检查。Java编译器将强类型检查应用于通用代码,如果代码违反类型安
全,则会发出错误。修复编译时错误比修复运行时错误容易,后者可能很难找到。
2.消除类型转换
3.可以帮助我们写出更加通用的代码
泛型擦除
java中的泛型 只是在编译期做语法检查 对你进行限制
泛型只在编译期有效(class).运行期没有泛型,泛型就被擦除了,所有类型都变成了相同的基本类型
public class Test1 {
/**
* @param args
* ArrayList<Integer>的一个对象,在这个集合中添加一个字符串数据,如何实现呢?
* 泛型只在编译期有效,在运行期会被擦除掉
* @throws Exception
*/
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<>();
list.add(111);
list.add(222);
Class clazz = Class.forName("java.util.ArrayList"); //获取字节码对象
Method m = clazz.getMethod("add", Object.class); //获取add方法,add方法中可以放任意对象所以参数是Object.class
m.invoke(list, "abc");
System.out.println(list);
}
}
泛型类
泛型类型修饰类为泛型类,例如List
1.泛型类可以由多个泛型参数例如Class<T,R>
2.泛型参数如果想使用基本数据类型的话必须传递其包装类,举例:int->Integer
3.如果不给泛型类指定具体类型,此时泛型类型可以为任意类型
public class ExampleUnitTest {
@Test
public void exampleTest() {
TestClass<Integer> object1 = new TestClass<>(1);//泛型是Integer类型,int的包装类
TestClass<String> object2 = new TestClass<>("2");//泛型是String类型
TestClass object3 = new TestClass();//如果不给泛型类指定具体类型,类中使用泛型的成员可以为任意类型
object3.setMember(123);
System.out.println(object3.member);
object3.setMember("123");//
System.out.println("exampleTest: " + object1.getMember() + " , " + object2.getMember() + " , " + object3.getMember());
}
public class TestClass<T> {
private T member;
public TestClass() {
}
public TestClass(T member) {
this.member = member;
}
public T getMember() {
return member;
}
public void setMember(T member) {
this.member = member;
}
}
}
输出
123
exampleTest: 1 , 2 , 123
泛型接口
泛型修饰接口
1.泛型接口的实现类未传入具体的泛型实参,此时应该将泛型同样声明到类中,否者使用的时候无法确定具体类型
public interface TestInterface1<R> {
void callback(R r);
}
public class ExampleClass4<R> implements TestInterface1<R> {
//泛型接口的实现类未传入具体的泛型,举例<T>,此时应该将泛型同样声明到类中
@Override
public void callback(R r) {
System.out.println(r);
}
}
public class ExampleUnitTest {
@Test
public void exampleTest2(){
ExampleClass4<String> exampleClass4 = new ExampleClass4<>();
exampleClass4.callback("String类型");
}
}
打印
String类型
2.泛型接口的实现类传入具体的泛型实参,举例 < String>,使用对应泛型的地方都应该为对应类型
public class ExampleClass5 implements TestInterface1<String> {
@Override
public void callback(String s) {
System.out.println(s);
}
}
public class ExampleUnitTest {
@Test
public void exampleTest2(){
ExampleClass5 exampleClass5 = new ExampleClass5();
exampleClass5.callback("exampleClass5");
}
}
打印
exampleClass5
如果泛型接口有两个泛型参数的时候,当泛型接口的实现类传入了具体的泛型实参,情况和一个泛型参数的一样,使用对应泛型的参数或成员都应该为对应类型
这里注意的是当泛型接口的实现类没有传入具体的泛型实参的情况
泛型接口的实现类未传入具体的泛型,举例<T,R>,此时应该将泛型同样声明到类中
alt+enter默认会在ExampleClass2声明第一个泛型T,如果想要使用泛型R,也需要在ExampleClass2类上声名,否者会报错,见以下例子
public interface TestInterface2<T, R> {
void callback(T t);
R onNext();
}
public class ExampleClass2<T> implements TestInterface2<T, R> {
//泛型接口的实现类未传入具体的泛型,举例<T,R>,此时应该将泛型同样声明到类中
//alt+enter默认会在ExampleClass2声明第一个泛型T,如果想要使用泛型R,也需要在ExampleClass2类上声名
private R member;
@Override
public void callback(T t) {
System.out.println(t);
}
@Override
public R onNext() {
return getMember();
}
private R getMember() {
return member;
}
public void setMember(R member) {
this.member = member;
}
}
具体使用的时候会报错cannot be applied to …
所以如果外部需要使用到第二个泛型参数,同样也需要声明到类上
public class ExampleClass3<T,R> implements TestInterface2<T, R> {
//泛型接口的实现类未传入具体的泛型,举例<T,R>,此时应该将泛型同样声明到类中
private R member;
@Override
public void callback(T t) {
System.out.println(t);
}
@Override
public R onNext() {
return getMember();
}
private R getMember() {
return member;
}
public void setMember(R member) {
this.member = member;
}
}
public class ExampleUnitTest {
@Test
public void exampleTest2(){
ExampleClass3<String, Boolean> exampleClass3 = new ExampleClass3<>();
exampleClass3.callback("传递String");
exampleClass3.setMember(true);
Boolean aBoolean = exampleClass3.onNext();
}
}
泛型方法
1.在访问权限和返回值类型之间声明方法的泛型,注意,这个和泛型类 TestMethod的T不是同一个,可以是相同类型也可以是不同类型,当然中的T可以换成任意字母
2.声明了泛型的方法才是泛型方法,仅在方法中,没有声明的不是泛型方法,比如当前类的构造函数public TestMethod(T t){},就不是泛型方法
3.泛型方法中声明的泛型,可以在方法中使用该泛型,反之不能使用该泛型
例子:
public class TestMethod<T> {
private T mT;
public TestMethod(T t) {
this.mT = t;
}
public T getmT() {
return mT;
}
/**
* 泛型方法
* 1.在访问权限和返回值类型之间声明方法的泛型<T>,注意,这个<T>和泛型类 TestMethod<T>的T不是同一个,可以是相同类型也可以是不同类型,取决于泛型实参传的是什么类型,可以见例子ExampleUnitTest,当然<T>中的T可以换成任意字母
* 2.声明了泛型<T>的方法才是泛型方法,仅在方法中,没有声明的不是泛型方法,比如当前类的构造函数public TestMethod(T t){},就不是泛型方法
* 3.泛型方法中声明的泛型<T>,可以在方法中使用该泛型,反之不能使用该泛型
* @param t
* @param <T>
*/
public <T> void myMethod1(TestMethod<T> t){
T t1 = t.getmT();
System.out.println(t1+","+t1.getClass().toString());
}
/**
* 不是泛型方法,只是在参数中使用了类中声明的泛型T,此处如果调用该方法,只能传和类一致的泛型
* @param t
*/
public void myMethod2(TestMethod<T> t){
T t1 = t.getmT();
System.out.println(t1+","+t1.getClass().toString());
}
}
public class ExampleUnitTest {
@Test
public void test() {
TestMethod<String> testMethod1 = new TestMethod<>("泛型方法");
TestMethod<Integer> testMethod2 = new TestMethod<>(123);
/**
* 下面方法调用正常
* 因为在方法中声明了新泛型,可以在方法中使用
*/
testMethod1.myMethod1(testMethod1);
/**
* 下面报错:TestMethod<String> in TestMethod cannot be applied to TestMethod<Integer>
* 因为myMethod2使用的类中声明的泛型,当前是String类型的,而传入Integer,所以报错
*/
testMethod1.myMethod2(testMethod2);
}
}
注释掉testMethod1.myMethod2(testMethod2);,运行
泛型方法,class java.lang.String
注意:如果在静态方法中使用泛型,这个静态方法必须设置成泛型方法
泛型通配符
<?>可以传入任意类型,例如在上一节泛型方法中举的例子,在myMethod2中使用的是类中的泛型,对象testMethod1对应类中的泛型是String,而想传入泛型是Integer的对象,在不是泛型方法的情况下是行不通的,这里如果我们在方法参数上使用泛型通配符,就可以解决这个问题
例子:
/**
* 泛型通配符,可以传入任意类型
* @param t
*/
public void myMethod3(TestMethod<?> t){
Object o = t.getmT();
System.out.println(o+" , "+o.getClass().toString());
}
@Test
public void test() {
TestMethod<String> testMethod1 = new TestMethod<>("泛型方法");
TestMethod<Integer> testMethod2 = new TestMethod<>(123);
/**
* 下面方法调用正常
* 因为在方法中声明了新泛型,可以在方法中使用
*/
// testMethod1.myMethod1(testMethod1);
/**
* 下面报错:TestMethod<String> in TestMethod cannot be applied to TestMethod<Integer>
* 因为myMethod2使用的类中声明的泛型,当前是String类型的,而传入Integer,所以报错
*/
// testMethod1.myMethod2(testMethod2);
testMethod1.myMethod3(testMethod2);
}
输出
123 , class java.lang.Integer
泛型上下边界
泛型通配符最常用的两种方式就是这个场景
在使用泛型的时候,可以限制泛型实参的上下边界,也就是:
指定泛型的父类是某种类型就是限制泛型的上边界,只能使用上边界类型和上边界内的类型,例如< ? extends TestClass1>,上边界是TestClass1,可以使用泛型TestClass1和TestClass1的子类泛型;
指定泛型的子类是某种类型就是限制泛型的下边界,只能使用下边界类型的泛型和下边界以内的泛型,例如< ? super TestClass2 > 下边界是TestClass2,只能使用泛型为TestClass2和TestClass2的父类泛型
举例
水果、苹果、绿苹果的例子,水果是超类,苹果继承水果,绿苹果继承苹果;
定义一个泛型容器Generic,
eat2(Generic<? extends Apple> t) 方法是确定了上边界为Apple,所以只能传的泛型为Apple或者GreenApple
eat3(Generic<? super Apple> t) 方法是确定了下边界为Apple,所以只能传的泛型为Apple或者Friut
public class Fruit {
private String name;
public Fruit() {
}
public Fruit(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Apple extends Fruit {
public Apple() {
}
public Apple(String name) {
super(name);
}
@Override
public String toString() {
return "Apple{}";
}
}
public class GreenApple extends Apple {
public GreenApple() {
}
public GreenApple(String name) {
super(name);
}
@Override
public String toString() {
return "GreenApple{}";
}
}
public class Generic<T> {
private T t;
public Generic() {
}
public Generic(T t) {
this.t = t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public String eat2(Generic<? extends Apple> t){
return t.getT().toString();
}
public String eat3(Generic<? super Apple> t){
return t.getT().toString();
}
}
@Test
public void exampleTest3() {
Generic<Fruit> fruitGeneric = new Generic<>();
Generic<Apple> appleGeneric = new Generic<>();
Generic<GreenApple> greenAppleGeneric = new Generic<>();
Fruit fruit = new Fruit("水果");
Apple apple = new Apple("苹果");
GreenApple greenApple = new GreenApple("绿苹果");
fruitGeneric.setT(fruit);
appleGeneric.setT(apple);
greenAppleGeneric.setT(greenApple);
//Apple是上边界 可以传Generic中泛型继承于Apple的子类以及Apple
System.out.println(appleGeneric.eat2(greenAppleGeneric));
System.out.println(appleGeneric.eat2(appleGeneric));
//Apple是下边界 可以传Generic中Apple泛型的父类以及Apple
System.out.println(appleGeneric.eat3(fruitGeneric));
System.out.println(appleGeneric.eat3(appleGeneric));
}
打印
GreenApple{}
Apple{}
com.himi.test.Fruit@3b95a09c
Apple{}
这里需要注意的是,
1.? extends X 设置泛型上界时,主要用于安全的访问数据,可以访问上边界以及上边界的子类型,并且不能写入非null的数据。
set方法不能调用。set方法只知道传入的是X,不知道传入的是X的哪个子类。
例如eat2方法参数中可以传入泛型Generic< GreenApple>,虽然Apple与GreenApple是继承关系,
但是Generic< Apple>与Generic< GreenApple>是两个不同的类;
get方法可以调用,返回上边界,X类型。get方法返回是X类型(X和X的子类都行,子类可以向上转型为X),就像例子中的,返回一个Apple类型,因为上边界是Apple,get返回的肯定是Apple的子类,可以向上转型为Apple。
2.? super X 设置泛型下界时,用户安全的写入数据,可以写入下界和下界的子类型。
set方法可以调用,只能传入X或X的子类,对于set方法来说,编译器不知道它需要的确切类型,但是X和X的子类都可以向上转型为X(X不用向上转型,这里不知道怎么描述,知道就可以了),就像例子中,eat3方法参数中限定的泛型的下界是Apple,eat3方法的泛型中可以传入的是Apple的超类和Apple,所以setT()中可以传Apple以及Apple的子类;
get方法可以调用,但是返回的是Object,因为传入get方法的肯定是X或者是X的超类,Object是所有类的超类,所以get方法返回是Object。
截图:
代码:
public class Generic<T> {
private T t;
public Generic() {
}
public Generic(T t) {
this.t = t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public String eat2(Generic<? extends Apple> t){
//这里get方法没问题,返回一个Apple类型,因为上边界是Apple,get返回的肯定是Apple的子类,可以向上转型为Apple
Apple t1 = t.getT();
// set方法不知道传入的参数是谁。eat2方法参数中可以传入泛型Generic<GreenApple>,虽然Apple与GreenApple是继承关系,
// 但是Generic<Apple>与Generic<GreenApple>是两个不同的类
t.setT(new Apple()); //报错
return t1.toString();
}
public String eat3(Generic<? super Apple> t){
// 因为传入get方法的肯定是Apple或者是Apple的超类,Object是所有类的超类,所以get方法返回是Object。
Object t1 = t.getT();
//set方法可以使用,因为eat3方法参数中限定的泛型的下界是Apple,eat3方法的泛型中可以传入的是Apple的超类和Apple,所以setT()中可以传Apple以及Apple的子类,
// 因为Apple的子类可以向上转型为Apple
t.setT(new Apple());
t.setT(new GreenApple());
return t1.toString();
}
}
结束语
先写一下泛型基本使用,下一篇 泛型(二)介绍:限定类型变量、泛型类型的继承关系、泛型中的约束与局限性和Java虚拟机是如何实现泛型的