分享继续
泛型
接下来,我要介绍JDK1.5以后出现的新技术,集合框架中的重点--泛型。
在JDK1.4版本之前,容器什么类型的对象都可以存储。但是在取出时,需要用到对象的特有内容时,需要做向下转型。但是对象的类型不一致,导致了向下转型发生了ClassCastException异常。为了避免这个问题,只能主观上控制,往集合中存储的对象类型保持一致。
JDK1.5以后,解决了该问题。在定义集合时,就直接明确集合中存储元素的具体类型。这样,编译器在编译时,就可以对集合中存储的对象类型进行检查。一旦发现类型不匹配,就编译失败。这个技术就是泛型技术。
package ustc.maxiaolun.generic.demo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GenericDemo {
public static void main(String[] args) {
List list = new ArrayList();
list.add("abc");
list.add(4);//list.add(Integer.valueOf(4));自动装箱.
for (Iterator it = list.iterator(); it.hasNext();) {
System.out.println(it.next());
//等价于:
Object obj = it.next();
System.out.println(obj.toString());
//因为String和Integer类都复写了Object类的toString方法,所以可以这么做。
String str = (String)it.next();
System.out.println(str.length());
//->java.lang.ClassCastException:java.lang.Integer cannot be cast to java.lang.String
}
//为了在运行时期不出现类型异常,可以在定义容器时,就明确容器中的元素的类型。-->泛型
List<String> list = new ArrayList<String>();
list.add("abc");
for (Iterator<String> it = list.iterator(); it.hasNext();) {
String str = it.next();
//class文件中怎么保证it.next()返回的Object类型一定能够变成String类型?
//虽然class文件中,没有泛型标识。但是在编译时期就已经保证了元素类型的统一,一定都是某一类元素。
//那么在底层,就会有自动的相应类型转换。这叫做泛型的补偿。
System.out.println(str.length());
}
}
}
泛型的擦除:
编译器通过泛型对元素类型进行检查,只要检查通过,就会生成class文件,但在class文件中,就将泛型标识去掉了。
泛型只在源代码中体现。但是通过编译后的程序,保证了容器中元素类型的一致。
泛型的补偿:
在运行时,通过获取元素的类型进行转换操作。不用使用者再强制转换了。
泛型的好处:
1.将运行时期的问题,转移到了编译时期,可以更好的让程序员发现问题并解决问题。
2.避免了强制转换、向下转型的麻烦。
package ustc.maxiaolun.generic.demo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GenericDemo2 {
public static void main(String[] args) {
//创建一个List集合,存储整数。List ArraytList
List<Integer> list = new ArrayList<Integer>();
list.add(5);//自动装箱
list.add(6);
for (Iterator<Integer> it = list.iterator(); it.hasNext();) {
Integer integer = it.next();//使用了泛型后,it.next()返回的就是指定的元素类型。
System.out.println(integer);
}
}
}
总结:泛型就是应用在编译时期的一项安全机制。泛型技术是给编译器使用的技术,用于编译时期,确保了类型的安全。
例子一枚:
package ustc.maxiaolun.domain;
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int compareTo(Person o) {
int temp = this.getAge() - o.getAge();
return temp == 0 ? this.getName().compareTo(o.getName()) : temp;
}
@Override
public int hashCode() {
final int NUMBER = 31;
return this.name.hashCode()+this.age*NUMBER;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if(!(obj instanceof Person))
throw new ClassCastException("类型不匹配");
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
}
package ustc.maxiaolun.generic.demo;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import ustc.maxiaolun.comparator.ComparatorByName;
import ustc.maxiaolun.domain.Person;
public class GenericDemo3 {
public static void main(String[] args) {
Set<String> set = new TreeSet<String>();
set.add("abcd");
set.add("aa");
set.add("nba");
set.add("cba");
for (String s : set) {
System.out.println(s);
}
//按照年龄排序
Set<Person> set = new TreeSet<Person>();
set.add(new Person("abcd",20));
set.add(new Person("aa",26));
set.add(new Person("nba",22));
set.add(new Person("cba",24));
for(Person p: set){
System.out.println(p);
}
//按照姓名排序
Set<Person> set = new TreeSet<Person>(new ComparatorByName());
set.add(new Person("abcd",20));
set.add(new Person("aa",26));
set.add(new Person("nba",22));
set.add(new Person("cba",24));
for(Person p: set){
System.out.println(p);
}
//HashSet不重复的实现
Set<Person> set = new HashSet<Person>();
set.add(new Person("aa",26));
set.add(new Person("abcd",20));
set.add(new Person("abcd",20));
set.add(new Person("nba",22));
set.add(new Person("nba",22));
set.add(new Person("cba",24));
for(Person p: set){
System.out.println(p);
}
}
}
泛型的表现:
泛型技术在集合框架中应用的范围很大。
什么时候需要写泛型呢?
当类中的操作的引用数据类型不确定的时候,以前用的Object来进行扩展的,现在可以用泛型来表示。这样可以避免强转的麻烦,而且将运行问题转移到的编译时期。
只要看到类或者接口在描述时右边定义<>,就需要泛型。其实是,容器在不明确操作元素的类型的情况下,对外提供了一个参数,用<>封装。使用容器时,只要将具体的类型实参传递给该参数即可。说白了,泛型就是,传递类型参数。
下面依次介绍泛型类、泛型方法、泛型接口。
1. 泛型类 --> 泛型定义在类上
首先,我们实现两个继承自Person类的子类,分别是Student类、Worker类,代码如下:
package ustc.maxiaolun.domain;
public class Student extends Person {
public Student() {
super();
}
public Student(String name, int age) {
super(name, age);
}
@Override
public String toString() {
return "Student [name="+getName()+", age="+getAge()+"]";
}
}
package ustc.maxiaolun.domain;
public class Worker extends Person {
public Worker() {
super();
}
public Worker(String name, int age) {
super(name, age);
}
@Override
public String toString() {
return "Worker [name=" + getName() + ", age=" + getAge() + "]";
}
}
需求:创建一个用于操作Student对象的工具类。对 对象进行设置和获取。
class Tool1{
private Student stu;
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
}
发现程序太有局限性了,可不可以定义一个可以操作所有对象的工具呢?类型需要向上抽取。当要操作的对象类型不确定的时候,为了扩展,可以使用Object类型来完成。
//JDk 1.4 类型向上抽取到Object-->向下转型在运行时期报ClassCastException。
class Tool2{
private Object obj;
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
但是这种方式有一些小弊端,会出现转型,尤其是向下转型容易在编译时期看不见错误、运行时期发生ClassCastExccption。
JDK1.5以后,新的解决方案:使用泛型。类型不确定时,可以对外提供参数。由使用者通过传递参数的形式完成类型的确定。
//JDK 1.5 在类定义时就明确参数。由使用该类的调用者,来传递具体的类型。
class Util<W>{//-->泛型类。
private W obj;
public W getObj() {
return obj;
}
public void setObj(W obj) {
this.obj = obj;
}
}
利用泛型类,我们就可以直接在编译时期及时发现程序错误,同时避免了向下转型的麻烦。利用上述泛型类工具,示例代码如下:
package ustc.maxiaolun.generic.demo;
import ustc.maxiaolun.domain.Student;
import ustc.maxiaolun.domain.Worker;
public class GenericDemo4 {
public static void main(String[] args) {
/*
* 泛型1:泛型类-->泛型定义在类上。
*/
//JDK 1.4
Tool2 tool = new Tool2();
tool.setObj(new Worker());
Student stu = (Student)tool.getObj();//异常-->java.lang.ClassCastException: Worker cannot be cast to Student
System.out.println(stu);
//JDK 1.5
Util<Student> util = new Util<Student>();
//util.setObj(new Worker());//编译报错-->如果类型不匹配,直接编译失败。
//Student stu = util.getObj();//避免了向下转型。不用强制类型转换。
System.out.println(stu);
//总结:什么时候定义泛型?
//当类型不明确时,就应该使用泛型来表示,在类上定义参数,由调用者来传递实际类型参数。
}
}
2. 泛型方法 --> 泛型定义在方法上。这里只需要注意一点,如果静态方法需要定义泛型,泛型只能定义在方法上。代码示例如下:
package ustc.maxiaolun.generic.demo;
public class GenericDemo5 {
public static void main(String[] args) {
/*
* 泛型2:泛型方法-->泛型定义在方法上。
*/
Demo1<String> d = new Demo1<String>();
d.show("abc");
//d.print(6);在类上明确类型后,错误参数类型在编译时期就报错。
Demo1<Integer> d1 = new Demo1<Integer>();
d1.print(6);
//d1.show("abc");
System.out.println("----------------");
Demo2<String> d2 = new Demo2<String>();
d2.show("abc");
d2.print("bcd");
d2.print(6);
}
}
class Demo1<W>{
public void show(W w){
System.out.println("show: "+w);
}
public void print(W w){
System.out.println("print: "+w);
}
}
class Demo2<W>{
public void show(W w){
System.out.println("show: "+w);
}
public <Q> void print(Q w){//-->泛型方法。某种意义上可以将Q理解为Object。
System.out.println("print: "+w);
}
/*
public static void show(W w){//报错-->静态方法是无法访问类上定义的泛型的。
//因为静态方法优先于对象存在,而泛型的类型参数确定,需要对象明确。
System.out.println("show: "+w);
}
*/
public static <A> void staticShow(A a){//如果静态方法需要定义泛型,泛型只能定义在方法上。
System.out.println("static show: "+a);
}
}
3. 泛型接口--> 泛型定义在接口上。
package ustc.maxiaolun.generic.demo;
public class GenericDemo6 {
public static void main(String[] args) {
/*
* 泛型3:泛型接口-->泛型定义在接口上。
*/
SubDemo d = new SubDemo();
d.show("abc");
}
}
interface Inter<T>{//泛型接口。
public void show(T t);
}
class InterImpl<W> implements Inter<W>{//依然不明确要操作什么类型。
@Override
public void show(W t) {
System.out.println("show: "+t);
}
}
class SubDemo extends InterImpl<String>{
}
/*
interface Inter<T>{//泛型接口。
public void show(T t);
}
class InterImpl implements Inter<String>{
@Override
public void show(String t) {
}
}
*/
泛型通配符<?>
可以解决当具体类型不确定的时候,这个通配符就是<?>
当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。
package ustc.maxiaolun.generic.demo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import ustc.maxiaolun.domain.Student;
public class GenericDemo7 {
public static void main(String[] args) {
/*
* 通配符<?> --> 相当于<? extends Object>
*/
List<String> list = new ArrayList<String>();
list.add("abc1");
list.add("abc2");
list.add("abc3");
printCollection(list);
Set<String> set = new HashSet<String>();
set.add("haha");
set.add("xixi");
set.add("hoho");
printCollection(set);
List<Student> list2 = new ArrayList<Student>();
list2.add(new Student("abc1",21));
list2.add(new Student("abc2",22));
list2.add(new Student("abc3",23));
list2.add(new Student("abc4",24));
//Collection<Object> coll = new ArrayList<Student>();-->wrong-->左右不一样,可能会出现类型不匹配
//Collection<Student> coll = new ArrayList<Object>();-->wrong-->左右不一样,可能会出现类型不匹配
//Collection<?> coll = new ArrayList<Student>();-->right-->通配符
printCollection(list2);
}
/*private static void printCollection(List<String> list) {
for (Iterator<String> it = list.iterator(); it.hasNext();) {
String str = it.next();
System.out.println(str);
}
}*/
/*private static void printCollection(Collection<String> coll) {
for (Iterator<String> it = coll.iterator(); it.hasNext();) {
String str = it.next();
System.out.println(str);
}
}*/
private static void printCollection(Collection<?> coll) {//在不明确具体类型的情况下,可以使用通配符来表示。
for (Iterator<?> it = coll.iterator(); it.hasNext();) {//技巧:迭代器泛型始终保持和具体集合对象一致的泛型。
Object obj = it.next();
System.out.println(obj);
}
}
}
泛型的限定
<? extends E>:接收E类型或者E的子类型对象。上限。一般存储对象的时候用。比如添加元素addAll。
<? super E>:接收E类型或者E的父类型对象。下限。一般取出对象的时候用。比如比较器。
示例代码:
package ustc.maxiaolun.generic.demo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import ustc.maxiaolun.domain.Person;
import ustc.maxiaolun.domain.Student;
import ustc.maxiaolun.domain.Worker;
public class GenericDemo8 {
public static void main(String[] args) {
/*
* 泛型的限定
*/
List<Student> list = new ArrayList<Student>();
list.add(new Student("abc1",21));
list.add(new Student("abc2",22));
list.add(new Student("abc3",23));
list.add(new Student("abc4",24));
printCollection(list);
Set<Worker> set = new HashSet<Worker>();
set.add(new Worker("haha",23));
set.add(new Worker("xixi",24));
set.add(new Worker("hoho",21));
set.add(new Worker("haha",29));
printCollection(set);
}
/*
* 泛型的限定:
* ? extends E :接收E类型或者E的子类型。-->泛型上限。
* ? super E :接收E类型或者E的父类型。-->泛型下限。
*/
private static void printCollection(Collection<? extends Person> coll) {//泛型的限定,支持一部分类型。
for (Iterator<? extends Person> it = coll.iterator(); it.hasNext();) {
Person obj = it.next();//就可以使用Person的特有方法了。
System.out.println(obj.getName()+":"+obj.getAge());
}
}
}
程序结果:
练习1:演示泛型上限在API中的体现。我们这里使用的是TreeSet的构造函数:TreeSet<E>(Collection<? extends E> coll)
package ustc.maxiaolun.generic.demo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.TreeSet;
import ustc.maxiaolun.domain.Person;
import ustc.maxiaolun.domain.Student;
import ustc.maxiaolun.domain.Worker;
public class GenericDemo9 {
public static void main(String[] args) {
/*
* 演示泛型限定在API中的体现。
* TreeSet的构造函数。
* TreeSet<E>(Collection<? extends E> coll);
*
* 什么时候会用到上限呢?
* 一般往集合存储元素时。如果集合定义了E类型,通常情况下应该存储E类型的对象。
* 对于E的子类型的对象,E类型也可以接受(多态)。所以这时可以将泛型从E改为 ? extends E.
*/
Collection<Student> coll = new ArrayList<Student>();
coll.add(new Student("abc1",21));
coll.add(new Student("abc2",22));
coll.add(new Student("abc3",23));
coll.add(new Student("abc4",24));
Collection<Worker> coll2 = new ArrayList<Worker>();
coll2.add(new Worker("abc11",21));
coll2.add(new Worker("abc22",27));
coll2.add(new Worker("abc33",35));
coll2.add(new Worker("abc44",29));
TreeSet<Person> ts = new TreeSet<Person>(coll);//coll2 也可以传进来。
ts.add(new Person("abc8",21));
ts.addAll(coll2);//addAll(Collection<? extends E> c);
for (Iterator<Person> it = ts.iterator(); it.hasNext();) {
Person person = it.next();
System.out.println(person.getName());
}
}
}
//原理
class MyTreeSet<E>{
MyTreeSet(){
}
MyTreeSet(Collection<? extends E> c){
}
}
练习2:演示泛型下限在API中的体现。同样,我们这里使用的是另一个TreeSet的构造函数:TreeSet<E>(Comparator<? super E> comparator)
package ustc.maxiaolun.generic.demo;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
import ustc.maxiaolun.domain.Person;
import ustc.maxiaolun.domain.Student;
import ustc.maxiaolun.domain.Worker;
public class GenericDemo10 {
public static void main(String[] args) {
/*
* 演示泛型限定在API中的体现。
* TreeSet的构造函数。
* TreeSet<E>(Comparator<? super E> comparator)
*
* 什么时候用到下限呢?
* 当从容器中取出元素操作时,可以用E类型接收,也可以用E的父类型接收。
*
*/
//创建一个Student、Worker都能接收的比较器。
Comparator<Person> comp = new Comparator<Person>() {//匿名内部类
@Override
public int compare(Person o1, Person o2) {//每次都是容器中的两个元素过来进行比较。
int temp = o1.getAge()-o2.getAge();
return temp==0?o1.getName().compareTo(o2.getName()):temp;
}
};
TreeSet<Student> ts = new TreeSet<Student>(comp);
ts.add(new Student("abc1",21));
ts.add(new Student("abc2",28));
ts.add(new Student("abc3",23));
ts.add(new Student("abc4",25));
TreeSet<Worker> ts1 = new TreeSet<Worker>(comp);
ts1.add(new Worker("abc11",21));
ts1.add(new Worker("abc22",27));
ts1.add(new Worker("abc33",22));
ts1.add(new Worker("abc44",29));
for (Iterator<? extends Person> it = ts1.iterator(); it.hasNext();) {
Person p = it.next();//多态
System.out.println(p);
}
}
}
//原理
class YouTreeSet<E>{
YouTreeSet(Comparator<? super E> comparator){
}
}
泛型的细节:
1.泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型;
2.使用带泛型的类创建对象时,等式两边指定的泛型必须一致;
原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了;
3.等式两边可以在任意一边使用泛型,在另一边不使用(考虑向后兼容);
ArrayList<String> al = new ArrayList<Object>(); //错
//要保证左右两边的泛型具体类型一致就可以了,这样不容易出错。
ArrayList<? extends Object> al = new ArrayList<String>();
al.add("aa"); //错
//因为集合具体对象中既可存储String,也可以存储Object的其他子类,所以添加具体的类型对象不合适,类型检查会出现安全问题。
// ?extends Object 代表Object的子类型不确定,怎么能添加具体类型的对象呢?
public static void method(ArrayList<? extends Object> al) {
al.add("abc"); //错
//只能对al集合中的元素调用Object类中的方法,具体子类型的方法都不能用,因为子类型不确定。
}
Map<K, V>接口
java.util.Map<K,V>接口,将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。要保证键的唯一性-->Set。值可以重复-->Collection。
Map:双列集合,一次存一对,键值对。
|--Hashtable:底层是哈希表数据结构,是线程同步的,不允许存储null键,null值。
|--Properties:用来存储键值对型的配置文件的信息,可以和IO技术相结合。
|--HashMap:底层是哈希表数据结构,是线程不同步的,允许存储null键,null值。替代了Hashtable。
|--TreeMap:底层是二叉树结构,线程不同步的。可以对map集合中的键进行指定顺序的排序。
揭秘:HashSet、TreeSet的底层是用HashMap、TreeMap实现的,只操作键,就是Set集合。
Map集合存储和Collection有着很大不同:
Collection一次存一个元素;Map一次存一对元素。
Collection是单列集合;Map是双列集合。
Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应(映射)关系。
特点:要保证map集合中键的唯一性。
…………………未完待续………………
更多内容尽在:www.njzhenghou.com