java泛型详解
来源:互联网 发布:智能后视镜安装软件 编辑:程序博客网 时间:2024/06/10 23:43
一、概念
百度百科:
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。Java语言引入泛型的好处是安全简单。
在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。
根据《Java编程思想 (第4版)》中的描述,泛型出现的动机在于:
有许多原因促成了泛型的出现,而最引人注意的一个原因,就是为了创建容器类。
二、泛型类
容器类应该算得上最具重用性的类库之一。先来看一个没有泛型的情况下的容器类如何定义:
public class Container { private String key; private String value; public Container(String k, String v) { key = k; value = v; } //省略Setter Getter}
Container类保存了一对key-value键值对,但是类型是定死的,也就说如果我想要创建一个键值对是String-Integer类型的,当前这个Container是做不到的,必须再自定义。那么这明显重用性就非常低。
当然,我可以用Object来代替String,并且在Java SE5之前,我们也只能这么做,由于Object是所有类型的基类,所以可以直接转型。但是这样灵活性还是不够,因为还是指定类型了,只不过这次指定的类型层级更高而已,有没有可能不指定类型?有没有可能在运行时才知道具体的类型是什么?
所以,就出现了泛型。
public class Container<K, V> { private K key; private V value; public Container(K k, V v) { key = k; value = v; } public K getKey() { return key; } public void setKey(K key) { this.key = key; } public V getValue() { return value; } public void setValue(V value) { this.value = value; }}
在编译期,是无法知道K和V具体是什么类型,只有在运行时才会真正根据类型来构造和分配内存。所以容器可以支持不同的类型
Container<String, String> c1 = new Container<String, String>("name", "findingsea");Container<String, Integer> c2 = new Container<String, Integer>("age", 24);Container<Double, Double> c3 = new Container<Double, Double>(1.1, 2.2);
三、泛型接口
在泛型接口中,生成器是一个很好的理解,看如下的生成器接口定义:
public interface Generator<T> { public T next();}
然后定义一个生成器类来实现这个接口:
public class FruitGenerator implements Generator<String> { private String[] fruits = new String[]{"Apple", "Banana", "Pear"}; @Override public String next() { Random rand = new Random(); return fruits[rand.nextInt(3)]; }}
四、泛型方法
一个基本的原则是:无论何时,只要你能做到,你就应该尽量使用泛型方法。也就是说,如果使用泛型方法可以取代将整个类泛化,那么应该有限采用泛型方法。下面来看一个简单的泛型方法的定义:
public class Main { public static <T> void out(T t) { System.out.println(t); } public static void main(String[] args) { out("findingsea"); out(123); out(11.11); out(true); }}
可以看到方法的参数彻底泛化了,这个过程涉及到编译器的类型推导和自动打包,也就说原来需要我们自己对类型进行的判断和处理,现在编译器帮我们做了。这样在定义方法的时候不必考虑以后到底需要处理哪些类型的参数,大大增加了编程的灵活性。
再看一个泛型方法和可变参数的例子:
public class Main { public static <T> void out(T... args) { for (T t : args) { System.out.println(t); } } public static void main(String[] args) { out("findingsea", 123, 11.11, true); }}
输出和前一段代码相同,可以看到泛型可以和可变参数非常完美的结合。
五、泛型通配符
使用大写字母A,B,C,D……X,Y,Z定义的,就都是泛型,把T换成A也一样,这里T只是名字上的意义而已
- ? 表示不确定的java类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值对中的Key Value
- E (element) 代表Element
将T换成A,在执行效果上是没有任何区别的,只不过我们约定好了T代表type,按照约定规范来,增加了代码的可读性。
- ArrayList al=new ArrayList(); 指定集合元素只能是T类型
- ArrayList
List<Object> objects = new ArrayList<String>(); //合法吗?
乍看起来,似乎是正确的,因为String是Object的子类,因而集合中任何一个String类型的元素都是有效的Object对象。但是
List<Object> objects = new ArrayList<String>(); //合法吗?objects.add(new Object()); //如果合法,那么这句呢?
既然objects的类型声明为List< Object >,那么就能将Object实例存入其中;但是,这个实例保存的元素都是字符串,尝试存入的Object对象与其并不兼容。因而这种写法是不合理的,也就是说,“ List< String > 并不是 List< Object > 的子类” 如果想让容器的类型具有父子关系,需要使用未知类型:
List<?> objects = new ArrayList<String>();
使用Class和Class
People people =(People)Class.forName("com.lyang.demo.fanxing.People").newInstance();
需要强转,如果反射的类型不是People类,就会报java.lang.ClassCastException错误。使用Class泛型后,不用强转了。
public class Test { public static <T> T createInstance(Class<T> clazz) throws IllegalAccessException, InstantiationException { return clazz.newInstance(); } public static void main(String[] args) throws IllegalAccessException, InstantiationException { Fruit fruit= createInstance(Fruit .class); People people= createInstance(People.class); }}
那Class< T >和Class< ? >有什么区别呢?
Class< T >在实例化的时候,T要替换成具体类。
Class< ? >它是个通配泛型,?可以代表任何类型,主要用于声明时的限制情况。
public Class<?> clazz; //合理public Class<T> clazz; //不合理
六、类型擦除
Java 泛型中的类型参数只在编译时可见,javac 会去掉类型参数,这个被称为类型擦除(type erasure)。在生成的Java字节代码中是不包含泛型中的类型信息的,但会保留泛型的一些踪迹,在运行时通过反射可以看到。类型擦除也是Java的泛型实现方式与C++模板机制实现方式之间的重要区别。
Java 5中添加的泛型是一个新增加的语言特性,而类型擦除正是解决后向兼容型的保证。旧的非泛型集合类和新的泛型集合类是可以兼容的:
List someList = getSomeThings();List<String> stringList = (List<String>)someList;
这是因为在经过了编译器的类型擦除后,对 JVM 来说看到的都是List。非泛型的List一般成为原始类型。类型擦除机制会导致一些看上去比较奇怪的现象:
//无法编译interface OrderCounter { int totalOrders(Map<String, List<String>> orders); int totalOrders(Map<String, Integer> orders);}
上述代码乍看上去是合法的,但实际上并不能进行编译。因为在类型擦除以后,两个方法的签名都是:
int totalOrders(Map);
运行时无法通过签名区分这两个方法,因而Java规范把这种句法列为非法的。
关于类型擦除需要记住的几点是:
- 泛型类并没有自己独有的Class类对象。比如并不存List< String >.class或是List< Integer >.class,而只有List.class。在经过类型擦除后剩下的只有原始类型,无论是List< String >或是List< Integer >,对JVM来说都看作List。
- 静态变量是被泛型类的所有实例所共享的。对于声明为MyClass的类,访问其中的静态变量的方法仍然是MyClass.myStaticVar。不管是通过new MyClass< String > 还是newMyClass< Integer >创建的对象,都是共享一个静态变量。
- 泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException< String >和MyException< Integer >的。对于JVM来说,它们都是MyException类型的。也就无法执行与异常对应的catch语句。
七、泛型的问题
引入泛型之后的类型系统增加了两个维度:一个是类型参数自身的继承体系结构,另外一个是泛型类或接口自身的继承体系结构。第一个指的是对于List< String >和List< Object >这样的情况,类型参数String是继承自Object的。而第二种指的是List接口继承自Collection接口。对于这个类型系统,有如下的一些规则:
- 相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构。即List< String >是Collection< String >的子类型,List< String >可以替换Collection< String >。这种情况也适用于带有上下界的类型声明。
- 当泛型类的类型声明中使用了通配符的时候,其子类型可以在两个维度上分别展开。如对Collection< ? extends Number>来说,其子类型可以在Collection这个维度上展开,即List< ? extends Number>和Set< ? extends Number>等;也可以在Number这个层次上展开,即Collection< Double >和 Collection< Integer >等。如此循环下去,ArrayList< Long >和HashSet< Double >等也都算是Collection< ? extends Number>的子类型。
- 如果泛型类中包含多个类型参数,则对于每个类型参数分别应用上面的规则。
创建泛型数组在Java中是不被允许的。需要适用类型转换来创建泛型数组。
T[] a = (T[])new Object[10];
- Java泛型详解
- java泛型详解
- java泛型详解
- Java泛型详解
- Java泛型详解
- java泛型详解
- Java泛型详解
- java泛型详解
- java 泛型详解
- java 泛型详解
- Java 泛型详解
- java 泛型详解
- java 泛型详解
- java泛型详解
- java 泛型详解
- Java泛型详解
- java 泛型详解
- java 泛型详解
- 最小生成树kruskal POJ 3522 Slim Span
- AndroidStudio上传自己的项目到Bintray jCenter远程仓库!
- 动态规划
- 看阮一峰ES6 笔记
- 输入外挂
- java泛型详解
- Sequence (opentrains)
- javascript 对json数据排序
- 原生js分页效果
- springboot全局异常捕获
- hadoop伪分布式安装
- 中国河流名称代码解释
- 序列化
- HDU-Balala Power!