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

北镇做网站深圳百度seo哪家好

北镇做网站,深圳百度seo哪家好,国外电商网站如何建立,食品检测公司Java 基础 - 深入理解泛型机制 背景#xff1a;Java泛型这个特性是从JDK 1.5才开始加入的#xff0c;为了兼容之前的版本#xff0c;Java泛型的实现采取了“伪泛型”的策略#xff0c;即Java在语法上支持泛型#xff0c;但是在编译阶段会进行所谓的“类型擦除”#xff0…Java 基础 - 深入理解泛型机制 背景Java泛型这个特性是从JDK 1.5才开始加入的为了兼容之前的版本Java泛型的实现采取了“伪泛型”的策略即Java在语法上支持泛型但是在编译阶段会进行所谓的“类型擦除”Type Erasure将所有的泛型表示尖括号中的内容都替换为具体的类型其对应的原生态类型就像完全没有泛型一样。本文是Java基础知识第4讲总结了Java 泛型的相关知识希望可以提升你对Java中泛型的认知效率。 文章目录 Java 基础 - 深入理解泛型机制1、为什么会引入泛型2、泛型的基本使用2.1、泛型类2.2、泛型接口2.3、泛型方法2.4、泛型标记和泛型限定E、T、K、V、N、?1对泛型上限的限定? extends T2对泛型下限的限定? super T3、小结 2.5、泛型数组 3、深入理解泛型3.1、如何理解Java中的泛型是伪泛型泛型中类型擦除3.2、如何证明类型的擦除呢3.3、如何理解类型擦除后保留的原始类型3.4、如何理解泛型的编译期检查3.5、如何理解泛型的多态泛型的桥接方法3.6、泛型数组能不能采用具体的泛型类型进行初始化3.7、泛型数组如何正确的初始化泛型数组实例3.8、如何理解泛型类中的静态方法和静态变量3.9、如何理解异常中使用泛型3.10、如何获取泛型的参数类型 4、参考文章问题1如何理解基本类型不能作为泛型类型问题2如何理解泛型类型不能实例化 1、为什么会引入泛型 泛型的本质是为了参数化类型在不创建新的类型的情况下通过泛型指定的不同类型来控制形参具体限制的类型。也就是说在泛型使用过程中操作的数据类型被指定为一个参数这种参数类型可以用在类、接口和方法中分别被称为泛型类、泛型接口、泛型方法。 引入泛型的意义在于 适用于多种数据类型执行相同的代码代码复用 我们通过一个例子来阐述 private static int add(int a, int b) {System.out.println(a b (a b));return a b; }private static float add(float a, float b) {System.out.println(a b (a b));return a b; }private static double add(double a, double b) {System.out.println(a b (a b));return a b; }如果没有泛型要实现不同类型的加法每种类型都需要重载一个add方法通过泛型我们可以复用为一个方法 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 list new ArrayList(); list.add(xxString); list.add(100d); list.add(new Person());我们在使用上述list中list中的元素都是Object类型无法约束其中的类型所以在取出集合元素时需要人为的强制类型转化到具体的目标类型且很容易出现java.lang.ClassCastException异常 引入泛型它将提供类型的约束提供编译前的检查 ListString list new ArrayListString();// list中只能放String, 不能放其它类型的元素2、泛型的基本使用 泛型有三种使用方式分别为泛型类、泛型接口、泛型方法 2.1、泛型类 泛型类指在定义类时在类上定义了泛型以便类在使用时可以根据传入的不同参数类型实例化不同的对象。 从一个简单的泛型类看起 // 此处可以随便写标识符号T是type的简称 class PointT{// var的类型由T指定即由外部指定private T var;public T getVar(){ // 返回值的类型由外部决定 return var ;}// 设置的类型也由外部决定public void setVar(T var){this.var var ;} } public class GenericsDemo06{public static void main(String args[]){PointString p new PointString() ; // 里面的var类型为String类型 p.setVar(it) ; // 设置字符串 System.out.println(p.getVar().length()) ; // 取得字符串的长度 } }商品中心对泛型类的使用 场景1封装业务执行结果 public class ApplyResultDTOT implements Serializable {private Long itemId;private String itemName;private Boolean success;private T result;private String error; }场景2封装厂家商品sku信息 public class FactoryItemSkuInfoDTOT extends SkuInnerDTO implements Serializable {private ListT skus;public FactoryItemSkuInfoDTO(ListT skus) {...} }多元泛型 class NotepadK,V{ // 此处指定了两个泛型类型 private K key ; // 此变量的类型由外部决定 private V value ; // 此变量的类型由外部决定 public K getKey(){ return this.key ; } public V getValue(){ return this.value ; } public void setKey(K key){ this.key key ; } public void setValue(V value){ this.value value ; } } public class GenericsDemo09{ public static void main(String args[]){ NotepadString,Integer t null ; // 定义两个泛型类型的对象 t new NotepadString,Integer() ; // 里面的key为Stringvalue为Integer t.setKey(汤姆) ; // 设置第一个内容 t.setValue(20) ; // 设置第二个内容 System.out.print(姓名 t.getKey()) ; // 取得信息 System.out.print(年龄 t.getValue()) ; // 取得信息 } }商品中心对多元泛型的使用 封装K/V键值对 public class KeyValueK, V implements Serializable {private K key;private V value; }在工具类中的使用 public static K, V, T MapK, V load(NonNull CollectionT params, NonNull FunctionCollectionT, MapK, V function, NonNull MapK, V cacheMap, NonNull FunctionT, K paramToKeyFunction) {Objects.requireNonNull(params, params 不能为空);Objects.requireNonNull(function, function 不能为空);Objects.requireNonNull(cacheMap, cacheMap 不能为空);Objects.requireNonNull(paramToKeyFunction, paramToKeyFunction 不能为空);if (CollectionUtils.isEmpty(params)) {return new HashMap();}ListT toLoadIds params.stream().filter(id - !cacheMap.containsKey(paramToKeyFunction.apply(id))).collect(Collectors.toList());if (!toLoadIds.isEmpty()) {Optional.ofNullable(function.apply(toLoadIds)).ifPresent(t - t.entrySet().forEach(e - cacheMap.put(e.getKey(), e.getValue())));}return params.stream().map(paramToKeyFunction::apply).filter(cacheMap::containsKey).collect(Collectors.toMap(id - id, cacheMap::get, (a, b) - a)); }泛型类的具体使用方法是在类的名称后面添加一个或多个类型参数的声明部分在多个泛型参数之间用逗号隔开。具体用法如下 比如我们对数据的操作如果我们有Person、Department、Device三个实体每个实体都对应数据库中的一张表每个实体都有增删改查方法这些方法基本都是通用的因此我们可以抽取出一个BaseDao T里面提供CRUD方法这样我们操作谁只需要将我之前提到的三个类作为泛型值传递进去就OK了被广泛应用Mybatis Plus的思想具体可以参考这篇文章todo Demo1在模板模式代码里面定义模板时使用泛型 在以下代码中通过public class AbstractItemWriteServiceT extends BaseInput 定义了一个泛型类可根据不同的需求参数化不同的类型参数化类型指编译器可以自动定制作用于特定类型的类 // 抽象父类 public abstract class AbstractItemWriteServiceT extends BaseInput {private static final int PAGE_NO 1;private static final int PAGE_SIZE 100;// 定义通用模板方法public abstract Object itemWriteTemplate(T param); }// 子类1 实现类 public class UpdateItemStatusByDistrictCodeImpl extends AbstractItemWriteServiceItemStatusParam {public final OpenResponseListDistrictItemDto itemWriteTemplate(ItemStatusParam param) {OpenItemWriteContext itemWriteContext new OpenItemWriteContext(); itemWriteContext.setParamType(ItemWriteBizTypeEnum.UPDATE_ITEM_STATUS);// 1、前置处理 todo 去掉返回值preHandleItemWrite(param, itemWriteContext);// 2、业务逻辑return handlerItemWrite(param, itemWriteContext);} } // 子类2 实现类2.2、泛型接口 泛型接口的声明和泛型类的声明类似通过在接口名后面添加类型参数的声明部分来实现。泛型接口的具体类型一般在实现类中进行声明不同类型的实现类处理不同的业务逻辑。 简单的泛型接口 interface InfoT{ // 在接口上定义泛型 public T getVar() ; // 定义抽象方法抽象方法的返回值就是泛型类型 } class InfoImplT implements InfoT{ // 定义泛型接口的子类 private T var ; // 定义属性 public InfoImpl(T var){ // 通过构造方法设置属性内容 this.setVar(var) ; } public void setVar(T var){ this.var var ; } public T getVar(){ return this.var ; } } public class GenericsDemo24{ public static void main(String arsg[]){ InfoString i null; // 声明接口对象 i new InfoImplString(汤姆) ; // 通过子类实例化对象 System.out.println(内容 i.getVar()) ; } }商品中心对泛型接口的使用 场景1商品审核策略类 public interface AuditStrategyT {T agree(AuditAgreeContext context);T submit(SubmitAuditContext param); }场景2商品扩展属性转换器 有四个实现类不同类型的实现类处理不同的业务逻辑。 public interface ExtAttrConvertorT {ExtAttrConvertorString STRING new StringExtAttrConvertor();ExtAttrConvertorBigDecimal BIG_DECIMAL new BigDecimalExtAttrConvertor();ExtAttrConvertorLong LONG new LongExtAttrConvertor();ExtAttrConvertorBoolean BOOLEAN new BooleanExtAttrConvertor();/*** 序列化成String* param obj* return*/String toStrVal(T obj);/*** 反序列化成obj* param strVal* return*/T fromStrVal(String strVal);}// String类型public class StringExtAttrConvertor implements ExtAttrConvertorString {Overridepublic String toStrVal(String obj) {return obj;}Overridepublic String fromStrVal(String strVal) {return strVal;}}2.3、泛型方法 泛型方法是在调用方法的时候指明泛型的具体类型。重点看下泛型的方法 定义泛型方法语法格式 调用泛型方法语法格式 说明一下定义泛型方法时必须在返回值前边加一个T来声明这是一个泛型方法持有一个泛型T然后才可以用泛型T作为方法的返回值。 ClassT的作用就是指明泛型的具体类型而ClassT类型的变量c可以用来创建泛型类的对象。 为什么要用变量c来创建对象呢 既然是泛型方法就代表着我们不知道具体的类型是什么也不知道构造方法如何因此没有办法去new一个对象但可以利用变量c 的newInstance方法去创建对象也就是利用反射创建对象。 泛型方法要求的参数是ClassT类型而Class.forName()方法的返回值也是ClassT因此可以用Class.forName()作为参数。其中forName()方法中的参数是何种类型返回的ClassT就是何种类型。在本例中forName()方法中传入的是User类的完整路径因此返回的是ClassUser类型的对象因此调用泛型方法时变量c的类型就是ClassUser因此泛型方法中的泛型T就被指明为User因此变量obj的类型为User。 当然泛型方法不是仅仅可以有一个参数ClassT可以根据需要添加其他参数。 为什么要使用泛型方法呢 因为泛型类要在实例化的时候就指明类型如果想换一种类型不得不重新new一次可能不够灵活而泛型方法可以在调用的时候指明类型更加灵活。 商品中心对泛型方法的使用 场景1获取快照信息 public T T getSnapshotBean(ClassT clazz) {try {if (Strings.isNullOrEmpty(this.content)) {return null;}return objectMapper.readValue(this.content, clazz);} catch (Exception e) {log.warn(parse json to object failed,class:{},json:{},cause:{}, clazz, this.content, e);}return null; } // 不在范围内的值将用修正值 amendValue private T T amendByRange(T testValue, T min, T max, T amendValue, ComparatorT comparator) {if (testValue null) {return amendValue;}if (comparator.compare(testValue, min) 0 || comparator.compare(testValue, max) 0) {return amendValue;}return testValue; }各种工具类获取apollo配置商品中心重写 public T T getProperty(String key, FunctionString, T function, T defaultValue) {try {String value getProperty(key, null);if (value ! null) {return function.apply(value);}} catch (Throwable ex) {Tracer.logError(new ApolloConfigException(String.format(getProperty for %s failed, return default value %s, key, defaultValue), ex));}return defaultValue; }去重工具类 public static T PredicateT distinct(Function? super T, Object keyExtractor) {MapObject, Boolean context new ConcurrentHashMap();return t - ObjectUtils.isEmpty(context.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE)); }2.4、泛型标记和泛型限定E、T、K、V、N、? 在使用泛型前首先要了解有哪些泛型标记 类型通配符使用“? ”表示所有具体的参数类型例如List? 在逻辑上是List、List等所有List具体类型实参的父类。在使用泛型的时候若希望将类的继承关系加入泛型应用中就需要对泛型做限定具体的泛型限定有对泛型上线的限定和对泛型下线的限定 是为了解决泛型中隐含的转换问题Java泛型加入了类型参数的上下边界机制。 1对泛型上限的限定? extends T 在Java中使用通配符“? ”和“extends”关键字指定泛型的上限具体用法为? extends T它表示该通配符所代表的类型是T类的子类或者接口T的子接口。 上限 class InfoT 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 demo1{public static void main(String args[]){InfoInteger i1 new InfoInteger() ; // 声明Integer的泛型对象} }2对泛型下限的限定? super T 在Java中使用通配符“? ”和“super”关键字指定泛型的下限具体用法为? super T它表示该通配符所代表的类型是T类型的父类或者父接口。 下限 class InfoT{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 GenericsDemo21{public static void main(String args[]){InfoString i1 new InfoString() ; // 声明String的泛型对象InfoObject i2 new InfoObject() ; // 声明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 , ) ;} }3、小结 ? 无限制通配符 ? extends E extends 关键字声明了类型的上界表示参数化的类型可能是所指定的类型或者是此类型的子类 ? super E super 关键字声明了类型的下界表示参数化的类型可能是指定的类型或者是此类型的父类 // 使用原则《Effictive Java》 // 为了获得最大限度的灵活性要在表示 生产者或者消费者 的输入参数上使用通配符使用的规则就是生产者有上限、消费者有下限 如果参数化类型表示一个 T 的生产者使用 ? extends T; 如果它表示一个 T 的消费者就使用 ? super T 如果既是生产又是消费那使用通配符就没什么意义了因为你需要的是精确的参数类型。 再看一个实际例子加深印象 private E extends Comparable? super E E max(List? extends E e1) {if (e1 null){return null;}//迭代器返回的元素属于 E 的某个子类型Iterator? extends E iterator e1.iterator();E result iterator.next();while (iterator.hasNext()){E next iterator.next();if (next.compareTo(result) 0){result next;}}return result; }上述代码中的类型参数 E 的范围是E extends Comparable? super E我们可以分步查看 要进行比较所以 E 需要是可比较的类因此需要 extends Comparable…注意这里不要和继承的 extends 搞混了不一样Comparable ? super E 要对 E 进行比较即 E 的消费者所以需要用 super而参数 List ? extends E 表示要操作的数据是 E 的子类的列表指定上限这样容器才够大多个限制 使用符号 public class Client {//工资低于2500元的上斑族并且站立的乘客车票打8折public static T extends Staff Passenger void discount(T t){if(t.getSalary()2500 t.isStanding()){System.out.println(恭喜你您的车票打八折);}}public static void main(String[] args) {discount(new Me());}}2.5、泛型数组 首先我们泛型数组相关的申明 ListString[] list11 new ArrayListString[10]; //编译错误非法创建 ListString[] list12 new ArrayList?[10]; //编译错误需要强转类型 ListString[] list13 (ListString[]) new ArrayList?[10]; //OK但是会有警告 List?[] list14 new ArrayListString[10]; //编译错误非法创建 List?[] list15 new ArrayList?[10]; //OK ListString[] list6 new ArrayList[10]; //OK但是会有警告那么通常我们如何用呢 讨巧的使用场景 public class GenericsDemo30{ public static void main(String args[]){ Integer i[] fun1(1,2,3,4,5,6) ; // 返回泛型数组 fun2(i) ; } public static T T[] fun1(T...arg){ // 接收可变参数 return arg ; // 返回泛型数组 } public static T void fun2(T param[]){ // 输出 System.out.print(接收泛型数组) ; for(T t:param){ System.out.print(t 、) ; } } }合理使用 public ArrayWithTypeToken(ClassT type, int size) {array (T[]) Array.newInstance(type, size); }商品中心没有使用 3、深入理解泛型 我们通过泛型背后的类型擦除以及相关的问题来进一步理解泛型。 3.1、如何理解Java中的泛型是伪泛型泛型中类型擦除 泛型中类型擦除 Java泛型这个特性是从JDK 1.5才开始加入的因此为了兼容之前的版本Java泛型的实现采取了“伪泛型”的策略即Java在语法上支持泛型但是在编译阶段会进行所谓的“类型擦除”Type Erasure将所有的泛型表示尖括号中的内容都替换为具体的类型其对应的原生态类型就像完全没有泛型一样。 在编码阶段采用泛型时加上的类型参数会被编译器在编译时去掉这个过程就被称为类型擦除。因此泛型主要用于编译阶段。在编译后生成的Java字节代码文件中不包含泛型中的类型信息。例如编码时定义的List和List在经过编译后统一为List。JVM所读取的只是List由泛型附加的类型信息对JVM来说是不可见的。 Java类型的擦除过程为首先查找用来替换类型参数的具体类该具体类一般为Object如果指定了类型参数的上界则以该上界作为替换时的具体类然后把代码中的类型参数都替换为具体的类。 泛型的类型擦除原则是 消除类型参数声明即删除及其包围的部分。根据类型参数的上下界推断并替换所有的类型参数为原生态类型如果类型参数是无限制通配符或没有上下界限定则替换为Object如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型即父类。为了保证类型安全必要时插入强制类型转换代码。自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”。 擦除类定义中的类型参数 - 无限制类型擦除 当类定义中的类型参数没有任何限制时在类型擦除中直接被替换为Object即形如T和?的类型参数都被替换为Object。 擦除类定义中的类型参数 - 有限制类型擦除 当类定义中的类型参数存在限制上下界时在类型擦除中替换为类型参数的上界或者下界比如形如T extends Number和? extends Number的类型参数被替换为Number? super Number被替换为Object。 擦除方法定义中的类型参数 擦除方法定义中的类型参数原则和擦除类定义中的类型参数是一样的这里仅以擦除方法定义中的有限制类型参数为例。 3.2、如何证明类型的擦除呢 我们通过两个例子证明Java类型的类型擦除 原始类型相等 public class Test {public static void main(String[] args) {ArrayListString list1 new ArrayListString();list1.add(abc);ArrayListInteger list2 new ArrayListInteger();list2.add(123);System.out.println(list1.getClass() list2.getClass()); // true} }在这个例子中我们定义了两个ArrayList数组不过一个是ArrayListString泛型类型的只能存储字符串一个是ArrayListInteger泛型类型的只能存储整数最后我们通过list1对象和list2对象的getClass()方法获取他们的类的信息最后发现结果为true。说明泛型类型String和Integer都被擦除掉了只剩下原始类型。 通过反射添加其它类型元素 public class Test {public static void main(String[] args) throws Exception {ArrayListInteger list new ArrayListInteger();list.add(1); //这样调用 add 方法只能存储整形因为泛型类型的实例为 Integerlist.getClass().getMethod(add, Object.class).invoke(list, asd);for (int i 0; i list.size(); i) {System.out.println(list.get(i));}} }在程序中定义了一个ArrayList泛型类型实例化为Integer对象如果直接调用add()方法那么只能存储整数数据不过当我们利用反射调用add()方法的时候却可以存储字符串这说明了Integer泛型实例在编译之后被擦除掉了只保留了原始类型。 3.3、如何理解类型擦除后保留的原始类型 在上面两次提到了原始类型什么是原始类型 原始类型 就是擦除去了泛型信息最后在字节码中的类型变量的真正类型无论何时定义一个泛型相应的原始类型都会被自动提供类型变量擦除并使用其限定类型无限定的变量用Object替换。 原始类型Object class PairT { private T value; public T getValue() { return value; } public void setValue(T value) { this.value value; } } Pair的原始类型为: class Pair { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value value; } }因为在PairT中T 是一个无限定的类型变量所以用Object替换其结果就是一个普通的类如同泛型加入Java语言之前的已经实现的样子。在程序中可以包含不同类型的Pair如PairString或PairInteger但是擦除类型后他们的就成为原始的Pair类型了原始类型都是Object。 从上面章节我们也可以明白ArrayList被擦除类型后原始类型也变为Object所以通过反射我们就可以存储字符串了。 如果类型变量有限定那么原始类型就用第一个边界的类型变量类替换。 比如: Pair这样声明的话 public class PairT extends Comparable {}那么原始类型就是Comparable。 要区分原始类型和泛型变量的类型。 在调用泛型方法时可以指定泛型也可以不指定泛型: 在不指定泛型的情况下泛型变量的类型为该方法中的几种类型的同一父类的最小级直到Object 在指定泛型的情况下该方法的几种类型必须是该泛型的实例的类型或者其子类 public class Test { public static void main(String[] args) { /**不指定泛型的时候*/ int i Test.add(1, 2); //这两个参数都是Integer所以T为Integer类型 Number f Test.add(1, 1.2); //这两个参数一个是Integer一个是Float所以取同一父类的最小级为Number Object o Test.add(1, asd); //这两个参数一个是Integer一个是String所以取同一父类的最小级为Object /**指定泛型的时候*/ int a Test.Integeradd(1, 2); //指定了Integer所以只能为Integer类型或者其子类 int b Test.Integeradd(1, 2.2); //编译错误指定了Integer不能为Float Number c Test.Numberadd(1, 2.2); //指定为Number所以可以为Integer和Float } //这是一个简单的泛型方法 public static T T add(T x,T y){ return y; } }其实在泛型类中不指定泛型的时候也差不多只不过这个时候的泛型为Object就比如ArrayList中如果不指定泛型那么这个ArrayList可以存储任意的对象。 Object泛型 public static void main(String[] args) { ArrayList list new ArrayList(); list.add(1); list.add(121); list.add(new Date()); } 3.4、如何理解泛型的编译期检查 既然说类型变量会在编译的时候擦除掉那为什么我们往 ArrayList 创建的对象中添加整数会报错呢不是说泛型变量String会在编译的时候变为Object类型吗为什么不能存别的类型呢既然类型擦除了如何保证我们只能使用泛型变量限定的类型呢 Java编译器是通过先检查代码中泛型的类型然后在进行类型擦除再进行编译。 例如 public static void main(String[] args) { ArrayListString list new ArrayListString(); list.add(123); list.add(123);//编译错误 }在上面的程序中使用add方法添加一个整型在IDE中直接会报错说明这就是在编译之前的检查因为如果是在编译之后检查类型擦除后原始类型为Object是应该允许任意引用类型添加的。可实际上却不是这样的这恰恰说明了关于泛型变量的使用是会在编译之前检查的。 那么这个类型检查是针对谁的呢我们先看看参数化类型和原始类型的兼容。 以 ArrayList举例子以前的写法: ArrayList list new ArrayList(); 现在的写法: ArrayListString list new ArrayListString();如果是与以前的代码兼容各种引用传值之间必然会出现如下的情况 ArrayListString list1 new ArrayList(); //第一种 情况 推荐使用 ArrayList list2 new ArrayListString(); //第二种 情况这样是没有错误的不过会有个编译时警告。 不过在第一种情况可以实现与完全使用泛型参数一样的效果第二种则没有效果。 因为类型检查就是编译时完成的new ArrayList() 只是在内存中开辟了一个存储空间可以存储任何类型对象而真正涉及类型检查的是它的引用因为我们是使用它引用list1来调用它的方法比如说调用add方法所以list1引用能完成泛型类型的检查。而引用list2没有使用泛型所以不行。 例如 public class Test {public static void main(String[] args) {ArrayListString list1 new ArrayList();list1.add(1); //编译通过list1.add(1); //编译错误String str1 list1.get(0); //返回类型就是StringArrayList list2 new ArrayListString();list2.add(1); //编译通过list2.add(1); //编译通过Object object list2.get(0); //返回类型就是Objectnew ArrayListString().add(11); //编译通过new ArrayListString().add(22); //编译错误String str2 new ArrayListString().get(0); //返回类型就是String} } 通过上面的例子我们可以明白类型检查就是针对引用的谁是一个引用用这个引用调用泛型方法就会对这个引用调用的方法进行类型检测而无关它真正引用的对象。 泛型中参数话类型为什么不考虑继承关系 在Java中像下面形式的引用传递是不允许的: ArrayListString list1 new ArrayListObject(); //编译错误 ArrayListObject list2 new ArrayListString(); //编译错误我们先看第一种情况将第一种情况拓展成下面的形式 ArrayListObject list1 new ArrayListObject(); list1.add(new Object()); list1.add(new Object()); ArrayListString list2 list1; //编译错误实际上在第4行代码的时候就会有编译错误。那么我们先假设它编译没错。那么当我们使用list2引用用get()方法取值的时候返回的都是String类型的对象上面提到了类型检测是根据引用来决定的可是它里面实际上已经被我们存放了Object类型的对象这样就会有ClassCastException了。所以为了避免这种极易出现的错误Java不允许进行这样的引用传递。这也是泛型出现的原因就是为了解决类型转换的问题我们不能违背它的初衷。 再看第二种情况将第二种情况拓展成下面的形式 ArrayListString list1 new ArrayListString(); list1.add(new String()); list1.add(new String());ArrayListObject list2 list1; //编译错误没错这样的情况比第一种情况好的多最起码在我们用list2取值的时候不会出现ClassCastException因为是从String转换为Object。可是这样做有什么意义呢泛型出现的原因就是为了解决类型转换的问题。 我们使用了泛型到头来还是要自己强转违背了泛型设计的初衷。所以java不允许这么干。再说你如果又用list2往里面add()新的对象那么到时候取得时候我怎么知道我取出来的到底是String类型的还是Object类型的呢 所以要格外注意泛型中的引用传递的问题。 3.5、如何理解泛型的多态泛型的桥接方法 类型擦除会造成多态的冲突而JVM解决方法就是桥接方法。 现在有这样一个泛型类 class PairT { private T value; public T getValue() { return value; } public void setValue(T value) { this.value value; } }然后我们想要一个子类继承它。 class DateInter extends PairDate { Override public void setValue(Date value) { super.setValue(value); } Override public Date getValue() { return super.getValue(); } }在这个子类中我们设定父类的泛型类型为PairDate在子类中我们覆盖了父类的两个方法我们的原意是这样的将父类的泛型类型限定为Date那么父类里面的两个方法的参数都为Date类型。 public Date getValue() { return value; } public void setValue(Date value) { this.value value; }所以我们在子类中重写这两个方法一点问题也没有实际上从他们的Override标签中也可以看到一点问题也没有实际上是这样的吗 分析实际上类型擦除后父类的的泛型类型全部变为了原始类型Object所以父类编译之后会变成下面的样子 class Pair {private Object value;public Object getValue() {return value; } public void setValue(Object value) { this.value value; } } 再看子类的两个重写的方法的类型 Override public void setValue(Date value) { super.setValue(value); } Override public Date getValue() { return super.getValue(); }先来分析setValue方法父类的类型是Object而子类的类型是Date参数类型不一样这如果实在普通的继承关系中根本就不会是重写而是重载。 我们在一个main方法测试一下 public static void main(String[] args) throws ClassNotFoundException { DateInter dateInter new DateInter(); dateInter.setValue(new Date()); dateInter.setValue(new Object()); //编译错误 }如果是重载那么子类中两个setValue方法一个是参数Object类型一个是Date类型可是我们发现根本就没有这样的一个子类继承自父类的Object类型参数的方法。所以说确实是重写了而不是重载了。 为什么会这样呢 原因是这样的我们传入父类的泛型类型是DatePairDate我们的本意是将泛型类变为如下 class Pair { private Date value; public Date getValue() { return value; } public void setValue(Date value) { this.value value; } }然后再子类中重写参数类型为Date的那两个方法实现继承中的多态。 可是由于种种原因虚拟机并不能将泛型类型变为Date只能将类型擦除掉变为原始类型Object。这样我们的本意是进行重写实现多态。可是类型擦除后只能变为了重载。这样类型擦除就和多态有了冲突。JVM知道你的本意吗知道可是它能直接实现吗不能如果真的不能的话那我们怎么去重写我们想要的Date类型参数的方法啊。 于是JVM采用了一个特殊的方法来完成这项功能那就是桥方法。 首先我们用javap -c className的方式反编译下DateInter子类的字节码结果如下 class com.tao.test.DateInter extends com.tao.test.Pairjava.util.Date { com.tao.test.DateInter(); Code: 0: aload_0 1: invokespecial #8 // Method com/tao/test/Pair.init:()V 4: return public void setValue(java.util.Date); //我们重写的setValue方法 Code: 0: aload_0 1: aload_1 2: invokespecial #16 // Method com/tao/test/Pair.setValue:(Ljava/lang/Object;)V 5: return public java.util.Date getValue(); //我们重写的getValue方法 Code: 0: aload_0 1: invokespecial #23 // Method com/tao/test/Pair.getValue:()Ljava/lang/Object; 4: checkcast #26 // class java/util/Date 7: areturn public java.lang.Object getValue(); //编译时由编译器生成的桥方法 Code: 0: aload_0 1: invokevirtual #28 // Method getValue:()Ljava/util/Date 去调用我们重写的getValue方法; 4: areturn public void setValue(java.lang.Object); //编译时由编译器生成的桥方法 Code: 0: aload_0 1: aload_1 2: checkcast #26 // class java/util/Date 5: invokevirtual #30 // Method setValue:(Ljava/util/Date; 去调用我们重写的setValue方法)V 8: return }从编译的结果来看我们本意重写setValue和getValue方法的子类竟然有4个方法其实不用惊奇最后的两个方法就是编译器自己生成的桥方法。可以看到桥方法的参数类型都是Object也就是说子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法上面的Oveerride只不过是假象。而桥方法的内部实现就只是去调用我们自己重写的那两个方法。 所以虚拟机巧妙的使用了桥方法来解决了类型擦除和多态的冲突。 不过要提到一点这里面的setValue和getValue这两个桥方法的意义又有不同。 setValue方法是为了解决类型擦除与多态之间的冲突。 而getValue却有普遍的意义怎么说呢如果这是一个普通的继承关系 那么父类的getValue方法如下 public Object getValue() { return super.getValue(); }而子类重写的方法是 public Date getValue() { return super.getValue(); }其实这在普通的类继承中也是普遍存在的重写这就是协变。 并且还有一点也许会有疑问子类中的桥方法Object getValue()和Date getValue()是同时存在的可是如果是常规的两个方法他们的方法签名是一样的也就是说虚拟机根本不能分别这两个方法。如果是我们自己编写Java代码这样的代码是无法通过编译器的检查的但是虚拟机却是允许这样做的因为虚拟机通过参数类型和返回类型来确定一个方法所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情然后交给虚拟器去区别。 3.6、泛型数组能不能采用具体的泛型类型进行初始化 我们先来看下Oracle官网提供的一个例子 ListString[] lsa new ListString[10]; // Not really allowed. Object o lsa; Object[] oa (Object[]) o; ListInteger li new ArrayListInteger(); li.add(new Integer(3)); oa[1] li; // Unsound, but passes run time store check String s lsa[1].get(0); // Run-time error ClassCastException.由于 JVM 泛型的擦除机制所以上面代码可以给 oa[1] 赋值为 ArrayList 也不会出现异常但是在取出数据的时候却要做一次类型转换所以就会出现 ClassCastException如果可以进行泛型数组的声明则上面说的这种情况在编译期不会出现任何警告和错误只有在运行时才会出错但是泛型的出现就是为了消灭 ClassCastException所以如果 Java 支持泛型数组初始化操作就是搬起石头砸自己的脚。 而对于下面的代码来说是成立的 List?[] lsa new List?[10]; // OK, array of unbounded wildcard type. Object o lsa; Object[] oa (Object[]) o; ListInteger li new ArrayListInteger(); li.add(new Integer(3)); oa[1] li; // Correct. Integer i (Integer) lsa[1].get(0); // OK所以说采用通配符的方式初始化泛型数组是允许的因为对于通配符的方式最后取出数据是要做显式类型转换的符合预期逻辑。综述就是说Java 的泛型数组初始化时数组类型不能是具体的泛型类型只能是通配符的形式因为具体类型会导致可存入任意类型对象在取出时会发生类型转换异常会与泛型的设计思想冲突而通配符形式本来就需要自己强转符合预期。 Oracle 官方文档 更进一步的我们看看如下的代码 ListString[] list11 new ArrayListString[10]; //编译错误非法创建 ListString[] list12 new ArrayList?[10]; //编译错误需要强转类型 ListString[] list13 (ListString[]) new ArrayList?[10]; //OK但是会有警告 List?[] list14 new ArrayListString[10]; //编译错误非法创建 List?[] list15 new ArrayList?[10]; //OK ListString[] list6 new ArrayList[10]; //OK但是会有警告因为在 Java 中是不能创建一个确切的泛型类型的数组的除非是采用通配符的方式且要做显式类型转换才可以。 3.7、泛型数组如何正确的初始化泛型数组实例 这个无论我们通过new ArrayList[10] 的形式还是通过泛型通配符的形式初始化泛型数组实例都是存在警告的也就是说仅仅语法合格运行时潜在的风险需要我们自己来承担因此那些方式初始化泛型数组都不是最优雅的方式。 我们在使用到泛型数组的场景下应该尽量使用列表集合替换此外也可以通过使用 java.lang.reflect.Array.newInstance(ClassT componentType, int length) 方法来创建一个具有指定类型和维度的数组如下 public class ArrayWithTypeTokenT {private T[] array;public ArrayWithTypeToken(ClassT type, int size) {array (T[]) Array.newInstance(type, size);}public void put(int index, T item) {array[index] item;}public T get(int index) {return array[index];}public T[] create() {return array;} }//... ArrayWithTypeTokenInteger arrayToken new ArrayWithTypeTokenInteger(Integer.class, 100); Integer[] array arrayToken.create();所以使用反射来初始化泛型数组算是优雅实现因为泛型类型 T在运行时才能被确定下来我们能创建泛型数组也必然是在 Java 运行时想办法而运行时能起作用的技术最好的就是反射了。 3.8、如何理解泛型类中的静态方法和静态变量 泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数 举例说明 public class Test2T { public static T one; //编译错误 public static T show(T one){ //编译错误 return null; } }因为泛型类中的泛型参数的实例化是在定义对象的时候指定的而静态变量和静态方法不需要使用对象来调用。对象都没有创建如何确定这个泛型参数是何种类型所以当然是错误的。 但是要注意区分下面的一种情况 public class Test2T { public static T T show(T one){ //这是正确的 return null; } }因为这是一个泛型方法在泛型方法中使用的T是自己在方法中定义的 T而不是泛型类中的T。 3.9、如何理解异常中使用泛型 不能抛出也不能捕获泛型类的对象。事实上泛型类扩展Throwable都不合法。例如下面的定义将不会通过编译 public class ProblemT extends Exception {}为什么不能扩展Throwable因为异常都是在运行时捕获和抛出的而在编译的时候泛型信息全都会被擦除掉那么假设上面的编译可行那么在看下面的定义 try{} catch(ProblemInteger e1) {} catch(ProblemNumber e2) {} 类型信息被擦除后那么两个地方的catch都变为原始类型Object那么也就是说这两个地方的catch变的一模一样,就相当于下面的这样 try{} catch(ProblemObject e1) {} catch(ProblemObject e2) {}这个当然就是不行的。 不能再catch子句中使用泛型变量 public static T extends Throwable void doWork(ClassT t) {try {...} catch(T e) { //编译错误...} }因为泛型信息在编译的时候已经变为原始类型也就是说上面的T会变为原始类型Throwable那么如果可以再catch子句中使用泛型变量那么下面的定义呢 public static T extends Throwable void doWork(ClassT t){try {} catch(T e) { //编译错误} catch(IndexOutOfBounds e) {} }根据异常捕获的原则一定是子类在前面父类在后面那么上面就违背了这个原则。即使你在使用该静态方法的使用T是ArrayIndexOutofBounds在编译之后还是会变成ThrowableArrayIndexOutofBounds是IndexOutofBounds的子类违背了异常捕获的原则。所以java为了避免这样的情况禁止在catch子句中使用泛型变量。 但是在异常声明中可以使用类型变量。下面方法是合法的。 public staticT extends Throwable void doWork(T t) throws T {try{...} catch(Throwable realCause) {t.initCause(realCause);throw t; } }上面的这样使用是没问题的。 3.10、如何获取泛型的参数类型 既然类型被擦除了那么如何获取泛型的参数类型呢可以通过反射java.lang.reflect.Type获取泛型 java.lang.reflect.Type 是Java中所有类型的公共高级接口, 代表了Java中的所有类型. Type体系中类型的包括数组类型(GenericArrayType)、参数化类型(ParameterizedType)、类型变量(TypeVariable)、通配符类型(WildcardType)、原始类型(Class)、基本类型(Class)以上这些类型都实现Type接口。 public class GenericTypeT {private T data;public T getData() {return data;}public void setData(T data) {this.data data;}public static void main(String[] args) {GenericTypeString genericType new GenericTypeString() {};Type superclass genericType.getClass().getGenericSuperclass();// getActualTypeArguments 返回确切的泛型参数, 如MapString, Integer返回[String, Integer]Type type ((ParameterizedType) superclass).getActualTypeArguments()[0]; System.out.println(type);//class java.lang.String} }其中 ParameterizedType: public interface ParameterizedType extends Type {// 返回确切的泛型参数, 如MapString, Integer返回[String, Integer]Type[] getActualTypeArguments();//返回当前class或interface声明的类型, 如List?返回ListType getRawType();//返回所属类型. 如,当前类型为OT.IS, 则返回OT. 顶级类型将返回null Type getOwnerType(); }4、参考文章 泛型作为Java基础知识点之一网上知识点比较多也比较散本文主要综合了网络上比较好的文章方便你快速学习。 Java泛型深入理解泛型的内部原理类型擦除以及类型擦除带来的问题Lesson: GenericsJava 泛型详解Java中的泛型方法Java泛型—类型转换Java泛型详解深入理解 Java 泛型 问题1如何理解基本类型不能作为泛型类型 比如我们没有ArrayListint只有ArrayListInteger, 为何 因为当类型擦除后ArrayList的原始类型变为Object但是Object类型不能存储int值只能引用Integer的值。 另外需要注意我们能够使用list.add(1)是因为Java基础类型的自动装箱拆箱操作。 问题2如何理解泛型类型不能实例化 不能实例化泛型类型这本质上是由于类型擦除决定的 我们可以看到如下代码会在编译器中报错 T test new T(); // ERROR因为在 Java 编译期没法确定泛型参数化类型也就找不到对应的类字节码文件所以自然就不行了此外由于T被擦除为 Object如果可以 new T() 则就变成了 new Object()失去了本意。 如果我们确实需要实例化一个泛型应该如何做呢可以通过反射实现 static T T newTclass (Class T clazz) throws InstantiationException, IllegalAccessException {T obj clazz.newInstance();return obj; }
http://www.hkea.cn/news/14283829/

相关文章:

  • 邢台网站设计厂家本地生活服务平台app
  • 网站备案中国开头怎样注册网站卖东西
  • 池州建行网站樱花大黄云服务器
  • 做网站人家直接百度能搜到的石家庄设计公司
  • 东莞互联网营销网站建设建个网站需要投资多少钱
  • 融资融券配资网站开发什么网站程序好
  • 闭站保护期间网站能够打开吗谷歌网站站长指南
  • 上海网站建设lv cn三亚政策最新消息
  • 网站title怎么修改公众号微网站制作
  • 免费自助建站免费的网站认证
  • 给人做网站网站平台优化是什么意思
  • 合肥营销型网站建设开发网站开发工具排行
  • 南京网站建设王道下拉??网站服务费可以做宣传费吗
  • 不是网站可以用云主机吗企业推广文案
  • 手机网站 等比缩放app编程入门教程
  • steamcn网站是谁做的wordpress首页修改路径
  • 原阳网站建设wordpress装多站点
  • 在服务器上布网站怎么做的模板网站开发推广
  • 网上书城网站开发的结论和不足化工原料东莞网站建设
  • 金融 网站 模板ih5 做视频网站
  • 设计素材网站特点深圳建筑协会
  • 电子商务静态网站建设实验报告免费推广的平台都有哪些
  • 网站哪家做的好中细软做的网站
  • 专注旅游网站网站开发wordpress saml
  • 西安有哪些家做网站的公司如何创建一个网站链接
  • php网站开发工作描述啤酒免费代理0元铺货
  • 安溪城乡建设局网站买链接
  • 广州微信网站制作建设网站需要会什么
  • 泰安集团网站建设方案网站关停怎么做
  • 吴江区经济开发区建设工程网站网站 宕机 优化