java基础-泛型举例详解
泛型
泛型是JDK5.0增加的新特性,泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数。这种类型参数可以在类、接口、和方法的创建中,分别被称为泛型类、泛型接口、泛型方法。
一、认识泛型
在没有泛型之前,通过对类型Object的引用来实现参数的"任意化",但"任意化"带来的缺点是需要显示的强制类型转换,此种转换要求开发者对实际参数类型预知的情况下进行,对于强制转换错误的情况,编译器可能不会提示错误,但在运行时会出现异常,这是一个安全隐患。
举例:不使用泛型实现参数化类型
package generic; public class NoGeneric {
private Object ob; //定义通用类型成员
public NoGeneric(Object ob) {
this.ob = ob;
}
public Object getOb() {
return ob;
}
public void setOb(Object ob) {
this.ob = ob;
}
public void showType() {
System.out.println("实际类型是:"+ob.getClass().getName());
}
} package generic; public class NoGenericDemo { public static void main(String[] args) {
// TODO 自动生成的方法存根
//定义类NoGener的一个Integer版本
NoGeneric intob = new NoGeneric(new Integer(66));
intob.showType();
int i = (Integer)intob.getOb();
System.out.println("value="+ i);
System.out.println("-----------------------------");
//定义类NoGeneric的一个String版本
NoGeneric strob = new NoGeneric(new String("hello"));
strob.showType();
String s = (String)strob.getOb();
System.out.println("value="+ s);
}
}
执行结果为:
实际类型是:java.lang.Integer
value=66
-----------------------------
实际类型是:java.lang.String
value=hello
上面的实例有两点需要注意:首先如下语句
String s = (String)strob.getOb();
在使用时必须明确指定返回对象需要被强制转化的类型为String,否则无法编译通过;其次,由于intob和strob都属于NoGeneric的类型,假如执行如下语句
intob = strob;
此种赋值,语法上是合法的,而在语义上是错误的,对于这种情况,只有在运行时才会出现异常,使用泛型就不会出现上述错误,泛型的好处就是在编译期 检查类型,捕捉类型不匹配错误,并且所有强制转换都是自动和隐式的,提高代码的重用率.
举例 2:使用泛型使用泛型实现参数实例化类型
package generic; public class Generic<T> {
private T ob; //定义泛型成员变量
public Generic(T ob) {
this.ob = ob;
}
public T getOb() {
return ob;
}
public void setOb(T ob) {
this.ob = ob;
}
public void showType() {
System.out.println("实例类型为:" + ob.getClass().getName());
}
} package generic; public class GenericDemo { public static void main(String[] args) {
// TODO 自动生成的方法存根
//定义泛型Generic的一个Integer的版本
Generic<Integer> intob = new Generic<Integer>(88);
intob.showType();
int i = intob.getOb();
System.out.println("value=" + i);
System.out.println("----------------------");
//定义泛型Generic的一个String版本
Generic<String> strob = new Generic<String>("hello");
strob.showType();
String s = strob.getOb();
System.out.println("value=" + s);
}
}
运行结果为:
实例类型为:java.lang.Integer
value=88
----------------------
实例类型为:java.lang.String
value=hello
在引入泛型的前提下,如果再次执行
intob = strob;
将提示错误,编译无法通过
二、泛型定义
泛型的语法可归纳为
class class-name <type-param-list>{//......}
实例化泛型的语法为:
class-name <type-param-list> obj = new class-name<type-param-list>(cons-arg-list);
type-param-list用于指明当前泛型类可接受的类型参数占位符的个数; 如:
class Generic<T>{//......}
这里的T是类型参数的名称,并且只允许传一个类型参数给Generic类,在创建对象时,T用作传递 给Generic的实际类型的占位符,每当声明类型参数时,只需用目标类型替换T即可. 如:
Generic <Integer> intob;
声明对象时占位符T用于指定实际类型,如果传递实际类型为Integer,属性ob就是Integer类型,类型T还可以指定方法的返回类型 如:
public T getOb(){
return ob;
}
理解泛型有三点需要注意:
1、泛型的类型参数只能为类类型(包括自定义类),不能是基本数据类型。
2、同一种泛型可以对应多个版本(因为类型参数时不确定的)、不同版本的泛型类实例是不兼容的。
3、泛型的类型参数可以有多个。
注意 根据惯例,泛型类定义时通常使用一个唯一的大写字母表示一个类型参数.
三、有界类型
定义泛型类时,可以向类型参数指定任何类型信息,特别是集合框架操作中,可以最大限度地提高适用范围,但有时候需要对类型参数的取值进行一定程度的限制,以使数据具有可操作性.
为了处理这种情况,java提供了有界类型,.在指定类型参数时可以使用extends关键字限制此类型参数代表的类必须是继承自指定父类或父类本身.
使用extends关键字实现有界类型泛型类的定义
package generic; public class BoundGeneric<T extends Number> {
//定义泛型数组
T[] array;
public BoundGeneric(T[] array) {
this.array = array;
}
//计算总和
public double sum() {
double sum = 0.0;
for(T t : array) {
sum = sum + t.doubleValue();
}
return sum;
}
}
BoundGeneric类的定义中,使用extends将T的类型限制为Number类及其子类,故可以再定义过程中调用Number类的doubleValue方法,现在分别指定Integer,double,String类型作为类型参数,测试BoundGeneric:
package generic; public class BoundGenericDemo { public static void main(String[] args) {
// TODO 自动生成的方法存根
//使用整形数组构造泛型对象
Integer[] intArray = {1, 2, 3, 4};
BoundGeneric<Integer> iobj = new BoundGeneric<Integer>(intArray);
System.out.println("iobj的和为:" + iobj.sum()); //使用Double型数组构造泛型对象
Double[] douArray = {1.2, 2.3, 3.4, 4.5};
BoundGeneric<Double> dobj = new BoundGeneric<Double>(douArray);
System.out.println("dobj的和为:" + dobj.sum()); String[] strArray = {"str1","str2"};
//下面的语句将会报错,String不是Number的子类
//BoundGeneric<String> sobj = new BoundGeneric<String>(strArray);
}
}
运行结果为:
iobj的和为:10.0
dobj的和为:11.4
注:在使用extends(如:T extends someClass)声明的泛型类进行实例化时允许传递的参数类型为:如果someClass是类,可以传递someClass本身及其子类;如果someClass接口可以传递实现接口的类
四、通配符
首先在说通配符之前先看一下这段代码:使用前面定义的Generic类.
package generic; public class WildcarDemo {
public static void func(Generic <Object> g) {
//...
}
public static void main(String args[]) {
Generic <Object> obj = new Generic<Object>(12);
func(obj);
Generic<Integer> iobj = new Generic<Integer>(12);
//这里讲产生一个错误:类型 WildcarDemo 中的方法 func(Generic<Object>)对于参数(Generic<Integer>)不适用
//func(iobj);
}
}
上述代码的func()方法的创建意图是能够处理各种类型参数的Generic对象,因为Generic是泛型,所以在使用时需要为其指定具体的参数化类型Object,看似不成问题,
但在
func(iobj);
处产生一个编译错误,因为func定义过程中以明确声明的Generic的类型参数为Object,这里试图将Generic<Integer>类型的对象传递给func()方法,类型不匹配导致编译错误.这种情况可以使用通配符解决.通配符由"?"来表示,它代表一个未知类型
package generic; public class WildcarDemo2 {
public static void func(Generic <?> g) {
//...
} public static void main(String args[]) {
Generic<Object> obj = new Generic<Object>(12);
func(obj); Generic<Integer> iobj = new Generic<Integer>(12);
func(iobj);
}
}
上述代码,在采用了通配符后语句将无误的编译,运行.
在通配符使用的过程中,也可通过extends关键字限定通配符的界定的类型参数的范围.
package generic; public class WildcarDemo3 {
public static void func(Generic <? extends Number> g) {
//...
} public static void main(String args[]) {
Generic<Object> obj = new Generic<Object>(12);
//这里将产生一个错误:类型 WildcarDemo3 中的方法 func(Generic<? extends Number>)对于参数(Generic<Object>)不适用
//func(obj); Generic<Integer> iobj = new Generic<Integer>(12);
func(iobj);
}
}
五、泛型的局限性
java并没有真正实现泛型,是编译器在编译的时候在字节码上做了手脚(称为擦除). 这种实现理念在成java泛型本身有很多漏洞, 为了避免这些问题java对泛型的使用上做了一些约束,但不可避免的还是有一些问题存在.多数的限制都是由类型擦除引起的.
1、泛型类型不能被实例化
public class Gen<T>{
T ob;
public Gen(){
ob = new T();
}
}
Gen<T>构造器是非法的,类型擦除将变量T替换成Object,但这段代码的本意肯定不是调用new Object().类似:如
public <T> T[]build (T[] a){
T [] array = new T[2];
//...
}
类型擦除会让这个方法总是构造一个Object[2]数组,但是可以通过调用Class.newInstance和Array.newInstance方法,利用反射构造泛型对象和数组
2、数组
不能实例化数组如:
T[] vals;
vals = new T[10];
因为T在运行时时不存在的,编译器无法知道实际创建那种类型的数据.
其次,不能创建一个类型特定的泛型引用的数组 如:
Gen<String> []arrays = new Gen<String>[100];
上面的代码会损害类型安全
如果使用通配符,就可以创建泛型类型的引用数组
Gen<?> []arrays = new Gen<?>[10];
3、怒能用类型参数替换基本类型
因为擦除类型后原先的类型参数被Object或者限定类型替换,而基本类型是不能被对象所存储的,可以使用基本类型的包装类来解决此问题
4、异常
不能抛出也不能捕获泛型类的异常对象,使用泛型类来扩展Throwable也是非法的. 如:
public class GenericException <T> extends Exception{
//泛型类无法继承Throwable
}
不能再catch子句中使用类型参数,例如下面的方法将不能编译
public static <T extends Throwable> void doWork(Class<T> t) {
try {
//...
}catch(Throwable realCause) {
//...
}
但是在异常声明时可以使用类型参数,如:
public static <T extends Throwable> void doWork(T t) throws T {
try {
//...
}catch(Throwable realCause) {
throw t;
}
}
5、静态成员
不能在静态变量或者静态方法中引用类型参数 如:
public class Gen<T>{
static T ob;
static T getOb() {
return ob;
}
}
这些均参考自“Java SE程序设计”,算是做个笔记,以后忘了可以翻阅一下,写在自己的随笔中,也希望可以帮助更多的人。如有侵权,请联系本人删除
java基础-泛型举例详解的更多相关文章
- Java基础之 数组详解
前言:Java内功心法之数组详解,看完这篇你向Java大神的路上又迈出了一步(有什么问题或者需要资料可以联系我的扣扣:734999078) 数组概念 同一种类型数据的集合.其实数组就是一个容器. 数组 ...
- java基础之:详解内部类(转载)
可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一个非常有用的特性但又比较难理解使用的特性(鄙人到现在都没有怎么使用过内部类,对内部类也只是略知一二). 第一次见面 内部类我们从外面 ...
- Java基础知识面试题详解(2019年)
文章目录 1. 面向对象和面向过程的区别 2. Java 语言有哪些特点? 3. 关于 JVM JDK 和 JRE 最详细通俗的解答 JVM JDK 和 JRE 4. Oracle JDK 和 Ope ...
- Java基础:String类详解,案例用户登录实现,案例手机号截取实现,案例敏感词替换实现;StringBuilder类详解,StringBuilder和String相互转换,附练习案例.
1.API 1.1 API概述-帮助文档的使用 什么是API API (Application Programming Interface) :应用程序编程接口 java中的API 指的就是 JDK ...
- Java基础(52):ClassCastException详解(转)
ClassCastException,从字面上看,是类型转换错误,通常是进行强制类型转换时候出的错误.下面对产生ClassCastException异常的原因进行分析,然后给出这种异常的解决方法. 这 ...
- java基础(十四)-----详解匿名内部类——Java高级开发必须懂的
在这篇博客中你可以了解到匿名内部类的使用.匿名内部类要注意的事项.匿名内部类使用的形参为何要为final. 使用匿名内部类内部类 匿名内部类由于没有名字,所以它的创建方式有点儿奇怪.创建格式如下: n ...
- Java基础之数组详解
数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同. Java 语言中提供的数组是用来存储固定大小的同类型元素. 你可以声明一个数组变量,如 numbers[1 ...
- java 基础之--反射详解
java 反射绝大部分都位于 java.lang.reflect package 中:常用的类就是: 1.class类:代表一个类 2.field类:代表类的成员变量 3.method:代表类的方法 ...
- java基础篇---枚举详解
在JDK1.5之前,JAVA可以有两种方式定义新类型:类和接口,对于大部分面向对象编程,有这两种似乎就足够了,但是在一些特殊情况就不合适.例如:想要定义一个Color类,它只能有Red,Green,B ...
随机推荐
- vue+element UI以组件递归方式实现多级导航菜单
介绍 这是一个是基于element-UI的导航菜单组件基础上,进行了二次封装的菜单组件,该组件以组件递归的方式,实现了可根据从后端接收到的json菜单数据,动态渲染多级菜单的功能. 使用方法 由于该组 ...
- 大数据之路day01_2--记事本与EditPlus编写Hello World并且运行
在上一节我们成功的安装JAVA并且将其环境配置成功,接下来我们来编写第一个java程序——Hello World 1.利用记事本编写代码,利用命令行来编译运行 (1)新建记事本,(文件名).java后 ...
- 易初大数据 2019年11月2日 计算机英语 wangqingchao
一.Match the explantions in Column B with words and expressions in Column A.(搭配每组中同意以的词或短语) 1.交换机(pos ...
- 深入理解计算机系统 第三章 程序的机器级表示 part3
这周看了刘老师提供的相关视频,以及书中对应的章节“3.7 过程” 这一节分为运行时栈.转移控制.数据传送.栈上的局部存储.寄存器中的局部存储空间和递归过程这 6 个小节 其中前 3 小节看懂了一部分内 ...
- jenkins里的定时构建
1. 定时构建语法:* * * * * (五颗星,多个时间点,中间用逗号隔开)第一个*表示分钟,取值0~59第二个*表示小时,取值0~23第三个*表示一个月的第几天,取值1~31第四个*表示第几月,取 ...
- linux禁用icmp(ping )
永久禁用: echo net.ipv4.icmp_echo_ignore_all=1 >>/etc/sysctl.conf 永久启用: echo net.ipv4.icmp_echo_ig ...
- # & 等特殊字符会导致传参失败
# & 等特殊字符会导致 post 传参失败 处理方法使用 encodeURIComponent 将字符串转化一下 实例 // toUpperCase() 转化为大写字母 var cateco ...
- Android DecorView 与 Activity 绑定原理分析
一年多以前,曾经以为自己对 View 的绘制已经有了解了,事后发现也只是懂了些皮毛而已.经过一年多的实战,Android 和 Java 基础都有了提升,时机成熟了,是时候该去总结 View 的绘制流程 ...
- PostGIS 查询点在线上
1.缓冲区法:查询数据库fm表里,与坐标(12989691.512 4798962.444)相距0.0001米的数据(3857坐标系) ),),),),geom) ; --如果坐标系统一,不用tran ...
- 【Stream—7】NetworkStream相关知识分享
一.NetworkStream的作用 和先前的流有所不同,NetworkStream的特殊性可以在它的命名空间中得以了解(System.Net.Sockets),聪明的你马上就会反应过来:既然是在网络 ...