网站建设先做后付费大庆市建设大厦网站
2026/1/7 22:24:14 网站建设 项目流程
网站建设先做后付费,大庆市建设大厦网站,黑龙省建设厅网站首页,3d建模游戏本篇我们来讲解 Java 泛型~1. 泛型是什么#xff1f;为什么要用泛型#xff1f;核心概念#xff1a;泛型是 JDK 5 引入的特性#xff0c;允许在定义类、接口或方法时使用类型参数#xff08;Type Parameter#xff09;。你可以将这个类型参数看作一个占位符#xff0c;表…本篇我们来讲解 Java 泛型~1. 泛型是什么为什么要用泛型核心概念泛型是 JDK 5 引入的特性允许在定义类、接口或方法时使用类型参数Type Parameter。你可以将这个类型参数看作一个占位符表示某种具体的类型但具体是什么类型要到使用这个类、接口或方法时才指定。解决的问题类型安全在 JDK 5 之前集合类如ArrayList,HashMap默认存储Object类型。当你从集合中取出元素时需要强制转型Casting为你期望的类型。如果实际存储的类型与你强制转型的目标类型不匹配就会在运行时抛出ClassCastException。泛型在编译时就检查类型是否匹配大大减少了这类运行时错误。没有泛型的问题示例ArrayList list new ArrayList(); // 存储 Object list.add(Hello); list.add(123); // 不小心存入了 Integer String str (String) list.get(1); // 编译通过但运行时抛出 ClassCastException!使用泛型的改进ArrayListString list new ArrayList(); // 指定存储 String list.add(Hello); // list.add(123); // 编译错误编译器阻止存入 Integer String str list.get(0); // 无需强制转型直接是 String消除强制转型如上例所示使用泛型后从集合中取出元素时不需要再进行强制转型代码更简洁清晰。提高代码复用性你可以编写适用于多种类型的通用代码。例如一个ListT接口可以用于存放任何类型T的元素而不需要为String、Integer等分别编写StringList、IntegerList。2. 泛型类定义在类名后面用尖括号声明一个或多个类型参数。这些参数可以在类体中像普通类型一样使用作为字段类型、方法参数类型、方法返回类型等。语法public class ClassNameT1, T2, ..., Tn { // 类体可以使用 T1, T2, ..., Tn }实例化创建泛型类的对象时在类名后的尖括号中指定具体的类型参数称为类型实参Type Argument。ClassName具体类型1, 具体类型2, ..., 具体类型n obj new ClassName();示例定义一个简单的泛型Box类public class BoxT { private T content; // T 表示某种类型的内容 public void setContent(T content) { this.content content; } public T getContent() { return content; } }使用BoxString stringBox new Box(); // T 被指定为 String stringBox.setContent(Hello Generics!); String message stringBox.getContent(); // 直接是 String无需转型 BoxInteger intBox new Box(); // T 被指定为 Integer intBox.setContent(42); int number intBox.getContent(); // 自动拆箱为 int注意泛型类可以有多个类型参数如PairK, V。类型参数通常用单个大写字母表示如T、E、K、V但这只是约定俗成。实例化时构造函数后的称为菱形语法Diamond Operator允许省略类型实参编译器会根据声明推断。3. 泛型接口定义与泛型类类似在接口名后声明类型参数。语法public interface InterfaceNameT1, T2, ..., Tn { // 接口方法可以使用 T1, T2, ..., Tn }实现方式一实现类在实现接口时指定具体类型。public interface ProducerT { T produce(); } public class StringProducer implements ProducerString { Override public String produce() { return Generated String; } }方式二实现类本身也声明为泛型类类型参数与接口一致。public class GenericProducerT implements ProducerT { Override public T produce() { // ... 生产 T 类型对象的逻辑 ... return result; } }使用时再指定具体类型GenericProducerInteger intProducer new GenericProducer();4. 泛型方法定义在方法签名上声明类型参数该方法可以在不同类型上操作。语法在方法的返回类型之前或修饰符之后用尖括号声明类型参数。public T1, T2, ..., Tn 返回类型 方法名(参数列表) { // 方法体可以使用 T1, T2, ..., Tn }特点类型参数的作用域仅限于该方法本身。泛型方法可以定义在普通类中也可以定义在泛型类中此时泛型方法的类型参数可以与类的类型参数同名但含义不同。编译器通常能根据传入的参数类型推断出类型实参。示例public class Util { // 泛型方法交换数组中两个元素的位置 public static T void swap(T[] array, int i, int j) { T temp array[i]; array[i] array[j]; array[j] temp; } // 泛型方法查找数组中最大值 (要求 T 实现了 ComparableT) public static T extends ComparableT T max(T[] array) { if (array null || array.length 0) return null; T maxVal array[0]; for (T element : array) { if (element.compareTo(maxVal) 0) { maxVal element; } } return maxVal; } }使用Integer[] intArray {1, 5, 3, 2}; Util.swap(intArray, 1, 2); // Integer 被编译器推断出来 Integer maxInt Util.max(intArray); // 同样推断出 Integer String[] strArray {apple, banana, cherry}; String maxStr Util.max(strArray); // 推断出 String注意示例中的T extends ComparableT是类型边界Type Bound用于约束T必须是实现了ComparableT接口的类型这样方法体内才能安全地调用compareTo方法。我们稍后会详细讲解边界。5. 类型擦除关键机制Java 泛型是通过类型擦除Type Erasure实现的。这意味着在编译时编译器会检查泛型代码的类型安全确保你放入ListString的是String。在编译后生成的字节码.class 文件中所有的类型参数都会被擦除掉替换成它们的上界如果没有指定上界则替换成Object并在必要的地方插入强制转型。目的为了兼容 JDK 5 之前的代码非泛型集合类。示例// 源代码 (编译前) ListString list new ArrayList(); list.add(Hi); String s list.get(0); // 经过类型擦除后的等效代码 (编译后近似表示) List list new ArrayList(); // 类型参数 String 被擦除 list.add(Hi); // 添加 String 没问题 String s (String) list.get(0); // 编译器插入的强制转型影响无法获取运行时类型参数例如ListString.class或new T()都是不合法的因为运行时T已经不存在了被擦除为Object或边界类型。不能创建参数化类型的数组如new ListString[10]通常会导致编译警告或错误因为数组需要确切知道其元素类型而擦除后ListString和ListInteger在运行时都是List数组无法区分。泛型类的不同实例化共享同一个类BoxString.class BoxInteger.class结果为true。6. 通配符:?目的增加泛型的灵活性表示“未知类型”。主要用于方法参数、局部变量有时也用于字段。类型无界通配符?表示任何类型。用途当你编写的方法只需要读取集合元素作为Object或某个公共父类使用而不关心具体类型时。示例public static void printList(List? list) { for (Object obj : list) { // 元素被当作 Object 处理 System.out.println(obj); } // list.add(new Object()); // 错误不能添加 (除了 null)因为不知道具体类型 }限制不能向声明为List?的变量添加除null以外的任何元素因为你不知道里面具体是什么类型。上界通配符? extends UpperBound表示UpperBound类型或其子类型。用途支持协变Covariance。你可以安全地从这样的结构中读取元素读取的元素至少是UpperBound类型但通常不能添加元素除了null。示例public static double sumOfList(List? extends Number list) { double sum 0.0; for (Number num : list) { // 安全读取每个元素都是 Number 或其子类 sum num.doubleValue(); } return sum; // list.add(new Integer(1)); // 错误不能添加可能是 ListDouble } ListInteger intList Arrays.asList(1, 2, 3); ListDouble doubleList Arrays.asList(1.1, 2.2, 3.3); sumOfList(intList); // OK, Integer extends Number sumOfList(doubleList); // OK, Double extends Number下界通配符? super LowerBound表示LowerBound类型或其父类型。用途支持逆变Contravariance。你可以安全地写入元素写入的元素是LowerBound或其子类但读取时只能当作Object因为不知道具体父类是什么。示例public static void addNumbers(List? super Integer list) { for (int i 1; i 5; i) { list.add(i); // 安全写入Integer 是 LowerBound } // Integer num list.get(0); // 错误读取出来可能是 Number 或 Object Object obj list.get(0); // 只能当作 Object 读取 } ListNumber numList new ArrayList(); ListObject objList new ArrayList(); addNumbers(numList); // OK, Number super Integer addNumbers(objList); // OK, Object super IntegerPECS 原则Producer-Extends, Consumer-Super。如果你需要一个结构提供生产元素Producer使用? extends T。如果你需要一个结构接受消费元素Consumer使用? super T。如果一个结构同时生产和消费你可能需要使用确切的类型参数T。7. 类型边界目的约束类型参数可以代表哪些类型。使用extends关键字在泛型中extends可以表示类继承或接口实现。语法单个边界T extends ClassOrInterface多个边界T extends ClassA InterfaceB InterfaceC类只能有一个且必须在第一个接口可以有多个。示例// T 必须是 Number 或其子类 public class NumericBoxT extends Number { private T value; // ... getter, setter ... public double getValueAsDouble() { return value.doubleValue(); // 安全调用因为 T 是 Number } } // T 必须实现 Comparable 接口并且能够和自己比较 (ComparableT) public static T extends ComparableT T max(T a, T b) { return (a.compareTo(b) 0) ? a : b; } // 多个边界T 必须是 Serializable 的子类 且 实现 Comparable public class SerializableComparableT extends Serializable ComparableT { // ... }注意边界在编译时被检查确保类型安全。类型擦除时类型参数会被替换为其最左边的边界或Object如果没有边界。8. 泛型在继承和子类型中的规则泛型类本身BoxNumber和BoxInteger没有继承关系。即使Integer是Number的子类BoxInteger也不是BoxNumber的子类。通配符与子类型List? extends Number是List?的子类型。ListNumber是List? super Number的子类型 (不是直接的父子关系但ListNumber可以赋值给List? super Number变量)更重要的关系由通配符捕获ListInteger可以赋值给List? extends Number变量因为Integer extends Number。ListNumber可以赋值给List? super Integer变量因为Number super Integer。9. 边界用例和限制不能实例化类型参数new T()是非法的因为运行时T被擦除。变通方法通过反射需要ClassTclazz 参数或工厂模式。不能用于静态上下文类的类型参数不能用于静态方法或静态字段因为静态成员属于类而类型参数属于实例。public class BoxT { // private static T staticField; // 错误 // public static T staticMethod() { ... } // 错误 public static U U genericStaticMethod(U u) { ... } // OK泛型方法有自己的类型参数 }不能创建基本类型的参数化类型泛型类型参数必须是引用类型。不能有Listint只能用ListInteger。自动装箱/拆箱缓解了这个问题。不能抛出或捕获泛型类的实例catch (T e)是不允许的。泛型类也不能直接或间接继承Throwable。方法重载冲突类型擦除可能导致两个方法签名在编译后变得相同引起编译错误。public class Example { public void print(ListString list) { ... } public void print(ListInteger list) { ... } // 编译错误擦除后都是 print(List) }总结泛型通过类型参数、类型擦除、通配符和类型边界等机制提供了强大的类型安全性和代码复用能力。理解类型擦除是深入掌握泛型行为的关键而通配符尤其是extends和super则提供了处理不同类型集合时的灵活性。遵循 PECS 原则有助于正确使用通配符。虽然泛型有一些限制主要是由类型擦除带来的但它们极大地提升了 Java 程序的健壮性和可读性。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询