java泛型详解

java泛型是java语言中的一颗语法糖,对java的功能没有什么影响,但是能够方便程序员的使用,减少出错的机会。比如hashMap的get()方法,获取的类型是一个Object,java中的所有类型都继承自java.lang.Object类,那么get()方法获取到的对象就存在向任何类型转换的可能,除了程序员自己和运行期的虚拟机,没人知道get到的这个对象到底是什么类型,也就容易出错。还是举一个例子吧:

package test;

import java.util.ArrayList;
import java.util.List;

public class GenericityTest {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(1);
        list.add(new Dog());

        for(Object m : list){
            System.out.println((Dog)m);
        }
    }
}
class Dog{
    @Override
    public String toString() {
        return String.valueOf(this.hashCode());
    }
}

这段代码在编译期间是没有问题的,因为迭代器返回的对象在编译期是不知道它到底是什么类型,我在list中放了一个int类型的1,又放了一只狗,结果会在运行期间报类型转换错误。假如写成List《Dog》。。。。,加上泛型就能够在编译期间发现这个错误,大家可以自己试一下。
java中的泛型所起的作用就是一个参数的作用,这个参数规定了在集合里能放什么类型,放了不符合参数规定的类型就会在编译期报错。
接下来稍微深入一下,泛型在运行期间到底起到作用了没,答案是否定的,java的泛型实现是用的类型擦除的技术,在编译成class之后,泛型也就消失了,
在java中List《String》和List《Int》类型是同一个类型,也就是说java是一个伪泛型,了解C#的可能知道在C#中List《String》和List《Int》是两个不同的类型。为了证明举了下面这个例子:

package test;

import java.util.ArrayList;
import java.util.List;

public class GenericityTest {

        public static void say(List<String> list){//编译器报错
        System.out.println("String");
    }
    public static void say(List<Integer> list){//编译器报错
        System.out.println("Integer");
    }

    public static void main(String[] args) {

 }
}

为什么会这样,因为java编译成class后会擦除掉泛型,所以两个say()方法在class中完全一样从而报错。但是加入非要通过泛型来重载的话,就必须采取一个并不好的方案,改变返回值。有人会说了,java语言中什么时候用返回值来重载了啊,你瞎说!表面上是这样,其实并不是根据返回值来重载,而是通过返回值的不同来防止编译期间的报错,虽然java语言的重载不包括返回值的不同来区分不同的方法,但是在class文件中方法的描述符除了方法名,参数个数类型外,还包括方法的返回值,只有返回值不同的两个方法在class中是可以区分和共存的。不明白的可以结合下面这个例子理解:

package test;

import java.util.ArrayList;
import java.util.List;
//必须使用jdk 1.6的javac才能够编译成功,别的编译器或者jdk版本仍然拒绝编译
public class GenericityTest {

    public static  int say(List<String> list){//返回int
        System.out.println("String");
        return 0;
    }
    public static  String say(List<Integer> list){//返回String
        System.out.println("Integer");
        return "ss";
    }

    public static void main(String[] args) {
        say(new ArrayList<String>());
        say(new ArrayList<Integer>());
 }
}

对于类型擦除,有兴趣的可以反编译试试,写一个List《String》……,反编译后发现泛型都不见了,变成了List……。
最后补充一点,类型擦除并不意味着完全没有泛型的信息,而是对方法区的字节码进行擦除,在想list中存对象的时候是当成Object存的, LocalVariableTypeTable中的signature中还保留这泛型的信息。
这里写图片描述

<think>嗯,用户想了解Java详解。首先,我需要回忆一下Java的基本概念和应用场景。Java 5引入的特性,主要用于增强类安全,减少类型转换的麻烦,以及提高代码的可重用性。用户可能是在学习过程中遇到了相关的困惑,或者在实际开发中需要更深入的理解。 接下来,用户的需求可能包括了解的基本语法、类擦除、通配符、方法、类与接口,以及使用限制等。需要分步骤讲解,确保结构清晰。可能需要从为什么需要开始,然后逐步展开各个部分。 要注意的是,用户可能对类擦除这一Java的实现机制不太清楚,需要详细解释。同时,通配符部分,尤其是上下界通配符的区别和使用场景,可能是容易混淆的地方,需要举例说明。此外,在集合框架中的应用是一个常见的例子,可以用来帮助用户理解。 还需要考虑用户可能的误区,比如在运行时类信息被擦除的问题,导致无法进行instanceof判断或者创建数组。这些限制需要明确说明,避免用户在实际使用中犯错。 另外,用户可能对方法与类的区别不太清楚,需要分别举例说明。比如,方法可以在普通类中定义,而类则是整个类作用于类参数。 最后,要确保语言通俗易懂,避免过于技术性的术语,同时结合代码示例,让用户能够通过实例理解抽象的概念。检查是否有遗漏的重要知识点,比如继承、通配符捕获等,但可能根据用户的提问深度决定是否展开。</think>以下是对Java的系统化详解,分步骤解析核心概念和用法: --- ### 一、解决的问题 1. **类安全** 在集合等场景中,编译时检测类错误,避免运行时`ClassCastException` ```java // 非写法(存在隐患) List list = new ArrayList(); list.add("abc"); Integer num = (Integer) list.get(0); // 运行时异常 // 写法(编译时报错) List<String> list = new ArrayList<>(); list.add(123); // 编译错误 ``` 2. **消除强制类型转换** 明确容器存储类,减少冗余代码 --- ### 二、基础语法 #### 1. 类 ```java public class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; } } ``` - `T`为类参数(可任意命名,常用`T`, `E`, `K`, `V`等) - 实例化时指定具体类: ```java Box<Integer> intBox = new Box<>(); ``` #### 2. 接口 ```java public interface Comparator<T> { int compare(T o1, T o2); } ``` #### 3. 方法 ```java public <T> void printArray(T[] array) { for (T element : array) { System.out.println(element); } } ``` - 类参数声明在方法返回值前 --- ### 三、类擦除(关键机制) 1. **编译时类检查** 编译器检查合法性 2. **运行时类擦除** 生成的字节码中不包含信息,替换为原始类 ```java List<String> list = new ArrayList<>(); // 编译后等效于 List list = new ArrayList(); ``` 3. **桥方法生成** 用于保持多态性(如继承类时) --- ### 四、通配符 #### 1. 上界通配符 `<? extends T>` ```java void process(List<? extends Number> list) { // 允许读取为Number类 Number num = list.get(0); // 不允许添加元素(编译器无法确定具体子类) } ``` #### 2. 下界通配符 `<? super T>` ```java void addNumbers(List<? super Integer> list) { list.add(42); // 允许添加Integer及其父类元素 Object obj = list.get(0); // 读取只能作为Object } ``` #### 3. 无限定通配符 `<?>` ```java void printList(List<?> list) { for (Object elem : list) { System.out.println(elem); } } ``` --- ### 五、重要限制 1. **无法实例化类参数** ```java T obj = new T(); // 编译错误 ``` 2. **不能创建数组** ```java T[] array = new T[10]; // 编译错误 ``` 3. **静态上下文中不可用** ```java public class Test<T> { private static T instance; // 错误 } ``` 4. **instanceof检测无效** ```java if (list instanceof ArrayList<String>) { ... } // 编译错误 ``` --- ### 六、高级技巧 1. **类参数多重限定** ```java <T extends Comparable & Serializable> ``` 2. **与反射** ```java Type type = new TypeToken<Map<String, Integer>>(){}.getType(); ``` 3. **PECS原则** Producer-Extends, Consumer-Super (适合选择通配符边界的场景) --- ### 七、典应用场景 1. 集合框架(`ArrayList<E>`, `HashMap<K,V>`) 2. 工具类方法(`Collections.sort()`) 3. 回调接口设计(`Comparator<T>`) 4. 自定义通用数据结构 --- 通过掌握这些核心概念,可以写出类更安全、复用性更强的Java代码。建议结合具体项目实践加深理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值