公众号:菜鸡干Java
上次讲了集合,就是放对象的容器,但是集合并不知道对象的具体数据类型,所以很容易发生异常。比如:
List a = new ArrayList();
a.add("as");
a.add("end");
a.add(2);
a.forEach(a->System.out.println(((String)a).length()) );//强制转换错误
Java 泛型generics
是JDK 5中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?使用 Java 泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。这样不仅大大减少了代码量,还利于避免不必要的异常!
泛型菱形
语法
从Java7开始,在用构造器创建对象时后面不用再带上泛型了,简化了代码,例如:
List<String> a = new ArrayList<>();
定义接口、类和泛型方法
Java5为接口和类增加了泛型支持,从而可以在创建集合对象时传入类型实参。
public interface List<E>{
void add(E x);
Iterator<E> iter();
}
public interface StrList extends List{
void add(String x);
Iterator<String> iter();
}
public interface StrList extends List<String>{
void add(String x);
Iterator<String> iter();
}
public interface Map<K,V>{
...
}
public class human<T>{//定义一个泛型类
public T man;
public human(){}
public human(T){}
public T getInfo(){
return this.man;
}
}
public class A extends human<String>{
public String getInfo(){
return ":"+super.getInfo();
}
}
上述代码中,定义一个泛型接口,可以衍生出多个类型子接口,同样一个带泛型的父类可以衍生出许多子类,当使用这些接口和父类时,不能再包含类型形参!但是可以不传入类型实参,例如:
public class A extends human{
public String getInfo(){
return super.getInfo().toString();
}//返回字符串类型
}
如果需要重写继承父类的子类方法,则需要注意类型。对于泛型,只是允许程序员在编译时检测到非法的类型而已。但是在运行期时,其中的泛型标志会变化为Object
类型。
泛型方法
方法想定义自己的泛型形参也是允许的,这样还提供了对泛型方法的支持。<>括号内为类型形参
声明!
格式为:修饰符 <T,V> 返回值类型 方法名(形参列表){...}
public class Test{
static <T> void test(Collection<? extends T> ele,Collection<T> a){
for(T ele: a){
a.add(ele);
}
}
public static void main(String[] args){
List<Object> ao = new ArrayList<>();
List<String> as = new ArrayList<>();
test(as,ao);
}
}
泛型构造器
class Output{
public <T> Output(T t){
System.out.println(t);
}
}
public class Test{
public static void main(String[] args){
new Output("发");
new Output(666);
new <String> Output(3.14);//实参是Double类型出错
}
}
泛型构造器可以认为是泛型方法的特殊一种,不过大致上它们都差不多!
类型通配符?
使用泛型类时,都应该为这个泛型类传入实参,否则会提出警告。类型通配符一般是使用?
代替具体的类型参数,它可以匹配任何类型,List<?>在逻辑上是List
public void test(List<?> c){
System.out.println(c.get(0));
}
这种类型通配符的List
仅表示它是各种泛型List的父类,并不能直接添加元素。例如:
List<?> c = new ArrayList<String>();
c.add(new Object());
由于无法得知c中的元素类型,所以不能向其中添加对象,除了添加null
。
类型通配符上限
public void getNumber(List<? extends Number> data) {
System.out.println("data :" + data.get(0));
}
<? extends T>
表示该通配符所代表的类型是T类型的子类(或本身),<? super T>
表示该通配符所代表的类型是T类型的父类。
类型通配符下限
类型通配符下限通过形如List<? super Number>
来定义,表示类型只能接受Number
本身及其父类类型,如Object
类型的实例。
泛型方法与类型通配符的区别
什么时候用类型通配符,什么时候用泛型方法呢?
- 通配符能用来支持灵活的子类化
- 泛型方法允许类型形参用来表示一个或多个参数或参数与返回值之间的依赖关系
- 类型通配符可以在方法名中定义形参类型,也可以定义变量类型;但泛型方法的类型形参必须在显示声明
设定类型形参的上限
泛型不仅允许使用通配符形参上限,还可以设置类型形参的上限。例如:
public class Apple<T extends Number>{//Number及其子类
public static void main(String[] args){
Apple<Integer> a = new Apple<>();
Apple<String> b = new Apple<>();//String不是Number的子类,将引起异常
}
}
上面定义了一个泛型类
,形参上限是Number
及其子类,传入一个String
类将导致编译错误。如果需要设定多个上限和接口,表示该形参类型既是父类及其子类,并且实现了多个上限接口。需要注意的是所有接口上限
必须在类上限之后,类上限
始终在前面。
public class Apple<T extends Number & java.io.Serializable>{}
擦除和转换
严格泛型代码里,带泛型声明的类总应该带着类型参数。但是允许使用带泛型声明的类时,不指定类型实参,如果没有指定的话,则该类型参数被称作raw type
原始类型,默认声明为该类型参数的第一个上限类型!
当把一个带泛型信息的对象赋给另一个没有泛型信息的变量时,泛型信息将抛弃,称擦除
。比如一个List<String>
类型转给List
类型,该List
的类型上限变为Object
。 如果将List
对象赋给一个List<String>
对象,不会引起编译错误,但是提示未经检查的转换
。
List<Integer> a = new ArrayList<>();
a.add(6);
List list = a;
List<String> ls = list;//引起警告
System.out.println(ls.get(0));//转换将引起错误
泛型与数组
数组元素类型不能包含类型变量或形参,除非无上限类型通配符,但可以声明元素类型包含类型变量或形参的数组。比如:只能说明List<String>[]
形式的数组,不能创建ArrayList<String>[10]
这样的数组。
List<String>[] a = new ArrayList[10]; //没有问题