Java中的泛型
泛型技术在绝大多数语言总都存在。对泛型狭义的解释就是,在定义类的时候不指定属性或方法参数的类型,用一个T或者其他单词代替类型,等到实例化对象的时候根据实际需要指定类型。这样可以极大的提高代码的重用性和健壮性。如下面的代码:
1 class Message<T> 2 { 3 private T info; 4 public Message(T info) 5 { 6 this.info = info; 7 } 8 9 public T getInfo() 10 { 11 return info; 12 } 13 14 public void setInfo(T info) 15 { 16 this.info = info; 17 } 18 19 public void show() 20 { 21 //利用反射获取info变量的类型 22 System.out.println("类型:" + this.info.getClass().getSimpleName()); 23 24 System.out.println("值:" + this.info); 25 } 26 }
上面的代码在类声明的时候并没有指定属性info的具体类型,而是用字母T代替。下面是测试代码:
1 public class Main 2 { 3 public static void main(String[] args) 4 { 5 Message<String> msg1 = new Message<String>("hello"); 6 msg1.show(); 7 System.out.println("----------"); 8 Message<Integer> msg2 = new Message<Integer>(10086); 9 msg2.show(); 10 System.out.println("Main Done//~~"); 11 } 12 }
可以看到,我们的测试代码在实际运行的时候才指定了具体类型。下面是输出结果:
类型:String 值:hello ---------- 类型:Integer 值:10086 Main Done//~~
泛型的通配符
先看下面的代码:
1 public class Main 2 { 3 public static void main(String[] args) 4 { 5 Message<String> msg = new Message<String>("hello"); 6 printInfo(msg); 7 8 System.out.println("Main Done//~~"); 9 } 10 11 public static void printInfo(Message<String> msg) 12 { 13 msg.show(); 14 } 15 }
我们可以将msg变量传入到函数中,这完全没有问题。但是,如果此时的msg用下面的代码声明,结果会如何呢?
1 public class Main 2 { 3 public static void main(String[] args) 4 { 5 Message<Integer> msg = new Message<Integer>(10086); 6 printInfo(msg); 7 8 System.out.println("Main Done//~~"); 9 } 10 11 public static void printInfo(Message<String> msg) 12 { 13 msg.show(); 14 } 15 }
这段代码无法通过编译。因为函数是Message<String>作为形参类型,而主函数中传入的是Message<Integer>类型。为了解决这个问题,我们可以修改函数的形参类型,去掉后面的泛型,直接用Message接收参数:
1 public class Main 2 { 3 public static void main(String[] args) 4 { 5 Message<Integer> msg = new Message<Integer>(10086); 6 printInfo(msg); 7 8 System.out.println("Main Done//~~"); 9 } 10 11 public static void printInfo(Message msg) 12 { 13 msg.show(); 14 } 15 }
但是,这并不是解决该问题的最佳方案。因为一旦我们取消掉泛型限制,实际上info类型变成了Object类型。这样会导致一个问题就是,我们在pritnInfo函数中,可以传递一个String类型给info,这就严重违背了我们在主方法中将msg变量设置为Message<String>类型的初衷。请看下面的代码:
1 public class Main 2 { 3 public static void main(String[] args) 4 { 5 Message<Integer> msg = new Message<Integer>(10086); 6 printInfo(msg); 7 8 System.out.println("Main Done//~~"); 9 } 10 11 public static void printInfo(Message msg) 12 { 13 msg.setInfo("hello"); 14 msg.show(); 15 } 16 }
输出结果:
类型:String 值:hello Main Done//~~
为了解决这个问题,我们可以采用通配符<?>来作为函数的形参类型。
public class Main { public static void main(String[] args) { Message<Integer> msg = new Message<Integer>(10086); printInfo(msg); System.out.println("Main Done//~~"); } public static void printInfo(Message<?> msg) { msg.show(); } }
但是,一旦设置了<?>接收参数,在printInfo中就只能读取参数值,不能再设置参数值。下面的代码不能通过编译:
1 public class Main 2 { 3 public static void main(String[] args) 4 { 5 Message<Integer> msg = new Message<Integer>(10086); 6 printInfo(msg); 7 8 System.out.println("Main Done//~~"); 9 } 10 11 public static void printInfo(Message<?> msg) 12 { 13 msg.setInfo(10010); 14 msg.show(); 15 } 16 }
泛型上限和下限
在通配符的基础上还有下面两个子通配符:
<? extends Type> 表示通配的类型必须是类型Type或者Type的子类
<? super Type> 表示通配符必须是类型Type或者Type的父类
如下面的代码,只能是Number类型或者Number类型的子类才能传递给printInfo函数:
1 public class Main 2 { 3 public static void main(String[] args) 4 { 5 Message<Integer> msg = new Message<Integer>(10086); 6 printInfo(msg); 7 8 System.out.println("Main Done//~~"); 9 } 10 11 public static void printInfo(Message<? extends Number> msg) 12 { 13 msg.show(); 14 } 15 }
下面的代码不能通过编译:
1 public class Main 2 { 3 public static void main(String[] args) 4 { 5 Message<String> msg = new Message<String>("hello"); 6 printInfo(msg); 7 8 System.out.println("Main Done//~~"); 9 } 10 11 public static void printInfo(Message<? extends Number> msg) 12 { 13 msg.show(); 14 } 15 }
有如下面的代码,只能向printInfo传递String以及String的父类才行:
public class Main { public static void main(String[] args) { Message<String> msg = new Message<String>("hello"); printInfo(msg); System.out.println("Main Done//~~"); } public static void printInfo(Message<? super String> msg) { msg.show(); } }
下面的代码不能通过编译,因为Integer不是String的父类:
1 public class Main 2 { 3 public static void main(String[] args) 4 { 5 Message<Integer> msg = new Message<Integer>(10086); 6 printInfo(msg); 7 8 System.out.println("Main Done//~~"); 9 } 10 11 public static void printInfo(Message<? super String> msg) 12 { 13 msg.show(); 14 } 15 }