[置顶] 黑马程序员_集合框架(泛型)

时间:2022-02-20 19:20:42
 ----------------- android培训java培训、期待与您交流! ---------- 泛型:jdk1.5版本,出现的技术。是一个安全机制。 

泛型技术的由来:
  • 集合中可以存储任意类型对象,但是在取出时,如果要使用具体对象的特有方法时,需要进行向下转型,
  • 如果存储的对象类型不一致,在转型过程中就会出现ClassCastException异常。
  • 这样就给程序带来了不安全性。

在jdk1.5以后就有了解决方案。就是泛型技术。
  • 解决方案就是,在存储元素时,就不允许存储不同类型的元素。
  • 存储了就编译失败。 所以就需要在存储元素时,在容器上明确具体的元素类型。这其实和数组定义很像。
好处:
  • 将运行时期的ClassCastException异常转移到了编译事情,进行检查,并以编译失败来体现。 
  • 这样有利于程序员尽早解决问题。 避免了向下转型(强转)的麻烦
什么时候写泛型呢?
  • 先简单理解:只要在使用类或者接口时,该类或者接口在api文当描述时都带着<>就需要在使用时,定义泛型。
  • 其实,泛型无非就是通过<>定义了一个形式参数。专门用于接收具体的引用类型。
  • 在使用时,一定要传递对应的实际参数类型。集合中泛型的应用特别多见。
泛型的擦除:
  • 泛型技术是用于编译时期的技术,编译器会按照<>中的指定类型对元素进行检查,
  • 检查不匹配,就编译失败,匹配,就编译通过,通过后,生产的class文件中是没有泛型的。这就成为泛型的擦除。
泛型的补偿:
  • 运行时,可以根据具体的元素对象获取其具体的类型。并用该类型对元素进行自动转换。
  • 泛型对对于程序的设计也有一定优化动作。

接下来就有了新的思考方法,这其实源于jdk1.5的泛型技术。
  • 当一个类要操作的引用数据类型不确定的时候,可以将该类型定义一个形参。
  • 用到的这类时,有使用者来通过传递类型参数的形式,来确定要操作的具体的对象类型。
  • 意味着在定义这个类时,需要在类上定义形参。用于接收具体的类型实参。
  • 这就是将泛型定义在类上。这就是泛型类。

什么时候使用泛型类呢?只要类中操作的引用数据类型不确定的时候,就可以定义泛型类
有了泛型类,省去了曾经的强转和类型转换异常的麻烦。
class Util<QQ>{
private QQ obj;
public void setObject(QQ obj){
this.obj = obj;
}
public QQ getObject(){
return obj;
}
}

定义一个工具类对worker对象也可以对Student对象进行操作,设置和获取。甚至于任意对象。
之所以定义Object类型的对象是因为不能确定要操作什么类型的具体对象。
弊端是要使用对象的特有方法需要向下转型,并且问题发生,会出现的运行时期,而不是编译时期,不已解决。

class Tool
{
private Object obj;
public void setObject(Object obj)
{
this.obj = obj;
}

public Object getObject()
{
return obj;
}
}

自定义泛型和方法演示代码:
package com.itheima.collection;

public class GenericDemo
{
public static void main(String[] args)
{
GenDemo<String> str = new GenDemo<String>();
str.speak("hello,there!");
str.speakOther(new Integer(5));
GenDemo.hello("thanks!");

GenDemo<Integer> in = new GenDemo<Integer>();
in.speak(new Integer(6));
in.speakOther(new Integer(9));
in.speakOther("Strings also can");
}
}

// 自定义泛型类
class GenDemo<E>
{
public void speak(E e)
{
System.out.println("说了什么?"+e.toString());
}

// 这个speakOther方法,要操作的类型不确定的,但是一不一定和调用该方法的对象指定的类型一致。
// 故使用泛型方法
public <F> void speakOther(F f)
{
System.out.println("说点不同类型的:"+f.toString());
}

// 静态方法不能访问类上定义的泛型,如果需要泛型,该泛型只能定义在方法上。
public static <S> void hello(S s)
{
System.out.println("静态方法说:"+s.toString());
}
}
运行结果如下:
说了什么?hello,there!
说点不同类型的:5
静态方法说:thanks!
说了什么?6
说点不同类型的:9
说点不同类型的:Strings also can

泛型的通配符:?
  • 当操作的不同容器中的类型都不确定的时候,而且使用的都是元素从Object类中继承的方法。
  • 这时泛型就用通配符?来表示即可。
  • 我们就通过Collection中的containsAll方法来对 ? 应用进行一下 体现。 
  • boolean  containsAll(Collection<?>)
  • 注意:如果我们要定义这样类似的方法怎么定义呢?
interface Collection<E>
{
public boolean add(E e);
public boolean containsAll(Collection<?> coll);
}

Collection<String> c1 = new ArrayList<String>();
c1.add("abc1");
c1.add("abc2");

Collection<String> c2 = new ArrayList<String>();
c2.add("abc1");
c1.containsAll(c2);//这是必须可以的。

Collection<Integer> c3 = new ArrayList<Integer>();
c3.add(5);

c1.containsAll(c3);//注意:containsAll方法使用的内部原理就是通过元素的equals进行相同的判断。
//equals方法的参数是Object.所以可以接受任意类型的对象。意味着: "abc".equals(new Integer(5));
演示代码:
package com.itheima.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class GenericDemo2
{
public static void main(String[] args)
{
ArrayList<String> als = new ArrayList<String>();
als.add("als1");
als.add("als2");
als.add("als3");
//System.out.println(als);

ArrayList<Integer> ali = new ArrayList<Integer>();
ali.add(3);
ali.add(4);
ali.add(7);
//System.out.println(ali);
printColl(als);
printColl(ali);
}

public static void printColl(Collection<?> al)
{
for (Iterator<?> it = al.iterator();it.hasNext();)
{
System.out.println(it.next().toString());
}
}
}

泛型的限定:
  • 明确具体类型代表一个类型。
  • 明确?代表所有类型。
  • 能不能对操作的类型限制在一个范围之内呢?
  • 比如:定义一个功能,只操作Person类型或者Person的子类型。 
  • 这时可以用 ? extends E:接收E类型或者E的子类型。这就是上限。 
  • 下限:? super E: 接收E类型或者E的父类型。
  • 什么时候使用上限呢?一般情况下,只要是往容器中添加元素时,使用上限。  ? extends E
  • 什么时候使用下限呢?一般清况下,只要是从容器中取出元素时,是用下限。 ? super E
//演示下限。
private static void superEDemo() 
{
TreeSet<Student> ts = new TreeSet<Student>(new CompByName());
ts.add(new Student("lisi1",21));
ts.add(new Student("lisi0",24));
ts.add(new Student("lisi2",22));

Iterator<Student> it = ts.iterator();
while(it.hasNext()
{
Person p = it.next();
System.out.println(p.getName()+":::student:::"+p.getAge());
}
TreeSet<Worker> ts1 = new TreeSet<Worker>(new CompByName());
ts1.add(new Worker("wangwu8",21));
ts1.add(new Worker("wangwu1",24));
ts1.add(new Worker("wangwu5",22));

Iterator<Worker> it1 = ts1.iterator();
while(it1.hasNext())
{
Person p = it1.next();
System.out.println(p.getName()+":::worker:::"+p.getAge());
}
}

//演示上限。
private static void extendsEDemo() 
{
Collection<Student> coll = new ArrayList<Student>();
coll.add(new Student("lisi1",21));
coll.add(new Student("lisi0",24));
coll.add(new Student("lisi2",22));

TreeSet<Person> ts = new TreeSet<Person>(coll);//将coll中的元素存储到TreeSet集合。
ts.add(new Person("wangwu",23));

Iterator<Person> it = ts.iterator();
while(it.hasNext())
{
Person p = it.next();
System.out.println(p.getName()+"::::::"+p.getAge());
}
}

  • 姓名排序。比较器是将容器中的元素取出来进行比较、 
  • 发现,如果对学生或者工人都进行姓名的排序,用的都是父类Person中的内容。
  • 不同的子类型需要定义不同的比较器就很麻烦,复用性差。
  • 定义Person类型的比较器行不?
  • 比较器是将容器中的元素取出来进行比较。所以无论你的元素是Student类型还是Worker类型。;
  • 我都可以用Person类型来接收。而Person类型是两者父类型。 
class CompByName implements Comparator<Person>
{
public int compare(Person o1, Person o2)
{
int temp = o1.getName().compareTo(o2.getName());
return temp==0? o1.getAge()-o2.getAge():temp;
}
}

jdk1.5以后 出现的新方式。这种升级就是简化书写。
Collection有了一个父接口,Iterable
该接口封装了iterator方法,同时提供了一个新的语句。foreach语句。
格式:
for(变量 : Collection集合or数组)
{
}
  • foreach循环简化了迭代器。迭代器还用吗?用,因为迭代过程中还可以remove().一般只对基本遍历简化使用。
  • foreach循环特点:必须明确被遍历的目标。没有目标没用。目的只能是数组或者Collection集合。如果要对数组的中的元素进行特定操作时,建议传统for循环,通过角标完成。 

ArrayList<String> al = new ArrayList<String>();

al.add("abc1");
al.add("abc2");
al.add("abc3");
al.add("abc5");
for(String s : al)
{
System.out.println(s);
}

int[] arr = {7,23,1,67,90,8};
for(int i : arr)//只为遍历元素,无法操作角标。
{
System.out.println("i="+i);
}