泛型概念的提出(为什么需要泛型)?
参考博文
https://www.cnblogs.com/lwbqqyumidi/p/3837629.html
https://www.cnblogs.com/coprince/p/8603492.html
首先,我们看下下面这段简短的代码:
import java.util.ArrayList;
import java.util.List;
public class demo1 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("张三");
list.add("李四");
list.add(10);
for(int i = 0; i < list.size(); i++){
String str = (String) list.get(i);
System.out.println(str+" ");
}
System.out.println();
}
}
定义了一个List类型的集合,先向其中加入了两个字符串类型的值,随后加入一个Integer类型的值。这是完全允许的,因为此时list默认的类型为Object类型。在之后的循环中,由于忘记了之前在list中也加入了Integer类型的值或其他编码原因,很容易出现类似于//1中的错误。因为编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常。因此,导致此类错误编码过程中不易发现。
在如上的编码过程中,我们发现主要存在两个问题:
1.当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,改对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。
2.因此,//1处取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现“java.lang.ClassCastException”异常。
那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就不会出现“java.lang.ClassCastException”异常呢?答案就是使用泛型。
什么是泛型
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
用泛型对上面出现的问题进行解决;
import java.util.ArrayList;
import java.util.List;
public class demo1 {
public static void main(String[] args) {
List<String> list = new ArrayList(); //规定list集合为String类型
list.add("张三");
list.add("李四");
/* list.add(10);*/ 此处在先这样写编译就会报错 应为10 不是String类型
for(int i = 0; i < list.size(); i++){
String str = (String) list.get(i);
System.out.println(str+" ");
}
System.out.println();
}
}
采用泛型写法后,在//1处想加入一个Integer类型的对象时会出现编译错误,通过List,直接限定了list集合中只能含有String类型的元素,从而在//2处无须进行强制类型转换,因为此时,集合能够记住元素的类型信息,编译器已经能够确认它是String类型了。
泛型类
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。
和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
class Generic<T,V>{ //制定多个泛型类型
private T t;
private V v;
public void setV(V v) {
this.v = v;
}
public void setT(T t) {
this.t = t;
}
public V getV() {
return v;
} /*这里虽然使用泛型但并不是泛型方法,这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。*/
public T getT() {
return t;
}
@Override
public String toString() {
return "generic{" +
"t=" + t +
", v=" + v +
'}';
}
}
public class demo1 {
public static void main(String[] args) {
Generic<String,Integer> generic = new Generic<String,Integer>();
generic.setT("张三");
generic.setV(30);
System.out.println(generic);
}
}
泛型方法
你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则: 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的)。每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)
class Generic<T,V>{ //制定多个泛型类型
private T t;
private V v;
public Generic() {
super();
}
public Generic(T t, V v) {
this.t = t;
this.v = v;
}
public void setV(V v) {
this.v = v;
}
public void setT(T t) {
this.t = t;
}
public V getV() {
return v;
}
public T getT() {
return t;
}
@Override
public String toString() {
return "generic{" +
"t=" + t +
", v=" + v +
'}';
}
public <T> void genericMethod(T t){ //泛型方法
System.out.println(t);
}
}
public class demo1 {
public static void main(String[] args) {
Generic<String,Integer> generic = new Generic<String,Integer>();
generic.setT("张三");
generic.setV(30);
generic.genericMethod("lis");
System.out.println(generic);
}
}
泛型通配符
参考文章
https://www.cnblogs.com/alsf/p/5690052.html
通配符只有在修饰一个变量时会用到,使用它可方便地引用包含了多种类型的泛型;
复制代码
public static void main() {
//不使用通配符
ArrayList<Object> arr = new ArrayList<Object>();
// ArrayList<Object> arr = new ArrayList<String>(); 编译不通过,arr只能引用包含Object的集合
//使用通配符
ArrayList<?> arr2;
arr2 = new ArrayList<String>();
arr2 = new ArrayList<Integer>();
arr2.get(0); //返回的,是一个Object对象,通配符会使原集合包含类型信息丢失,也是通配符的使用代价
// 通常在方法参数中才会使用通配符,使得这个方法可以引用多种泛型集合。这个和范型方法不一样,这里只是一个引用变量
void gMethod(ArrayList<? extends Number> param) {
}
复制代码
通配符的extends super关键字
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限和下限。
设置上限
复制代码
class Info<T>{
private T var ; // 定义泛型变量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){ // 直接打印
return this.var.toString() ;
}
};
public class GenericsDemo17{
public static void main(String args[]){
Info<Integer> i1 = new Info<Integer>() ; // 声明Integer的泛型对象
Info<Float> i2 = new Info<Float>() ; // 声明Float的泛型对象
i1.setVar(30) ; // 设置整数,自动装箱
i2.setVar(30.1f) ; // 设置小数,自动装箱
fun(i1) ;
fun(i2) ;
}
public static void fun(Info<? extends Number> temp){ // 只能接收Number及其Number的子类
System.out.print(temp + "、") ;
}
};
运行成功。但是,如果传人的泛型类型为String的话就不行,因为String不是Number子类。
在类中使用泛型上限。
package Thread1;
class Info<T extends Number>{ // 此处泛型只能是数字类型
private T var ; // 定义泛型变量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){ // 直接打印
return this.var.toString() ;
}
};
public class demo1{
public static void main(String args[]){
Info<Integer> i1 = new Info<Integer>() ; // 声明Integer的泛型对象
}
};
如果在使用Info的时候设置成String类型,则编译的时候将会出现错误(String不是Number子类):
设置下限
class Info<T>{
private T var ; // 定义泛型变量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){ // 直接打印
return this.var.toString() ;
}
};
public class GenericsDemo21{
public static void main(String args[]){
Info<String> i1 = new Info<String>() ; // 声明String的泛型对象
Info<Object> i2 = new Info<Object>() ; // 声明Object的泛型对象
i1.setVar("hello") ;
i2.setVar(new Object()) ;
fun(i1) ;
fun(i2) ;
}
public static void fun(Info<? super String> temp){ // 只能接收String或Object类型的泛型,String类的父类只有Object类
System.out.print(temp + "、") ;
}
};