泛型(Generic type 或者 generics)是对 java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。
在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。
可以在集合框架(Collection framework)中看到泛型的动机。例如,Map 类允许您向一个 Map添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象。
因为 Map.get() 被定义为返回 Object,所以一般必须将 Map.get() 的结果强制类型转换为期望的类型,如下面的代码所示:
Map m = new HashMap(); m.put("key", "blarg"); String s = (String) m.get("key");要让程序通过编译,必须将 get() 的结果强制类型转换为 String,并且希望结果真的是一个 String。但是有可能某人已经在该映射中保存了不是 String 的东西,这样的话,上面的代码将会抛出 ClassCastException。
理想情况下,您可能会得出这样一个观点,即 m 是一个 Map,它将 String 键映射到 String 值。这可以让您消除代码中的强制类型转换,同时获得一个附加的类型检查层,该检查层可以防止有人将错误类型的键或值保存在集合中。这就是泛型所做的工作。
package cn.itcast_01;import java.util.ArrayList;import java.util.Iterator;/* * ArrayList存储字符串并遍历 * * 我们按照正常的写法来写这个程序, 结果确出错了。 * 为什么呢? * 因为我们开始存储的时候,存储了String和Integer两种类型的数据。 * 而在遍历的时候,我们把它们都当作String类型处理的,做了转换,所以就报错了。 * 但是呢,它在编译期间却没有告诉我们。 * 所以,我就觉得这个设计的不好。 * 回想一下,我们的数组 * String[] strArray = new String[3]; * strArray[0] = "hello"; * strArray[1] = "world"; * strArray[2] = 10; * 集合也模仿着数组的这种做法,在创建对象的时候明确元素的数据类型。这样就不会在有问题了。 * 而这种技术被称为:泛型。 * * 泛型:是一种把类型明确的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型。参数化类型,把类型当作参数一样的传递。 * 格式: * <数据类型> * 此处的数据类型只能是引用类型。 * 好处: * A:把运行时期的问题提前到了编译期间 * B:避免了强制类型转换 * C:优化了程序设计,解决了黄色警告线 */public class GenericDemo { public static void main(String[] args) { // 创建 ArrayList<String> array = new ArrayList<String>(); // 添加元素 array.add("hello"); array.add("world"); array.add("java"); // array.add(new Integer(100)); //array.add(10); // JDK5以后的自动装箱 // 等价于:array.add(Integer.valueOf(10)); // 遍历 Iterator<String> it = array.iterator(); while (it.hasNext()) { // ClassCastException // String s = (String) it.next(); String s = it.next(); System.out.PRintln(s); } // 看下面这个代码 // String[] strArray = new String[3]; // strArray[0] = "hello"; // strArray[1] = "world"; // strArray[2] = 10; }}Java 语言中引入泛型是一个较大的功能增强。不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了。这带来了很多好处:
泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
Java 程序中的一种流行技术是定义这样的集合,即它的元素或键是公共类型的,比如“String 列表”或者“String 到 String 的映射”。通过在变量声明中捕获这一附加的类型信息,泛型允许编译器实施这些附加的类型约束。类型错误现在就可以在编译时被捕获了,而不是在运行时当作 ClassCastException 展示出来。将类型检查从运行时挪到编译时有助于您更容易找到错误,并可提高程序的可靠性。
泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。 尽管减少强制类型转换可以降低使用泛型类的代码的罗嗦程度,但是声明泛型变量会带来相应的罗嗦。
3、优化了程序设计,解决了黄色警告线
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入。但是,编译器编译带类型说明的集合时会去除掉“类型”信息,目的就是使程序运行效率不受影响。因此,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。
package com.itheima.day2;import java.util.ArrayList;public class GenericTest { public static void main(String[] args) { ArrayList<String> collection1 = new ArrayList<String>(); ArrayList collection2 = new ArrayList(); System. out.println(collection1.getClass() == collection2.getClass()); //结果:true }}由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
package com.itheima.day2;import java.util.ArrayList;public class GenericTest { public static void main(String[] args) throws Exception { ArrayList<Integer> collection1 = new ArrayList<Integer>(); collection1.getClass().getMethod( "add",Object.class).invoke(collection1, "abc"); System. out.println(collection1.get(0)); }}ArrayList类定义和ArrayList类引用中涉及如下术语:
整个称为ArrayList<E>泛型类型ArrayList<E>中的E称为类型变量或类型参数整个ArrayList<Integer>称为参数化的类型ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数ArrayList<Integer>中的<>念着typeofArrayList称为原始类型参数化类型与原始类型的兼容性:参数化类型可以引用一个原始类型的对象,编译报告警告,例如
Collection<String> c = new Vector();//考虑到对以前代码的兼容性,编译器是可以通过的原始类型可以引用一个参数化类型的对象,编译报告警告,例如
Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>(); //错误!不写<Object>没错,写了就是明知故犯Vector<Object> v = new Vector<String>(); //也错误!注意:
假设Vector<String> v = new Vector<Object>();可以的话,那么以后从v中取出的对象当作String用,而v实际指向的对象中可以加入任意的类型对象;
假设Vector<Object> v = new Vector<String>();可以的话,那么以后可以向v中加入任意的类型对象,而v实际指向的集合中只能装String类型的对象。
编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型。
例如,下面语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10];思考题:
下面的代码会报错误吗?
Vector v1 = new Vector<String>();Vector<Object> v = v1;答案:编译的时候是不会报错的,因为编译器是一行一行按照语法检查代码的,因此不会出错。
把泛型定义在类上,格式:public class 类名<泛型类型1,…>,注意:泛型类型必须是引用类型
package cn.itcast_04;/* * 泛型类的测试 */public class ObjectToolDemo { public static void main(String[] args) { // ObjectTool ot = new ObjectTool(); // // ot.setObj(new String("风清扬")); // String s = (String) ot.getObj(); // System.out.println("姓名是:" + s); // // ot.setObj(new Integer(30)); // Integer i = (Integer) ot.getObj(); // System.out.println("年龄是:" + i); // ot.setObj(new String("林青霞")); // // ClassCastException // Integer ii = (Integer) ot.getObj(); // System.out.println("姓名是:" + ii); System.out.println("-------------"); ObjectTool<String> ot = new ObjectTool<String>(); // ot.setObj(new Integer(27)); //这个时候编译期间就过不去 ot.setObj(new String("林青霞")); String s = ot.getObj(); System.out.println("姓名是:" + s); ObjectTool<Integer> ot2 = new ObjectTool<Integer>(); // ot2.setObj(new String("风清扬"));//这个时候编译期间就过不去 ot2.setObj(new Integer(27)); Integer i = ot2.getObj(); System.out.println("年龄是:" + i); }}//泛型类:把泛型定义在类上class ObjectTool<T> { private T obj; public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; }}把泛型定义在方法上,格式:public <泛型类型> 返回类型 方法名(泛型类型 .)
package cn.itcast_05;public class ObjectToolDemo { public static void main(String[] args) { // ObjectTool ot = new ObjectTool(); // ot.show("hello"); // ot.show(100); // ot.show(true); // ObjectTool<String> ot = new ObjectTool<String>(); // ot.show("hello"); // // ObjectTool<Integer> ot2 = new ObjectTool<Integer>(); // ot2.show(100); // // ObjectTool<Boolean> ot3 = new ObjectTool<Boolean>(); // ot3.show(true); // 定义泛型方法后 ObjectTool ot = new ObjectTool(); ot.show("hello"); ot.show(100); ot.show(true); }}//泛型方法:把泛型定义在方法上class ObjectTool { public <T> void show(T t) { System.out.println(t); }}把泛型定义在接口上,格式:public interface 接口名<泛型类型1…>
package cn.itcast_06;public class InterDemo { public static void main(String[] args) { // 第一种情况的测试 // Inter<String> i = new InterImpl(); // i.show("hello"); // // 第二种情况的测试 Inter<String> i = new InterImpl<String>(); i.show("hello"); Inter<Integer> ii = new InterImpl<Integer>(); ii.show(100); }}//泛型接口:把泛型定义在接口上 interface Inter<T> { public abstract void show(T t);}/实现类在实现接口的时候//第一种情况:已经知道该是什么类型的了//public class InterImpl implements Inter<String> {//// @Override// public void show(String t) {// System.out.println(t);// }// }//第二种情况:还不知道是什么类型的class InterImpl<T> implements Inter<T> { @Override public void show(T t) { System.out.println(t); }}为了解决类型被限制死了不能动态根据实例来确定的缺点,引入了“通配符泛型”,针对上面的例子,使用通配泛型格式为
package cn.itcast_07;import java.util.ArrayList;import java.util.Collection;/* * 泛型高级(通配符) * ?:任意类型,如果没有明确,那么就是Object以及任意的Java类了 * ? extends E:向下限定,E及其子类 * ? super E:向上限定,E极其父类 */public class GenericDemo { public static void main(String[] args) { // 泛型如果明确的写的时候,前后必须一致 Collection<Object> c1 = new ArrayList<Object>(); // Collection<Object> c2 = new ArrayList<Animal>(); // Collection<Object> c3 = new ArrayList<Dog>(); // Collection<Object> c4 = new ArrayList<Cat>(); // ?表示任意的类型都是可以的 Collection<?> c5 = new ArrayList<Object>(); Collection<?> c6 = new ArrayList<Animal>(); Collection<?> c7 = new ArrayList<Dog>(); Collection<?> c8 = new ArrayList<Cat>(); // ? extends E:向下限定,E及其子类 // Collection<? extends Animal> c9 = new ArrayList<Object>(); Collection<? extends Animal> c10 = new ArrayList<Animal>(); Collection<? extends Animal> c11 = new ArrayList<Dog>(); Collection<? extends Animal> c12 = new ArrayList<Cat>(); // ? super E:向上限定,E极其父类 Collection<? super Animal> c13 = new ArrayList<Object>(); Collection<? super Animal> c14 = new ArrayList<Animal>(); // Collection<? super Animal> c15 = new ArrayList<Dog>(); // Collection<? super Animal> c16 = new ArrayList<Cat>(); }}class Animal {}class Dog extends Animal {}class Cat extends Animal {}针对集合操作 的工具类,里面的方法都是静态的,可以对集合进行排序、二分查找、反转、混排等。
Collection:是单列集合的顶层接口,有子接口List和Set。Collections:是针对集合操作的工具类,有对集合进行排序和二分查找等方法
1、public static <T> void sort(List<T> list)
使用sort方法可以根据元素的自然顺序 对指定列表按升序进行排序。列表中的所有元素都必须实现 Comparable 接口。此列表内的所有元素都必须是使用指定比较器可相互比较的
2、public static <T> int binarySearch(List<?> list,T key)
使用二分搜索法搜索指定列表,以获得指定对象。
3、public static <T> T max(Collection<?> coll)
根据元素的自然顺序,返回给定 collection 的最大元素。
4、public static void reverse(List<?> list)
反转指定列表中元素的顺序。
5、public static void shuffle(List<?> list)
混排算法所做的正好与 sort 相反: 它打乱在一个 List 中可能有的任何排列的踪迹。也就是说,基于随机源的输入重排该 List, 这样的排列具有相同的可能性(假设随机源是公正的)。这个算法在实现一个碰运气的游戏中是非常有用的。例如,它可被用来混排代表一副牌的 Card 对象的一个 List 。另外,在生成测试案例时,它也是十分有用的。
6、fill(List<? super T> list, T obj)
使用指定元素替换指定列表中的所有元素。
7、copy(List<? super T> dest, List<? extends T> src)
将所有元素从一个列表复制到另一个列表。用两个参数,一个目标 List 和一个源 List, 将源的元素拷贝到目标,并覆盖它的内容。目标 List 至少与源一样长。如果它更长,则在目标 List 中的剩余元素不受影响。
8、集合线程安全化
List<T> synchronizedList(List<T> list);//返回支持的同步(线程安全的)List集合
Map<K,V> synchronizedMap(Map<K,V> m):返回支持的同步(线程安全的)Map集合
package cn.itcast_01;import java.util.Collections;import java.util.List;import java.util.ArrayList;/* * Collections:是针对集合进行操作的工具类,都是静态方法。 * * 面试题: * Collection和Collections的区别? * Collection:是单列集合的顶层接口,有子接口List和Set。 * Collections:是针对集合操作的工具类,有对集合进行排序和二分查找的方法 * * 要知道的方法 * public static <T> void sort(List<T> list):排序 默认情况下是自然顺序。 * public static <T> int binarySearch(List<?> list,T key):二分查找 * public static <T> T max(Collection<?> coll):最大值 * public static void reverse(List<?> list):反转 * public static void shuffle(List<?> list):随机置换 */public class CollectionsDemo { public static void main(String[] args) { // 创建集合对象 List<Integer> list = new ArrayList<Integer>(); // 添加元素 list.add(30); list.add(20); list.add(50); list.add(10); list.add(40); System.out.println("list:" + list); // public static <T> void sort(List<T> list):排序 默认情况下是自然顺序。 // Collections.sort(list); // System.out.println("list:" + list); // [10, 20, 30, 40, 50] // public static <T> int binarySearch(List<?> list,T key):二分查找 // System.out // .println("binarySearch:" + Collections.binarySearch(list, 30)); // System.out.println("binarySearch:" // + Collections.binarySearch(list, 300)); // public static <T> T max(Collection<?> coll):最大值 // System.out.println("max:"+Collections.max(list)); // public static void reverse(List<?> list):反转 // Collections.reverse(list); // System.out.println("list:" + list); //public static void shuffle(List<?> list):随机置换 Collections.shuffle(list); System.out.println("list:" + list); }}运行结果:
一、概述
此类包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂。 二、常用方法
1、集合与数组的转换
(1) 将数组转换为集合
Lsit<T> asList(T… a);//返回一个受指定数组支持的固定大小的列表。
把数组变成List集合的好处:可以使用集合的思想和方法来操作数组中的元素。如:contains,get,indexOf,subList等方法。
PS:
将数组转换成集合,不可使用集合的增删方法,因为数组的长度是固定的。如果进行增删操作,则会产生UnsupportedOperationException的编译异常。如果数组中的元素都是对象,则变成集合时,数组中的元素就直接转为集合中的元素。如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在。(2) 集合转数组
2、查找
int binarySearch():使用二分搜索法来搜索指定的 byte 型数组,以获得指定的值
3、判断 boolean equals(Object[] a, Object[] a2):判断指定的两个数组是否相等
4、排序 void sort():对指定的数组按数字升序进行排序。
5、复制 <T> T[] copyOf(T[] original, int newLength) 复制指定的数组,截取或用 null 填充(如有必要),以使副本具有指定的长度。
6、填充 void fill(Object[] a, Object val) 将指定的 Object 引用分配给指定 Object 数组的每个元素。
7、其他方法 (1) toString(Object[] a):返回指定数组内容的字符串表示形式。 (2) int hashCode(Object[] a) 基于指定数组的内容返回哈希码。
package com.heima.test;import java.util.ArrayList;import java.util.Arrays;import java.util.List;public class Test { public static void main(String[] args) { System.out.println("1、将数组转成集合"); System.out.println("-------------------"); // asList()将数组转换为集合 List<Integer> list = Arrays.asList(87, 67, 65, 544); for (Integer i : list) { System.out.println(i); } System.out.println("2、二分查找"); System.out.println("-------------------"); // binarySearch()二分查找 int[] a = { 23, 45, 67, 8, 32, 45, 6, 7, 85, 54, 3, 432 }; int index = Arrays.binarySearch(a, 45); System.out.println(index); System.out.println("3、排序"); System.out.println("-------------------"); // sort()排序 System.out.println("排序前:" + Arrays.toString(a)); Arrays.sort(a); System.out.println("排序后:" + Arrays.toString(a)); System.out.println("4、集合转成数组"); System.out.println("-------------------"); // toArray()集合转成数组 ArrayList<String> al = new ArrayList<String>(); al.add("java"); al.add("javase"); al.add("php"); al.add("ruby"); al.add("android"); String[] str = al.toArray(new String[al.size()]); System.out.println(Arrays.toString(str)); }}运行结果:
新闻热点
疑难解答