行业资讯
【南京java开发】java集合框架是什么?(四)
2019-10-21

分享继续

 

泛型

 

    接下来,我要介绍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());

//因为StringInteger类都复写了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的父类型接收。

 *

 */

//创建一个StudentWorker都能接收的比较器。

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集合中的键进行指定顺序的排序。

 

    揭秘:HashSetTreeSet的底层是用HashMapTreeMap实现的,只操作键,就是Set集合。

 

    Map集合存储和Collection有着很大不同:

    Collection一次存一个元素;Map一次存一对元素。

    Collection是单列集合;Map是双列集合。

    Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应(映射)关系。

    特点:要保证map集合中键的唯一性。

 


 

…………………未完待续………………

 

更多内容尽在:www.njzhenghou.com

咨询热线
预约试听:025-86665061
咨询热线:025-58781701
技术热线:025-86665061
联系地址
江苏省南京市鼓楼区湖南路16号5楼
南京中心:鼓楼区湖南路16号5楼
武汉中心:江夏区光谷智慧园16栋
微信公众号
联系我们
咨询热线:025-86665061
友情链接
Copyright © 2018 正厚软件官网-专注项目研发 软件测试 JAVA实训 ISTQB考证 苏ICP备17057415号