当前位置: 首页 > wzjs >正文

长春小程序开发制作杭州网站优化公司

长春小程序开发制作,杭州网站优化公司,wordpress 4.8.1 教程,无锡市建设银行总行网站一、简介 Java的泛型(Generics)是Java 5引入的一项重要特性,允许在定义类、接口和方法时使用类型参数,从而增强代码的类型安全性和重用性。泛型的主要目的是在编译时提供类型检查,减少运行时的类型转换错误。 1.1 基…

一、简介

Java的泛型(Generics)是Java 5引入的一项重要特性,允许在定义类、接口和方法时使用类型参数,从而增强代码的类型安全性和重用性。泛型的主要目的是在编译时提供类型检查,减少运行时的类型转换错误。

1.1 基本概念

泛型允许你编写可以操作多种类型的代码,而不需要为每种类型编写单独的类或方法。通过使用类型参数,可以在编译时指定具体的类型。

1.2 泛型类

泛型类是指具有一个或多个类型参数的类。类型参数在类定义时指定,并在实例化时确定具体类型。

public class Box<T> {private T item;public void setItem(T item) {this.item = item;}public T getItem() {return item;}
}

在这个例子中,Box 类有一个类型参数 T,可以在实例化时指定具体的类型:

Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
String item = stringBox.getItem();  // 不需要类型转换
1.3 泛型方法

泛型方法是指具有一个或多个类型参数的方法。类型参数在方法定义时指定,并在调用时确定具体类型。

public <T> void printArray(T[] array) {for (T element : array) {System.out.println(element);}
}

在这个例子中,printArray 方法有一个类型参数 T,可以在调用时指定具体的类型:

Integer[] intArray = {1, 2, 3};
String[] strArray = {"A", "B", "C"};printArray(intArray);  // 输出: 1 2 3
printArray(strArray);  // 输出: A B C
1.4 泛型的类型参数

类型参数可以是任何有效的Java标识符,但通常使用单个大写字母,如 T、E、K、V 等。常见的类型参数约定如下:

  • T:表示类型(Type)
  • E:表示元素(Element),常用于集合类
  • K:表示键(Key),常用于映射
  • V:表示值(Value),常用于映射
1.5 泛型的通配符

Java泛型支持通配符,用于表示未知类型。通配符有两种形式:?? extends T? super T

  1. 无界通配符
    • 无界通配符 ? 表示任意类型。
      public void printList(List<?> list) {for (Object element : list) {System.out.println(element);}
      }
      
  2. 上界通配符
    • 上界通配符 ? extends T 表示类型参数是 T 或其子类。
      public void printNumbers(List<? extends Number> list) {for (Number number : list) {System.out.println(number);}
      }
      
  3. 下界通配符
    • 下界通配符 ? super T 表示类型参数是 T 或其父类。
      public void addNumbers(List<? super Integer> list) {list.add(1);list.add(2);
      }
      
1.6 泛型的应用场景
  1. 集合框架:Java集合框架(如 ArrayList、HashMap 等)广泛使用泛型来保证类型安全。
  2. 工具类:泛型可以用于编写通用的工具类,如 Comparator、Function 等。
  3. 设计模式:泛型可以用于实现一些设计模式,如工厂模式、策略模式等。

二、泛型的类型擦除

泛型的类型擦除(Type Erasure) 是Java泛型实现的核心机制之一。它的主要目的是在编译时确保类型安全,同时在运行时保持与Java早期版本(Java 5之前)的兼容性。类型擦除的具体表现是:泛型类型参数在编译后被替换为它们的上限(通常是 Object),并在必要时插入类型转换。

2.1 类型擦除的工作原理
  1. 编译时:
    • 编译器会检查泛型代码的类型安全性,确保类型参数的使用是正确的。
    • 在编译后的字节码中,所有的泛型类型参数都会被擦除,替换为它们的上限(如果没有指定上限,则替换为 Object)。
    • 编译器会在需要的地方插入类型转换代码,以确保运行时的类型安全。
  2. 运行时:
    • 在运行时,JVM(Java虚拟机)并不知道泛型类型参数的具体信息。所有的泛型类型都被视为它们的上限类型(如 Object)。
    • 由于类型擦除,泛型类型的具体信息在运行时是不可用的。
2.2 类型擦除的具体表现
  1. 泛型类中的类型擦除
    以下是一个泛型类的例子:

    public class Box<T> {private T item;public void setItem(T item) {this.item = item;}public T getItem() {return item;}
    }
    

    在编译后,类型参数 T 会被擦除,替换为 Object:

    public class Box {private Object item;public void setItem(Object item) {this.item = item;}public Object getItem() {return item;}
    }
    

    当使用泛型类时,编译器会自动插入类型转换代码:

    Box<String> stringBox = new Box<>();
    stringBox.setItem("Hello");
    String item = stringBox.getItem();  // 编译后:String item = (String) stringBox.getItem();
    
  2. 泛型方法中的类型擦除
    以下是一个泛型方法的例子:

    public <T> T getFirst(List<T> list) {return list.get(0);
    }
    

    在编译后,类型参数 T 会被擦除,替换为 Object:

    public Object getFirst(List list) {return list.get(0);
    }
    

    当调用泛型方法时,编译器会自动插入类型转换代码:

    List<String> stringList = Arrays.asList("A", "B", "C");
    String first = getFirst(stringList);  // 编译后:String first = (String) getFirst(stringList);
    
  3. 有界类型参数的类型擦除
    如果泛型类型参数有上限(如 T extends Number),类型擦除时会替换为上限类型:

    public class Box<T extends Number> {private T item;public void setItem(T item) {this.item = item;}public T getItem() {return item;}
    }
    

    在编译后,类型参数 T 会被擦除,替换为 Number:

    public class Box {private Number item;public void setItem(Number item) {this.item = item;}public Number getItem() {return item;}
    }
    
2.3 类型擦除的优点
  1. 兼容性:
    • 类型擦除确保了Java泛型与早期版本(Java 5之前)的兼容性,使得现有的非泛型代码可以继续运行。
  2. 性能:
    • 由于类型擦除,泛型代码在运行时不需要额外的类型信息,减少了运行时的开销。
  3. 简单性:
    • 类型擦除简化了JVM的实现,因为JVM不需要为泛型引入新的机制。

三、泛型的限制

Java的泛型虽然极大地增强了代码的类型安全性和重用性,但也带来了一些问题和限制。

3.1 常见限制
  1. 不能创建泛型类型的实例:

    • 由于类型擦除,运行时无法知道类型参数的具体类型,因此不能直接创建泛型类型的实例。
    • 例如:
      public class Box<T> {private T item;// 错误:不能直接实例化泛型类型public Box() {this.item = new T();  // 编译错误}
      }
      
  2. 不能创建泛型数组:

    • 由于类型擦除,运行时无法确保数组的类型安全,因此不能直接创建泛型数组。
    • 例如:
      // 错误:不能创建泛型数组
      T[] array = new T[10];  // 编译错误// 替代方案:使用 ArrayList
      List<T> list = new ArrayList<>();
      
  3. 不能使用基本类型作为类型参数:

    • 泛型类型参数必须是引用类型,不能是基本类型(如 int、char 等)。
    • 例如:
      // 错误:不能使用基本类型
      List<int> intList = new ArrayList<>();  // 编译错误// 必须使用包装类
      List<Integer> integerList = new ArrayList<>();
      
  4. 类型擦除导致的运行时类型信息丢失:

    • 由于类型擦除,运行时无法获取泛型类型参数的具体信息。
    • 例如:
      List<String> stringList = new ArrayList<>();
      List<Integer> integerList = new ArrayList<>();// 运行时无法区分
      System.out.println(stringList.getClass() == integerList.getClass());  // 输出: true
      
  5. 泛型类型不能用于静态上下文:

    • 泛型类型参数不能用于静态字段、静态方法或静态初始化块,因为静态成员属于类级别,而泛型类型参数属于实例级别。
    • 例如:
      public class Box<T> {// 错误:不能使用泛型类型参数private static T staticField;  // 编译错误// 错误:不能使用泛型类型参数public static T staticMethod() {  // 编译错误return null;}
      }
      
  6. 泛型与重载的冲突:

    • 两个泛型方法如果只有类型参数不同,会导致编译错误,因为它们在编译后会被擦除为相同的方法签名。
    • 例如:
      public class Example {// 错误:方法签名冲突public void print(List<String> list) {}public void print(List<Integer> list) {}  // 编译错误
      }
      
  7. 泛型与异常处理的冲突:

    • 泛型类型参数不能用于 catch 块中的异常类型。
    • 例如:
      public class Example {public <T extends Exception> void handle(T exception) {try {throw exception;} catch (T e) {  // 编译错误// 处理异常}}
      }
      
  8. 泛型与多态性的冲突:

    • 泛型类型参数在继承和多态性中可能会导致一些意外行为。
    • 子类无法重写父类的泛型方法时,可能会导致类型不匹配的问题。
    • 泛型类型参数在继承链中可能会导致类型安全问题。
      class Parent<T> {public void set(T item) {}
      }class Child extends Parent<String> {// 错误:不能重写父类的泛型方法public void set(Object item) {}  // 编译错误
      }
      
  9. 泛型与类型转换的冲突:

    • 由于类型擦除,泛型类型在运行时无法进行类型检查,可能导致 ClassCastException。
    • 如果泛型类型使用不当,可能会导致运行时的类型转换异常。
      List<String> stringList = new ArrayList<>();
      List rawList = stringList;  // 原始类型
      rawList.add(10);  // 编译通过,但运行时会抛出 ClassCastException
      String item = stringList.get(0);  // 抛出 ClassCastException
      
  10. 泛型与通配符的复杂性:

    • 泛型通配符(如 ?、? extends T、? super T)虽然增强了灵活性,但也增加了代码的复杂性。
    • 通配符的使用可能会导致代码难以理解和维护。
    • 通配符的上界和下界可能会导致类型安全问题。
      public void process(List<? extends Number> list) {// 不能添加元素,因为类型未知list.add(10);  // 编译错误
      }
      
3.2 常见的绕过泛型限制的方法

Java泛型的限制主要源于类型擦除和语言设计,虽然无法完全绕过,但可以通过一些常用方法缓解这些限制。

  1. 使用Class传递类型信息
    • 解决问题:类型擦除导致运行时无法获取泛型的具体类型。
    public class Box<T> {private Class<T> type;public Box(Class<T> type) {this.type = type;}public T createInstance() throws Exception {return type.getDeclaredConstructor().newInstance();}
    }
    
  2. 使用反射创建泛型实例
    • 解决问题:无法直接实例化泛型类型(如new T())。
    public <T> T createInstance(Class<T> clazz) throws Exception {return clazz.getDeclaredConstructor().newInstance();
    }
    
  3. 使用Object[]并进行类型转换
    • 解决问题:无法直接创建泛型数组(如new T[10])。
    @SuppressWarnings("unchecked")
    public <T> T[] createArray(Class<T> clazz, int size) {return (T[]) java.lang.reflect.Array.newInstance(clazz, size);
    }
    
  4. 使用带界限的通配符(如<? super T>)来允许添加元素
    • 解决问题:通配符集合(如List<?>)不能添加元素。
    public void addNumber(List<? super Integer> list) {list.add(42); // 允许添加
    }
    

四、桥接方法

4.1 桥接方法的作用

Java编译器会自动生成一个桥接方法,用于在类型擦除后保持方法重写的正确性。桥接方法的作用是将泛型类型参数的具体类型转换为擦除后的类型,并调用子类的具体实现。

4.2 桥接方法的实现
  1. 桥接方法的生成
    • 编译器会为子类生成一个桥接方法,并调用子类的具体实现。
      class Child extends Parent<String> {// 子类的具体实现@Overridepublic void set(String item) {System.out.println("Child set: " + item);}// 编译器生成的桥接方法public void set(Object item) {set((String) item);  // 调用子类的具体实现}
      }
      
    • 这个桥接方法的作用是:
      • 将 Object 类型的参数强制转换为 String 类型。
      • 调用子类的 set(String item) 方法。
    • 通过这种方式,桥接方法确保了 Child 类的 set 方法能够正确重写 Parent 类的 set 方法。
  2. 桥接方法的字节码
    • 查看 Child 类的字节码,可以看到桥接方法的存在:
      // 子类的具体实现
      public void set(java.lang.String);// 编译器生成的桥接方法
      public void set(java.lang.Object);
      
      桥接方法的字节码如下:
      public void set(java.lang.Object);descriptor: (Ljava/lang/Object;)Vflags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETICCode:aload_0aload_1checkcast     #2  // 强制转换为 Stringinvokevirtual #3  // 调用子类的 set(String) 方法return
      
      • ACC_BRIDGE 标志表示这是一个桥接方法。
      • ACC_SYNTHETIC 标志表示这个方法是由编译器生成的,不会出现在源代码中。
4.3 桥接方法的典型场景
  1. 泛型方法的重写
    • 桥接方法主要用于泛型方法的重写场景。例如:
      class Parent<T> {public T get() {return null;}
      }class Child extends Parent<String> {@Overridepublic String get() {return "Hello";}
      }
      
      在编译后,Parent 类的 get 方法会被擦除为:
      public Object get() {return null;
      }
      
      而 Child 类的 get 方法仍然是:
      public String get() {return "Hello";
      }
      
      为了确保 Child 类的 get 方法能够正确重写 Parent 类的 get 方法,编译器会生成一个桥接方法:
      class Child extends Parent<String> {@Overridepublic String get() {return "Hello";}// 编译器生成的桥接方法public Object get() {return get();  // 调用子类的具体实现}
      }
      
  2. 泛型接口的实现
    • 桥接方法也用于泛型接口的实现。例如:
      interface MyInterface<T> {void set(T item);
      }class MyClass implements MyInterface<String> {@Overridepublic void set(String item) {System.out.println("MyClass set: " + item);}
      }
      
      在编译后,MyInterface 的 set 方法会被擦除为:
      void set(Object item);
      
      而 MyClass 的 set 方法仍然是:
      public void set(String item) {System.out.println("MyClass set: " + item);
      }
      
      为了确保 MyClass 的 set 方法能够正确实现 MyInterface 的 set 方法,编译器会生成一个桥接方法:
      class MyClass implements MyInterface<String> {@Overridepublic void set(String item) {System.out.println("MyClass set: " + item);}// 编译器生成的桥接方法public void set(Object item) {set((String) item);  // 调用子类的具体实现}
      }
      

五、在泛型为String的List中存放Integer对象

在Java中,泛型提供了编译时的类型安全检查,确保在泛型集合中只能存储指定类型的对象。例如,一个声明为 List 的集合只能存储 String 类型的对象。如果尝试将 Integer 对象存入 List 中,编译器会直接报错。

List<String> stringList = new ArrayList<>();
stringList.add("Hello");  // 正常
stringList.add(100);      // 编译错误:无法将 Integer 添加到 List<String>

然而,在某些情况下,可以通过一些特殊手段绕过编译器的类型检查,将不兼容类型的对象存入泛型集合中。绕过方法有如下:

  1. 通过原始类型绕过类型检查
    • 通过将泛型集合转换为原始类型,可以绕过编译器的类型检查,从而存入不兼容类型的对象。
      List<String> stringList = new ArrayList<>();
      List rawList = stringList;  // 转换为原始类型
      rawList.add(100);           // 绕过类型检查,存入 Integer 对象System.out.println(stringList);  // 输出: [100]
      
  2. 通过反射绕过类型检查
    • Java的反射机制可以在运行时动态操作对象,包括绕过泛型的类型检查。通过反射,可以直接向泛型集合中存入不兼容类型的对象。
      import java.lang.reflect.Method;
      import java.util.ArrayList;
      import java.util.List;public class Main {public static void main(String[] args) throws Exception {List<String> stringList = new ArrayList<>();// 获取 List 的 add 方法Method addMethod = stringList.getClass().getMethod("add", Object.class);// 使用反射调用 add 方法,存入 Integer 对象addMethod.invoke(stringList, 100);System.out.println(stringList);  // 输出: [100]}
      }
      

虽然可以绕过了编译器的类型检查,但如果尝试从 List 中获取元素并强制转换为 String,会抛出异常。

String item = stringList.get(0);  // 抛出 ClassCastException

正确的做法可以使用下面方法:

  • 使用 List:
    List<Object> list = new ArrayList<>();
    list.add("Hello");
    list.add(100);
    
  • 使用泛型通配符:
    List<?> list = new ArrayList<>();
    list.add("Hello");  // 编译错误,但可以通过其他方式实现
    

六、泛型的PECS原则

PECS 是 Java 泛型中的一个重要原则,全称为 Producer Extends, Consumer Super。它是用来指导在使用泛型通配符(? extends T 和 ? super T)时的最佳实践。PECS 原则的核心思想是:

  • Producer Extends:如果泛型集合是数据的生产者(即从中读取数据),使用 ? extends T。
  • Consumer Super:如果泛型集合是数据的消费者(即向其中写入数据),使用 ? super T。

PECS 原则的目的是在保证类型安全的同时,最大化代码的灵活性和通用性。

6.1 Producer Extends
  1. 含义
    • Producer 是指数据的生产者,即从集合中读取数据。
    • Extends 表示使用 ? extends T,即集合中的元素类型是 T 或其子类。
  2. 使用场景
    • 当我们需要从一个泛型集合中读取数据时,应该使用 ? extends T。这样可以确保从集合中读取的元素是 T 类型或其子类型。
      public void printNumbers(List<? extends Number> numbers) {for (Number number : numbers) {System.out.println(number);}
      }
      
    • List<? extends Number> 表示 numbers 是一个可以存储 Number 或其子类(如 Integer、Double)的集合。
    • 我们可以安全地从 numbers 中读取元素,因为所有元素都是 Number 类型或其子类型。
  3. 限制
    • 由于 ? extends T 表示类型参数是 T 或其子类,因此无法向集合中添加元素(除了 null),因为编译器无法确定具体的类型。
      List<? extends Number> numbers = new ArrayList<Integer>();
      numbers.add(10);  // 编译错误:无法添加元素
      numbers.add(null); // 允许,因为 null 是所有类型的有效值
      
6.2 Consumer Super
  1. 含义
    • Consumer 是指数据的消费者,即向集合中写入数据。
    • Super 表示使用 ? super T,即集合中的元素类型是 T 或其父类。
  2. 使用场景
    • 当我们需要向一个泛型集合中写入数据时,应该使用 ? super T。这样可以确保向集合中添加的元素是 T 类型或其父类型。
      public void addNumbers(List<? super Integer> numbers) {numbers.add(1);numbers.add(2);
      }
      
    • List<? super Integer> 表示 numbers 是一个可以存储 Integer 或其父类(如 Number、Object)的集合。
    • 我们可以安全地向 numbers 中添加 Integer 类型的元素,因为 Integer 是 ? super Integer 的子类型。
  3. 限制
    • 由于 ? super T 表示类型参数是 T 或其父类,因此无法从集合中读取元素(除了 Object),因为编译器无法确定具体的类型。
      List<? super Integer> numbers = new ArrayList<Number>();
      numbers.add(10);  // 允许
      Integer number = numbers.get(0);  // 编译错误:无法读取元素
      Object obj = numbers.get(0);  // 允许,因为所有类型都是 Object 的子类
      
6.3 PECS 原则的综合应用
  1. 集合的复制

    • 将一个集合中的元素复制到另一个集合中:
      public static <T> void copy(List<? super T> dest, List<? extends T> src) {for (T item : src) {dest.add(item);}
      }
      
    • src 是数据的生产者,因此使用 ? extends T。
    • dest 是数据的消费者,因此使用 ? super T。
  2. 集合的最大值

    • 查找集合中的最大值:
      public static <T extends Comparable<? super T>> T max(List<? extends T> list) {if (list.isEmpty()) {throw new IllegalArgumentException("List is empty");}T max = list.get(0);for (T item : list) {if (item.compareTo(max) > 0) {max = item;}}return max;
      }
      
    • List<? extends T> 用于读取数据。
    • Comparable<? super T> 确保 T 或其父类实现了 Comparable 接口。
http://www.dtcms.com/wzjs/122844.html

相关文章:

  • 公司没注册可以做网站吗seo网络优化
  • 网站建设考评办法企业网站建设推广
  • 万网公司注册网站网站建设流程
  • wordpress css 页面标题seo搜索引擎优化步骤
  • 宁波建站推广技术公司今日油价最新
  • 深圳品牌内衣t台秀seo广告投放是什么意思
  • 做一个wordpress模板下载地址seo网站建站
  • 纺织网站制作123纺织网网站提交入口链接
  • window7用jsp做的网站要什么工具网络营销优化
  • 找网站建设公司哪家最好网站seo什么意思
  • 网站建设功能清单最近三天的新闻大事国内
  • 动画型网站全网营销与seo
  • 做网站的应该怎么发广告怎么做网站平台
  • wordpress获取tag名称阜平网站seo
  • 是不是做推广都得有网站百度网址怎么输入?
  • 企业网站 梦织浏览器谷歌手机版下载
  • 菏泽做网站建设找哪家好网站链接推广工具
  • 简历制作专业模板通州优化公司
  • cm域名做网站市场营销策略
  • 那个软件可以做三个视频网站奶茶店推广软文500字
  • 武汉网站建设sz xhg今日头条十大新闻最新
  • 网站开发周期和进度管理seo兼职接单平台
  • 唐山网站建设冀icp备百度最新版下载
  • 网站建设项目设计的图片广州网络推广
  • 南通网站建设找哪家好十五种常见的销售策略
  • dede自适应网站模板域名地址查询
  • 网站标识描述可以填关键词吗品牌推广宣传词
  • 网站后台上传图片做难吗最彻底的手机优化软件
  • 做网站体会chrome官网下载
  • 网站建设的最新技术seo点击优化