为什么需要泛型?
适用于多种数据类型执行相同的代码,看下面例子
private static int put(int a, int b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static float put(float a, float b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static double put(double a, double b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
没有泛型的话,要实现不同类型的加减,每个类型都需要重载一个put方法;通过泛型,我们可以复用一个方法:
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();
}
泛型中的类型在使用的过程去指定,不需要强制类型转换(这样类型是安全的,编译器会在编译阶段检查类型)
看下面的这个例子:
List<String> strList = new ArrayList<>();
List<Object> objList = new ArrayList<>();
objList.add("laasc"); // 代码A
objList = strList; // 代码B
代码A很明显是合法的。Object类型是String类型的父类。那么代码B为什么不合法呢?
在 Java 中,对象类型的赋值其实是引用地址的赋值,也就是说,假设代码B赋值成功,objList和strList变量引用的是同一个地址。那会有什么问题呢?
如果此时,往objList中添加了一个非String类型的元素,也就相当于往strList中添加了一个非String类型的元素。很明显,此处就破坏了List<String>
strList。所以,Java 编译器会认为代码2是非法的,这是一种安全的做法。
这里我们简单的定义一个泛型类:
class PointParadigm<T> { // 随便定义一个泛型类,T是泛型标志符,T是type的简称
private T var; // var参数的类型由T指定,外部传入时指定
public T getVar() { // 此时返回值的类型由外部指定
return var;
}
public void setVar(T var) { // 设置的类型也是由外部指定
this.var = var;
}
}
public class GeneratorDemo {
public static void main(String[] args){
PointParadigm<String> p = new PointParadigm<String>() ; // 指定泛型类里面的var类型为String类型
p.setVar("test") ; // 设置字符串
System.out.println(p.getVar().length()) ; // 打印字符串的长度
}
}
多元泛型
class User<K,V>{ // 此处指定了两个泛型类型
private K name ; // 此变量的类型由外部决定
private V age ; // 此变量的类型由外部决定
public K getName() {
return name;
}
public void setName(K name) {
this.name = name;
}
public V getAge() {
return age;
}
public void setAge(V age) {
this.age = age;
}
}
public class GeneratorDemo {
public static void main(String args[]){
user<String,Integer> t = null ; // 定义两个泛型类型的对象
t = new user<String,Integer>() ; // 里面的name为String,age为Integer
t.setName("tom") ; // 设置第一个内容
t.setAge(20) ; // 设置第二个内容
System.out.print("姓名;" + t.getName()) ; // 取得信息
System.out.print(",年龄;" + t.getAge()) ; // 取得信息
}
}
泛型接口如何定义和使用?
简单的泛型接口
interface UserInfo<T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
}
class UserInfoImpl<T> implements UserInfo<T>{ // 定义泛型接口的子类
private T var ; // 定义属性
public UserInfoImpl(T var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
}
public class GeneratorDemo{
public static void main(String arsg[]){
Info<String> i = null; // 声明接口对象
i = new UserInfoImpl<String>("tom") ; // 通过子类实例化对象
System.out.println("内容:" + i.getVar()) ;
}
}
泛型方法如何定义和使用?
泛型方法,是在调用方法的时候指明泛型的具体类型。
public <T> T getData(Class<T> c) {
T t = c.newInstance();
return t;
}
调用泛型方法格式
Generator generator = new Generator();
Object obj = generator.getData(Class.forName("这里是指定泛型的具体类型"));
定义泛型方法时,必须在返回值前边加一个<T>
,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。 Class<T>
的作用就是指明泛型的具体类型,而Class<T>
类型的变量c,可以用来创建泛型类的对象。
为什么要用变量c来创建对象呢?既然是泛型方法,就代表着我们不知道具体的类型是什么,也不知道构造方法如何,因此没有办法去new一个对象,但可以利用变量c的newInstance方法去创建对象,也就是利用反射创建对象。 泛型方法要求的参数是Class<T>
类型,而Class.forName()方法的返回值也是Class<T>
,因此可以用Class.forName()作为参数。其中,forName()方法中的参数是何种类型,返回的Class<T>
就是何种类型。在本例中,forName()方法中传入的是User类的完整路径,因此返回的是Class<User>
类型的对象,因此调用泛型方法时,变量c的类型就是Class<User>
,因此泛型方法中的泛型T就被指明为User,因此变量obj的类型为User。
当然,泛型方法不是仅仅可以有一个参数Class<T>
,可以根据需要添加其他参数。
泛型的上限和下限?
在使用泛型的时候,我们可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。
上限
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 GeneratorDemo{
public static void main(String args[]){
Info<Integer> i1 = new Info<Integer>() ; // 声明Integer的泛型对象
}
}
下限
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 GeneratorDemo{
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 + ", ") ;
理解Java中的泛型是伪泛型?
泛型中类型擦除 Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。
评论区