Java泛型主题讨论

说明:在学习泛型这一知识点中,主要参考自《疯狂Java讲义》第7章P307-P330的泛型内容,因为是跳着阅读,所以前面的一些名词不是特别清楚,这里也做出适当备注,供自己识记与理解。

1.泛型

理解:说到泛型,感觉最初是为了解决Java集合的一个缺点——当我们想要把一个对象放进集合里面的时候,集合就会忘记这个对象的数据类型,再次把它取出来时,它的编译类型就会变成Object类型(运行类型不会变)。记住我们的目标是:在集合里面存储不会被忘记数据类型的各种对象。例子:

 1 package FanXing;
 2 
 3 public class ListErr {
 4      public static void main(String[] args) {
 5              List strList = new ArrayList();
 6             strList.add("泛型主题讨论");
 7             strList.add(017);//这里不小心把一个Integer对象放在了集合里面,可能报类型强制转换异常ClassCastException
 8            for(int i=0;i<strList.size();i++)
 9             {String str = (String)strList.get(i);
10                 }
11     }
12 }
编译信息:

Exception in thread "main" java.lang.Error: Unresolved compilation problems:
List cannot be resolved to a type
ArrayList cannot be resolved to a type

at FanXing.ListErr.main(ListErr.java:5)

图中的红色框已经提示我们需要用什么区解决所面临的的问题了。

为了达到我们的目标,我们想到了可以手动实现编译时去检查类型。

例子:(既然会发生异常那我们就在运行前先检查,我们这里先创建一个对象List,让它只保存字符串类型,这样就可以扩展ArrayList类)

package FanXing;import java.util.ArrayList;
import java.util.List;class StrList{private List strList = new ArrayList();public boolean add(String ele)//定义StrList的add方法,只添加字符串
    {return strList.add(ele);}public String get (int index){return (String)strList.get(index);}public int size(){return strList.size();}
}public class ListErr {public static void main(String[] args) {StrList strList = new StrList();strList.add("泛型主题讨论");//strList.add(017);如果没有这一句,代码可以成功被编译,否组会报错。
            System.out.println(strList);for(int i=0;i<strList.size();i++){String str = strList.get(i);}}
}

上面的代码中我们定义的StrList类实现了编译时的异常检查,当编译到strList.add(017);时,程序试图将一个Integer对象加入到StrList集合中,程序在这里会无法编译通过,因为StrList只接受String的对象。

不过,既然只接受String对象的时候可以编译通过,说明这个方法还是有用的,但是,这种方法虽然有效,局限性却非常明显——我们需要去定义大量的List子类。

虽然这样也可以实现我们的目标:在集合里面存储不会被忘记数据类型的各种对象。不过这样非常非常麻烦,这个时候,我们的泛型就被设计出来了,有了它,我们的目标可以轻易实现。

package FanXing;import java.util.ArrayList;
import java.util.List;public class ListErr {public static void main(String[] args) {List<String> strList = new ArrayList<String>();//创建一个List集合,只保留字符串strList.add("泛型主题讨论");for(int i = 0;i<strList.size();i++){String str = strList.get(i);}}
}

很显然这样代码简化了很多,List<String>说明这是一个String类型的List。

所以这里我们可以归纳出,如果List<>尖括号里面是其他类型的话也是同理,即有了一个JDK1.5以后引入的概念:

Java泛型(generics)【Java的参数化类型】 :是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。泛型最主要的应用是在JDK 5中的新集合类框架中。

泛型最大的好处是可以提高代码的复用性。以List接口为例,我们可以将String、Integer等类型放入List中,如不用泛型,存放String类型要写一个List接口,存放Integer要写另外一个List接口,泛型可以很好的解决这个问题。
2.深入泛型:

①定义泛型接口、类

public interface List<E>//定义接口,指定形参E,在这个接口里面E可以作为泛型使用
{void add(E,x);Iterator<E> iterator();//A...
}
public interface Iterator<E>//在这个接口里,E可以作为类型使用
{E next();boolean hasNext();...
}
public interface Map<K,V>//K,V可以作为类型使用
{Set<K> keySet()//B V put (K key ,V value) ...
}

可以发现:在A、B处方法声明返回值类型是Iterrator<E>和Set<K>,说明他们是一种特殊的数据类型,可以认为是Iterrator和Set类型的子类。

例如:使用List类型的时候,为E形参传入String实参,则产生了一个新的类型List<String>,把它想象成E全部被String取代的特殊的List子接口。

public interface ListString extends List
{void add(String x);Iterator<String> iterator();...
}

这样虽然只是设置了一个List<E>接口,实际实验时却是可以产生无数多个List 子接口。

【注意】包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动态的生成无数多个逻辑子表,但这种子类在物理上并不存在。也就是说,List<String>不会被替代成ListString,系统并没有进行源代码复制。

②从泛型类派生子类

当定义完泛型接口和泛型父类额时候,我们就可以为接口创建实现类或者从父类派生出子类,不过使用接口和父类的时候,不能再包含类型参数。

//错误演示public class A extends List<E>{//A继承List,List不能跟类型形参
}   //正确演示1
public class A extends List<String>{//A继承List,为List的E形参传入String
} //正确演示2
public class A extends List{//A继承List,也可以不为类型形参传入实际的类型参数,不过可能会出现unchecked警告
} 

③并不存在泛型类

前面有提到,可以把List<String>类当成是List的子类,这里可能会给大家带来误解,实际上,系统并没有为List<E>生成新的class文件,而且也不会把它当成新的类来处理。这里给一个验证:

package FanXing;import java.util.ArrayList;
import java.util.List;public class ListErr {public static void main(String[] args) {List<String> aaa = new ArrayList<>();List<Integer> aaa1 = new ArrayList<>();System.out.println(aaa1.getClass()==aaa1.getClass());}
}

从输出true可以看出,不管为泛型的类型形参传入哪一种类型实参,对于Java来说,他们依然被当做同一个类来处理,在内存中也只占用一块内存空间。

3.类型通配符

package FanXing;import java.util.List;public class test {public void test1 (List c){for(int i = 0;i<c.size();i++){System.out.println(c.get(i));}}
}

 这是一个普通的遍历List集合的代码,在编译过程中出现了一个泛型警告,因为在这里使用List接口时没有传入实际的参数类型。

修改后:

package FanXing;import java.util.List;public class test {public void test1 (List<?> c){for(int i = 0;i<c.size();i++){System.out.println(c.get(i));}}
}

看到原来的List变成了List<?>,这里就引入了类型通配符的概念。

类型通配符就是一个“?”,它的元素类型可以匹配任何类型。

比如,当使用List<?>时,List就成了任何泛型List的父类,比如List既是List<String>的父类,又是List<Integer>的父类,但是,类型之间没有继承关系,String是Object的子类,List<String>不是List<Object>的子类。

①设置类型通配符的上限

格式:List<? extends XXX>它表示所有XXX泛型List的父类

②设定类型形参的上限

例子:

public class List<T extends Number & java.io.Serializable>
{...//表明T类型必须是Number类或者其子类,并且必须实现java.io.Serializable接口
}

注意:为类型参数指定多个上限时,所有的接口上限必须位于类上限之后。

4.泛型方法

格式:

修饰符 <T,S> 返回值类型 方法名 (形参列表)
{//方法体   
}    

泛型方法和类型通配符的区别:

①大多数时候都可以用泛型方法来替换通配符
②使用通配符情况:用来支持灵活的子类化
③泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,就不应该使用泛型方法。
④形参a的类型或返回值的类型依赖于另一个形参b的类型,则b的类型声明不应该使用通配符,因为使用通配符表示类型b不确定,那么a的类型也不能确定,这时候要考虑使用泛型方法。
⑤类型不被依赖时,使用通配符。

泛型方法与方法重载:

public class MyUtils {// (1)public static <T> void copy(Collection<T> dest, Collection<? extends T> src) {...}// (2)public static <T> T copy(Collection<? super T> dest, Collection<T> src) {...}public static void main(String[] args) {List<Number> ln = new ArrayList<>();List<Integer> li = new ArrayList<>();copy(ln, li); // 这里会编译报错
  }
}

允许根据方法参数泛型不同进行方法重载,但是调用时,如果编译器分不清该调用哪个方法则编译报错,上面代码中有两个copy方法,调用的时候,编译器既可以调用第一个copy ,也可以调用第二个copy,这样它就无法确定调用哪个,就会引起编译报错。

5.擦除和转换

 Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List<Object>和List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方式与C++模板机制实现方式之间的重要区别。

参考链接:Java泛型使用详解

【备注1】:Java的编译类型和运行类型的理解。

Java的编译时类型是由声明变量时使用的类型决定,运行时类型是由实际赋值的对象所决定。参考链接:Java的编译类型和运行类型

【备注2】:Java泛型中K T V E ? 分别代表的含义:

E – Element (在集合中使用,因为集合中存放的是元素)

T – Type(Java 类)

K – Key(键)

V – Value(值)

N – Number(数值类型)

? – 表示不确定的java类型(无限制通配符类型)参考链接:泛型相关

【备注3】:为什么说在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型参数。

初步理解:

因为泛型是要在对象创建的时候才知道是什么类型的,而对象创建的代码执行先后顺序是static的部分,然后才是构造函数等等。所以在对象初始化之前static的部分已经执行了,如果你在静态部分引用的泛型,那么毫无疑问虚拟机根本不知道是什么东西,因为这个时候类还没有初始化。因此在静态方法、数据域或初始化语句中,为了类而引用泛型类型参数是非法的。

 实际原因:

静态变量是被泛型类所有实例所共享的。对于声明为MyClass<T>的类,访问其中的静态变量的方法仍然是MyClass.myStaticVar。不管是通过new MyClass<String>还是new MyClass<Integer>创建的对象,都是共享一个静态变量。假设允许类型参数作为静态变量的类型。那么考虑下面一种情况:

MyClass<String> class1 = new MyClass<String>();MyClass<Integer> class2 = new MyClass<Integer>();class1.myStaticVar = "hello";class2.myStaticVar = 5;

由于泛型系统的类型擦除(type erasure)。myStaticVar被还原成Object类型,然后当调用class1.myStaticVar= "hello"; 编译器进行强制类型转换,即myStaticVar = (String)"hello";接着调用class2.myStaticVar语句时,编译器继续进行强制类型转换,myStaticVar = (Integer)Integer.valueOf(5); 此时myStaticVar是String类型的,当然该语句会在运行时抛出ClassCastException异常,这样一来存在类型安全问题。因此泛型系统不允许类的静态变量用类型参数作为变量类型。

当然,静态泛型方法也不允许。
参考链接:

有关静态不允许使用类型参数的讨论

为什么类型参数不能作为静态变量的类型

【备注4】:Java泛型中上下界限定符extends 和 super的理解:

<? extends T>表示类型的上界,表示参数化类型可能是T或者T的子类;

<? super T>表示类型的下界,表示参数化类型是此类型的超类型(父类型),直至Object。

PECS原则:

如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)

如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)

如果既要存又要取,那么就不要使用任何通配符。

【备注5】:异常类

异常类一般分为两种:异常(Exception)和错误(Error)

Exception就用try&catch&finally来处理,先在try中运行代码,catch处理可能出现异常,finally是一定会执行到里面的代码。 
常见的异常有: 
NumberFormatException(数字格式异常) 
IndexOutOfBoundsException(数组越界异常) 
ArithmeticException(除零异常) 
RuntimeException(运行时异常) 
异常常用的方法: 
getMessage():返回异常的详细描述字符串。 
printStackTrace():跟踪栈详细输出到标准错误输出 
printStackTrace(PrintStream s):跟踪栈详细输出到标准错误输出到指定的输出流 
getStackTrace():返回异常的跟踪栈信息。 

Error一般是由虚拟机造成的系统崩溃的。

下一步学习拓展及计划:

1.总结讨论并做成思维导图

2.理解学习中一直提到的异常这一章的内容

3.擦除的实例(自己尝试用一个例子实践)

转载于:https://www.cnblogs.com/zhangqingwang/p/10609987.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/278090.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从Microsoft Teams技术栈看前端技术发展趋势

在前不久的微软美国一年一度的Ignite大会上&#xff0c;微软宣布Microsoft Teams是微软历史上发展最快的应用。它将取代Skype for business&#xff0c;成为语音视频协作的主打产品。 我也有幸在上个月微软中国年度技术大会Tech Summit 2018上被邀请作为讲师讲解基于Teams平台…

Confluence Cloud的Teams Message Extension

Confluence Cloud的Message Extension现在正式登入Microsoft Teams。 它可用于团队频道和私人聊天&#xff0c;使您的对话更具描述性和信息性。 从Microsoft Teams应用商店获取Confluence Cloud应用程序并连接到Confluence Cloud实例。 连接后&#xff0c;您将能够搜索Conflue…

45 | 打蛇打七寸:精准测试

转载于:https://www.cnblogs.com/lmx0621/p/10614966.html

Teams App统计

周末闲来无事&#xff0c;统计了一下Teams的app商店里的app ( Teams App Store )。截至到现在&#xff08;2018年11月&#xff09;一共有145个app。要注意一点&#xff1a;如果app不是公开的&#xff08;即单独安装到Office365租户里&#xff0c;并没有提交到office store&…

你必须要懂的APK瘦身知识

随着业务复杂度的逐渐增加&#xff0c;代码、资源也在不断的增加&#xff0c;此时你的APP大小也在增加。从用户层面来说&#xff0c;面对动辄几十兆的APP来说在非WIFI情况下还是会犹豫要不要下载&#xff0c;不下载你就可能因此失去了一个用户。从公司层面来讲&#xff0c;流量…

DHT网络

(基础技术) 现在有一种方法&#xff0c;可以通过磁力链接&#xff0c;例如magnet:?xturn:btih:0482e0811014fd4cb5d207d08a7be616a4672daa&#xff0c;就可以获取BT文件。 这个是通过DHT网络来实现的。 DHT网络是一个去中心化的&#xff0c;分布式信息存储系统。 存储的信息就…

Node文件模块

在上一篇文章中有提到&#xff0c;Node模块分为核心模块和文件模块&#xff0c;接下来就简单总结一下文件模块。 文件模块则是在运行时动态加载&#xff0c;需要完整的路径分析、文件定位、编译执行过程、速度相比核心模块稍微慢一些&#xff0c;但是用的非常多。这些模块需要我…

PHP GD库解析一张简单图片并输出

这里只演示一下2种颜色值的图片&#xff0c;简单描述下概念。 首先要安装下GD库。否则下面的代码运行不了。 $size getimagesize(2.png); // 获取图片大小 $res imagecreatefrompng(2.png); // 获取指定图片的资源对象for ($i 0; $i < $size[1]; $i) {for ($j 0; $j &…

开发Teams的messaging extension

什么是Messaging Extension Messaging Extension是微软Teams的一种十分有用的扩展方式。可以让用户发送adaptive cards。具体的说明不在这里展开了。可以阅读微软官方的详细说明&#xff1a; https://docs.microsoft.com/en-gb/microsoftteams/platform/concepts/messaging-e…

归并排序(转)

转载自&#xff1a;https://www.cnblogs.com/chengxiao/p/6194356.html 归并排序&#xff08;MERGE-SORT&#xff09;是利用归并的思想实现的排序方法&#xff0c;该算法采用经典的分治&#xff08;divide-and-conquer&#xff09;策略&#xff08;分治法将问题分(divide)成一些…

Site24x7 为Teams提供可智能 DevOps

我们生活在一个云的时代, SaaS 应用程序每天都在推动我们的生产力。作为一个消费者, 很难想象如果你最喜欢的应用无法访问&#xff0c;即使只是一秒钟无法访问。作为 SaaS业务, 更难以想象您的服务面临停机, 每一分钟停止服务都会花费大量的资金, 当然还损失客户的信任。Site24…

XUbuntu22.04之跨平台容器格式工具:MKVToolNix(二百零三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

redis集群搭建踩坑笔记

推荐参考教程&#xff1a;https://blog.csdn.net/pucao_cug/article/details/69250101 错误&#xff1a; from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in require from /usr/local/redis-3.0.6/src/redis-trib.rb:25:in <main> 解决&#xff1a; g…

Docker 创建镜像

文章首发自个人网站&#xff1a;https://www.exception.site/docker/docker-create-image 本文中&#xff0c;您将学习 Docker 如何创建镜像&#xff1f;Docker 创建镜像主要有三种&#xff1a; 基于已有的镜像创建&#xff1b;基于 Dockerfile 来创建&#xff1b;基于本地模板…

使用Azure Pipelines来实现Teams App的CI

我在之前的文章里介绍了如何一步步配置CI/CD来部署Teams App( 之前的文章 )&#xff0c;随着Azure DevOps的发展&#xff0c;微软推出了Azure Pipelines。在这篇文章中&#xff0c;主要介绍什么是Azure Pipelines&#xff0c;以及如何使用Azure Pipelines来进行Teams App的构建…

004-React入门概述

一、概述 参考地址&#xff1a;https://reactjs.org/docs/try-react.html 1.1、本地快速体验 <!DOCTYPE html> <html><head><meta charset"UTF-8" /><title>Hello World</title><script src"https://unpkg.com/react16/…

开发Teams Tabs应用程序

什么是Teams Tabs Tabs是微软Teams的一种十分有用的扩展方式。可以非常方便的和现有的网站或者网页应用进行整合。具体的说明不在这里展开了。可以阅读微软官方的详细说明&#xff1a; https://docs.microsoft.com/en-gb/microsoftteams/platform/concepts/tabs/tabs-overvie…

(转)关于SimpleDateFormat安全的时间格式化线程安全问题

想必大家对SimpleDateFormat并不陌生。SimpleDateFormat 是 Java 中一个非常常用的类&#xff0c;该类用来对日期字符串进行解析和格式化输出&#xff0c;但如果使用不小心会导致非常微妙和难以调试的问题&#xff0c;因为 DateFormat 和 SimpleDateFormat 类不都是线程安全的&…

IDEA开发工具的学习

1.设置jdk的版本 &#xff0c;快捷键&#xff1a;ctrl shirt alt s 打开项目的设置&#xff0c;选择Project 进行 jdk版本的设置。 2.鼠标移到项目上&#xff0c;右键&#xff0c;Show in Explorer 定位到当前项目对应的文件夹中 3.每次关闭项目时&#xff0c;需要手动选择Fi…

顺利达成微软HacktoberFest 2018

昨天收到邮件&#xff0c;我的HacktoberFest 2018奖品终于从美国寄出来了&#xff0c;不知道飘洋过海多久可以寄到。 今年的HacktoberFest 2018除了微软官方博客的宣传&#xff0c;连Channel 9的美女主播也在TWC上大肆宣传。 活动内容是在整个10月份需要给微软的开源代码贡献5…