Java中的泛型

Java中的泛型

Copyright©stonee

在JDK5之前,为了实现参数类型的任意化,都是 先通过Object类型引入,再进行强制类型转换 来处理。这种方式不仅使得代码臃肿,还要求程序员必须对实际所使用的参数类型已知的情况下才能进行,否则容易引起ClassCastException异常。而且,泛型把这些错误提前到编译期,更安全更方便。

  • 我们尝试比较一下通过Object类实现参数类型任意化和通过泛型实现参数类型任意化的栗子:

通过Object类来实现上述结果

package chapter08;

/** *object类实现参数类型任意化 
* @author www.stonee.club 
*/
public class ttest {    
    public static void main(String [] args){       
        c cc = new c();        
        int a = (int)cc.getA(5);    //必须强制类型转换为int,意味着必须要已知 c中getA方法的参数类型才可以 。这意味着:当上述语句循环的时候,判断参数类型将是很麻烦的事
        System.out.println(a);        
        cc.add("888");  //此处没有检查,可以添加任何对象    
    }
}
class c{    
    private Object a;    
    public Object getA(int i) {        //必须要已知这里的参数类型
        return i;    
    }
    public void add(Object o){        
        System.out.println(o);    
    }
}

通过泛型来实现上述结果:

package chapter08;

/** * 泛型类,用于和object继承作比较 
* @author www.stonee.club 
*/
public class ttestfanxing {    
    public static void main(String [] args){        
        ct <String> cc = new ct<>();        
        String a = cc.getA("44");        //不用强制类型转换
        System.out.println(a);       
        cc.add("888");      //此处必须添加string对象 ,否则会报错   
    }
}
class ct <T>{   
    private T a;    
    public T getA(T i) {        
        return i;   
    }
    public void add(T o){        
        System.out.println(o);    
    }
}

ok,可以看出来,泛型最大的特点就是实现参数类型的任意化。而且,泛型和Object的本质不一样,后者是通过 向上转型后再向下转型 实现的参数任意化,而前者根本不用转型。那么下面具体通过泛型类,泛型方法,泛型接口来了解泛型

语法

1. 泛型类

泛型<>中必须是引用数据类型,且前后类型必须一致
泛型一般不定义为Object

  • 泛型类的语法为:
 class className < customName >{ 
    private customName a;   //此时customName代替原有的类型变量
    }
    public customName getA(){
        return a;
    }   
}

调用:className<String> c = new className<>();//jdk7之后后面尖括号不用写

  • 我们可以把一个方法的返回值设置为泛型类
public className <String> methodName(String paramName){
    return new className<>();   //不能return String;
}  

一定要注意,虽然此类可以作为String用,但是返回值千万不能是String,而必须还要是这个泛型类的实例才可以!所以从这个角度来说,泛型类的构造方法必须要适应固定的需求才行。

2. 泛型方法

  • 在普通类中定义泛型方法:
class normalClass{   
    public static <T> T getW(T...a){        //<T> 声明定义泛型方法,T表示返回的泛型方法
        return a[a.length/2];    
     }
 }
  • 当类和方法都具有泛型时:
class className<T>{
    
    public <Q> void method(Q a){
        System.out.println(a);
    }
}
//调用
class main{
    public static void main(String [] args){
        className<String> c = new className<>();
        c.method(ture);    //boolean 自动装箱
    }
}

静态方法必须有自己的泛型,不能和类的泛型一致,因为泛型必须是在类加载实例的基础上的

3. 泛型接口

泛型接口和类的语法差不多

interface in <T>{    
    T a();
}

尝试用类实现接口

class ci implements in<String>{    
    @Override   
    public String  a() {
        return "stoneeissocool";    
    }
}

当接口和类都有泛型的时候

class cii<T> implements in<T>{    
//平常没必要在实现接口的时候加泛型    
    @Override    
    public T  a() {  
        return null;    
    }
}

4. 类型变量的限定

假如我们需要对传入类型参数进行限制:

 public <T> T min(T a){
    T s = 1;
    if ( s.compareTo(a) > 0 )
        s = a;
 }

显而易见,此时我们需要T实现Comparable接口,这时就必须对其进行限制,如:<T extends Comparable>此处虽然是实现,但是语法是规定的extends

如果需要继承多个:<T extends Comparable & Serializable>

5.泛型中的通配符

我们知道,虽然Employee和Manager是父子关系,但是Pair<Employee>Pair<Manager>并没有任何关系,那我们如何让方法public void print(Pair<Employee> p) 也可以接收Pair<Manager> 类型的参数呢?这就用到了通配符

  • Pair<? extends E> e 是E以及其子类的基类,可以接收Pair<sonOfE>类型的参数

此处注意不能调用更改器方法,因为编译器无法对setFirst()方法的输入进行强制类型检查。

  • Pair<? super E> e E及其子类拿出来

此处不能调用访问器方法,因为编译器无法给getFirst()方法输出前插入强制类型转换。除了将getFirst()方法返回值赋给一个Object,否则编译错误。

  • 无限定通配符Pair<?>

在这里插入图片描述

泛型类的内存逻辑模型

我们首先要知道的是,和内部类,lambda表达式一样,Java中的泛型语法在jvm中是不存在的。所以Java编译器需要先把泛型语法转换为虚拟机可已识别的语言。其实,就是转换成了我们开篇所提到的通过Object来实现参数任意化,只不过强制类型转化等需要虚拟机来完成。

假设此时有一个泛型类:

Pair<T> {
    private T a;
    public void set(T x){
       this.a = x;
   }
   public T get(){
       return a;
   }
}

当这个泛型类被编译的时候,和一般的类有些不同,编译器把它变成的字节码文件中含有两种,一种是泛型类信息和泛型类的普通类。因为在前面说过,jvm并不承认泛型,所以两种类型中的普通类是为了适应jvm。

当在另外一个类中调用Pair类时:
Pair <String> a = new Pair <> ();
这段代码的调用方式为:

  • 第一步:编译器来说,确实存在Pair类,调用get和set方法的时候也会正常把类型变为String,所以如果此时参数类型不为String时,编译器会报错
  • 第二步:当编译器把java文件编译为字节码文件时,因为jvm不理解泛型语法,所以编译器需要把带有<T> 全部擦除(类型擦除)原来的参数类型全部变为Object(详情可自习揣摩本文开篇的例子),然后编译器加上强制类型转换,然后传给jvm。

下面是内存逻辑图:

在这里插入图片描述

由于类型擦除引出的问题:

class Pair<T>{
    T first;
    public void setFirst(T first){
        this.first = first;
    }
}
class a extends Pair<String>{
    @Override
    public void setFirst(String first){
        this.first = first;
    }
}

当编译器将类型擦除后class a变为:

class a extends Pair{

    public void setFirst(Object first){
        this.first = first;
    }
}

这显然和原因不符合,这就需要编译器在Pair中生成一个桥方法对类型进行强制转换:

class Pair<T>{
    T first;
    public void setFirst(T first){
        this.first = first;
    }
    public void setFirst(Object first){
        setFirset((String) first);
    }
}

注:同样的,由于getset返回值一个是Object,另一个是String,因为具有相同返回类型的两个同名通参方法是不合法的,所以此处运行会报错

泛型的约束和局限性

  • 不能用基本类型实例化参数类型

例如:pair<double> 当类型擦除后会变成pair,由于Object中没有基本类型,所以不能转换

  • 运行时类型查询只适用于原始类型

例如: (pair<String>) a.getClass()的返回值是pair

  • 不能创建参数化类型的数组

例如:

//数组协变
Object[] table = new Pair[10];
table[0] = new pair();
table[1] = new Object();//编译时通过运行时不通过

上面是一个正常数组的例子,当我们用泛型数组的时候:

Pair<String>[] table = new Pair<String>[10];    //擦除后变为  Pair[] table = new Pair[10]
Object[] objarray = table;
objarray[0] = "hello";  
objarray[1] = new Pair<Emm>();

这个程序对于C#这种正常的泛型程序来说,两个赋值都是错误的,但当类型擦除之后,第二个赋值就可以通过,这显然不符合语义;

<think>好的,我现在需要帮助用户了解Java的使用方法和示例。首先,用户提供的引用内容提到类、方法、静态方法的以及伪的概念。我需要结合这些信息,整理出一个结构清晰的回答,并确保遵循系统级指令中的格式要求。 首先,用户可能对的基本概念不太清楚,所以需要先简单解释什么是,以及它的作用,比如类安全、代码复用等。然后,分点介绍的不同用法,比如类、方法、接口,每个部分都要有示例代码,并且代码中的数学表达式要用$...$格式,比如在解释方法时,如果有涉及类参数T,可以用$T$来表示。 接下来,需要注意引用内容中提到的伪,说明Java在编译时类擦除的特性,这可能会影响运行时无法获取的具体类。此外,用户可能想知道如何使用通配符和边界,比如上界和下界通配符,这部分需要给出示例,并正确应用LaTeX格式,比如$? extends Number$。 用户还提到静态方法的,需要特别注意静态方法不能使用类定义的,而必须自己声明,这点要强调,避免常见错误。示例代码需要正确展示如何在静态方法中定义类参数。 最后,生成相关问题时要考虑用户可能进一步想知道的内容,比如类擦除的影响、通配符的使用场景、与集合框架的结合,以及类边界的作用。确保每个问题都有针对性,帮助用户深入理解。 现在检查是否有遗漏,比如是否覆盖了所有引用中的要点,引用的标识是否正确添加,示例代码是否符合语法,数学表达式是否正确使用$符号。同时,确保回答结构清晰,分点明确,符合用户可见层的要求。</think>### Java使用方法及示例 Java用于实现**类参数**,提高代码复用性和类安全性[^1]。以下是具体用法及示例: --- #### 一、类 定义类时声明类参数$T$,可在类中作为类使用: ```java public class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; } } // 使用示例 Box<String> stringBox = new Box<>(); stringBox.setContent("Hello"); ``` --- #### 二、方法 在方法返回值前声明类参数$T$,可在非类中使用: ```java public class Utils { public static <T> T getMiddle(T[] arr) { return arr[arr.length/2]; } } // 调用示例 String[] names = {"Alice", "Bob", "Charlie"}; String middle = Utils.<String>getMiddle(names); // 返回"Bob" ``` --- #### 三、接口 定义接口时声明类参数,常用于集合类: ```java public interface List<T> { void add(T element); T get(int index); } // 实现示例 class StringList implements List<String> { // 必须使用String类实现方法 } ``` --- #### 四、类边界 限制范围: ```java public static <T extends Number> double sum(T a, T b) { return a.doubleValue() + b.doubleValue(); } // 只能传入Number及其子类 sum(10, 20.5); // 合法 sum("a", "b"); // 编译错误 ``` --- #### 五、通配符 处理未知类集合时使用: ```java // 上界通配符 void process(List<? extends Number> list) { /* 可读不可写 */ } // 下界通配符 void addNumbers(List<? super Integer> list) { /* 可写不可读 */ } ``` --- #### 关键特性 1. **类擦除**:编译后会被替换为Object或边界类[^3] 2. **静态方法限制**:静态方法需单独声明: ```java public static <T> void print(T item) { System.out.println(item); } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值