Java基础18--泛型--工具类--JDK1.5新特性

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xzm_rainbow/article/details/18080191

18-1,泛型-概述

1,泛型是JDK1.5出现的新技术,新技术的出现是为了解决问题。

2,泛型可以用于明确一个集合中存储什么类型的元素

ArrayList<String> al = new ArrayList<String>();
al.add("abc");//正确
//al.add(3);//编译直接报错
//在迭代器上加上泛型,下面取出来的时候就不用强转了
Iterator<String> it = al.iterator();
while(it.hasNext()) {
	//在这里不用强转了,因为从it中取出的肯定是String
	String str = it.next();
	System.out.println(str);
}

3,好处

(1)将运行时间的问题ClassCastException转到了编译时期。

(2)避免了强制转换的麻烦。

4,泛型什么时候用呢?

当操作的引用数据类型不确定的时候,就用泛型,将要操作的引用数据类型写在<>中即可。其实<>就是一个用于接收具体引用数据类型的参数范围。

在程序中,只要用到了带有<>的类型或者接口,就要明确传入的具体引用数据类型。

也可以同时接受多个数据类型:

class Tool<e1,e2,e3,e4>
Tool<String,Demo,Person,Integer> t = new Tool<String,Demo,Person,Integer>();

18-2,泛型-擦除&补偿

1,泛型解决了编译时期的安全问题,是给编译器使用的技术。

2,运行时,会将泛型去掉,生成的class文件中是不带有泛型的,这个称为泛型的擦除。

为什么要擦除呢?

因为为了兼容运行时的类加载器,因为泛型是JDK1.5的新特性,如果class文件中加上泛型的话,类加载器会不认识,若想要类加载器认识泛型,类加载器也要升级,为了避免升级类加载器的麻烦,泛型将交由编译器处理,编译通过后就会把他删去,使之前的类加载器也能运行它。

3,泛型的补偿:

擦除后,如果对元素进行取出的赋值动作,如:String str =it.next();是否需要加上强转呢?答案是不用了,首先,加上泛型且编译通过就能保证集合中的数据是指定的类型了,所以认为不会出现规定类型以外的元素。其实,在运行时,是自动添加了一步动作,通过用getClass()方法获取元素的类型再对其进行一次转换动作,就不用使用者在强制转换了。

 

18-3,泛型-在集合中的应用

1,以TreeSet为例,TreeSet具备比较功能,但存入的类型必须实现Comparable接口并实现了里面的compareTo方法才可以;或者定义一个比较器,实现Comparator接口并实现里面的compare方法也可以。定义TreeSet时,如果不加泛型Eclipse等IDE会有黄色波浪线提示,加上泛型,黄色波浪线即可消失。

2,上一章提到的Person类实现Comparable接口时Comparable接口也需要指定泛型,如:Comparable<Person>,指定后,覆写的compareTo方法中的参数就可以不写Object了,直接写Person就可以,方法体中的代码也不用强制向下转型了。Comparator接口也要加上泛型,它里面的compare方法的两个参数也直接接受Person类型就可以了。例如:

class Person implements Comparable<Person> {
	...code...
	public int compareTo(Person p) {
		//Person p = (Person)p;//强转可以省去,因为定义了泛型
		int temp = this.age = p.age;
		return temp == 0 ? this.name.compareTo(p.name) : temp;
	}
	...code...
}

18-4,泛型类

1,以前定义工具类的时候这么定义:

public class Tool {
	private Object object;
	public Object getObject() {
		return object;
	}
	public void setObject(Object object) {
		this.object = object;
	}
}

别的类用这个工具类的时候,可以这样做:

Tool tool = new Tool();
/*
这里因为没有Worker这个类,所以是错误的。
Worker为传入的错误值,但编译不会报错,因为Tool中接收的是Object类型,
下面的语句必须做强转才能匹配类型,因为把Worker向上提升为了Object。
*/
tool.setObject(new Worker());
Student stu = (Student)tool.getObject();

工具类是针对所有类型的,而任何一个类都是Object的子类,所以根据多态的特性,通过将参数定义成Object我们就可以使这个方法接收所有类型的参数,在没有泛型的时候我们就只能这么做。

在JDK1.5之后,使用泛型来接收类中要操作的引用数据类型,这就是泛型类。

泛型类什么时候用呢?

当类中的操作的引用数据类型不确定的时候,就是用泛型来表示。

例如:

public class Tool<Q> {
	private  Q q;
	public Q getObect() {
		return q;
	}
	public void setObject(Q object) {
		this.q = object;
	}
}

有泛型后,在别的类用到工具类的时候,可以这么做:

Tool<Student> tool = new Tool<Student>();
//tool.setObject(new Worker());//编译报错,因为有了泛型指定的Student,所以别的不能进入集合。
tool.setObject(new Student());
Student stu = tool.getObject();

 

18-5,泛型方法

1,在18-4中有泛型的Tool类中加入泛型方法,Tool<QQ>

public class Tool<Q> {
	private  Q q;
	public Q getObect() {
		return q;
	}
	public void setObject(Q object) {
		this.q = object;
	}
	/*
	如果方法不加泛型会报错,因为Tool明确只能接收QQ类型,
	所以不能操作W类型,要想让其中一个方法操作W类型,
	必须在返回值类型前加上泛型<W>。
	*/
	public <W> void show(W str) {
		System.out.println("show" + str.toString());
	}
	/*
	另外,print接收的是字符串,那么能不能打印字符串的长度呢?
	答案是不能的,因为QQ str = "abc"(这里假设传入abc),将"abc"提升为QQ类型,
	QQ类型不知道String类型中的特有方法length(),所以不能打印。但由于QQ也
	继承自Object,所以可以使用Object中的方法。
	*/
	public void print(QQ str) {
		System.out.println("print" + str);
	}
}

调用的时候:

Tool<String> tool = new Tool<String>();
tool.show(new Integer(4));//因为show加了泛型,所以可以接受Tool定义的String以外的类型。
tool.show("abc");//符合Tool的泛型。
tool.print("haha");//print方法没有加泛型,直接匹配。

2,当方法静态时,不能访问类上定义的泛型,如果静态方法是用泛型,只能将泛型定义在方法上。

如果在Tool<QQ>中定义方法:

//如果此方法上不加泛型,参数Y obj会编译报错,泛型需要对象来明确,
//如Tool<String> tool = new Tool<String>();静态是不需要对象的,所以必须在void前加泛型。
public static <Y> void method(Y obj) {
	System.out.println("method" + obj);
}

调用这个方法的时候:

Tool.method("bcd");//bcd
Tool.method(new Integer(9));//9

 

18-6,泛型接口

1,泛型接口就是将泛型定义在接口上。

//定义泛型时一般不明确具体类型,可以接受任意指定类型的对象,在创建对象的时候明确。
interface Inter<T> {
	public void show(T t);
}
//实现接口可以明确要操作的类型,那么在这个类上直接加上指定类型的泛型即可。
class ImterImpl<String> implements Inter<String> {
	public void show(String str) {
		System.out.println("show:" + str);
	}
}

调用show方法时:

InterImpl in = new InterImpl();//这样new对象时就不用加泛型了。
in.show("abc");//这样调用就行

 

2,在实现接口的类中还是不能明确使用的类型:

class InterImpl<Q> implements Inter<Q> {
	public void show(Q q) {
		System.out.println("show:" + q);
	}
}

在new这个InterImpl类的时候才能明确使用的类型,那么就定义成上述的样子,在new对象的时候再明确其泛型。

InterImpl<Integer> in = new InterImpl<Integer>();
in.show(5);

这样一来,in对象就只能接收Integer类型的对象了。

 

18-7,泛型的界定-上限

1,有多个集合,要想用Iterator取出多个集合中的数据,这时不要定义多个Iterator,要提高代码的复用性,就要单独定义一个方法来进行迭代。例如有这么两个集合:

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

ArrayList<Integer> al2 = newArrayList<Integer>();

假如这两个集合中已经有了数据,这时要取出里面的数据,定义一个迭代方法:

//这里接收的是Collection,所以可以迭代List,Set等所有集合,但这里也定义了泛型,所以只能接受String了。
public static void printCollection(Collection<String> al) {
	Iterator<String> it = al.iterator();
	while(it.hasNext()) {
		System.out.println(it.next());
	}
}

这是要考虑到接收的集合中的元素类型是不确定的,怎样接收不同类型的元素呢?可以用泛型通配符表示。

2,泛型的通配符---?,表示未知类型。

public static void printCollection(Collection<?> al) {
	Iterator<?> it = al.iterator();
	while(it.hasNext()) {
		//由于参数类型不确定,所以直接用it.next()就OK了。
		System.out.println(it.next());
	}
}

也可以在方法上加上泛型:

public static <T> void printCollection(Collection<T> al)
Iterator<T> it = al.iterator();
while(it.hasNext()) {
	T str = it.next();
	System.out.println(str);
}

但是这么做有些麻烦。但也有另外的好处,如果这么写:

public static <T> T printCollection(Collection<T> al) {
	Iterator<T> it = al.iterator();
	T t = it.next();
	return t;
}

这样就可以对传入的T返回其对象,并进行操作,不过主要还是用通配符--?。

3,需求:迭代方法中,只能接收Person及其子类。

分析:首先?就不行了,因为不能接受所有对象。

例如Person的子类有Worker,Student,若要迭代这两个而不能迭代Person以外的类(例如String),可以这么写:

//<? extends Person>表示泛型定义是所有继承Person的类型或Person类型
public static void printCollection(Collection<? extends Person> al){
	Iterator<? extends Person> it = al.iterator();
	while(it.hasNext()) {
		Person p = it.next();//已经可以明确接收的是Person或其子类
		System.out.println(p.getName() + ":" + p.getAge());
	}
}

若想用此方法迭代String类型的对象,则在编译时会直接报错,因为String不是Person的子类,这就是泛型的上限,其实,泛型写<?>是<? extends Object>的缩略。

 

18-8,下限

1,定义方式:<? super E>表示指定的类型必须是E或E的父类。

例如Student和Worker都是Person的子类,

定义了三个集合:

ArrayList<Person> al1 = new ArrayList<Person>();
ArrayList<Student> al2 = new ArrayList<Student>();
ArrayList<Worker> al3 = new ArrayList<Worker>();

然后定义一个方法:

public static void printCollection(Collection<? super Student> al) {
	Iterator<? super Student> it = al.iterator();
	while(it.hasNext()) {
		System.out.println(it.next());
	}
}

这时一个迭代集合元素的方法,泛型类型指的是Student或Student的父类,由于Student继承自Person,Worker也继承自Person,所以这里如果传入Worker是不行的,会报错。这就是泛型的下限。

 

18-9,选择使用集合的技巧

1,几何元素需要唯一么?

    |--需要:用Set

       |--需要指定顺序么?

           |--需要:TreeSet

           |--不需要:HashSet

           |--但是想要一个和存储一致的顺序(有序):LinkedHashSet

    |--不需要:用List

       |--需要频繁增删么?

           |--需要:LinkedList

           |--不需要:ArrayList

2,如何记住每个容器的结构和所属的体系呢?看名字!

List

 |--ArrayList

 |--LinkedList

Set

 |--HashSet

 |--TreeSet

后缀名就是该集合所属的体系。

前缀名就是该集合的数据结构。

看到array:就要想到数组,查询快,有角标。

看到Link:就要想到链表,增删快,想到add、get、remove方法。

看到hash:就要想到哈希表,唯一性,就要想到元素需要覆盖hashCode和equals方法。

看到tree:就要想到二叉树,想到需要排序,两个接口,Comparable,Comparator。

而且通常这些常用的集合容器都是不同步的。

 

18-10,工具类-Collections排序

1,java.util.Collections

此类完全由在Collection上进行操作或返回Collection的静态方法组成。

2,Collections是集合框架的工作类,里面的方法都是静态的。

static <T extendsComparable<?  super T>> voidsort(List<T> list)

根据元素的自然排序,对指定列表进行升序排序,列表中的所有元素都必须实现Comparable接口,列表中的所有元素都必须是相互可以比较的,即类型必须相互匹配才行。

演示排序方法示例:

public static void demo_1() {
	List<String> list = new ArrayList<String>();
	list.add("abcde");
	list.add("cba");
	list.add("aa");
	list.add("zzz");
	list.add("cba");
	list.add("nbaa");
	System.out.println(list);
	//排序
	Collections.sort(list);//String实现了Comparable接口,具备可比性
	System.out.println(list);
}

若要按照长度排序,可以通过sort(List<T>list,Comparator<? Super T> c)带比较器的sort方法实现,这需要定义一个类,并实现Comparator接口,并实现里面的compare方法。例如:

public class ComparatorByLength implements Comarator<String> {
	public int compare(String o1,String o2) {
		int temp = o1.length() - o2.length();
		return temp == 0 ? o1.compareTo(o2) : temp;
	}
}

然后调用方法Collections.sort(list,newComparatorByLength());即可。

 

18-11,折半&最值

1,static <T> int binarySearch(List<?extends Comparable<? super T>> list, T key)

这个方法是使用二进制搜索法来搜索指定列表,以获得指定对象,在进行此调用之前,必须根据列表元素的自然顺序对列表进行升序排序,其实就是二分查找法。

2,static <T> int binarySearch(List<?extends T> list,T key,Comparator<? super T> c)

与上面的方法的不同之处在于,用指定的比较器对集合中的元素进行升序排序。

3,折半:

public class Demo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("abcde");
		list.add("cba");
		list.add("aa");
		list.add("zzz");
		list.add("cba");
		list.add("nbaa");
		//排序
		Collections.sort(list);
		int index = Collections.binarySearch(list,"cba");//index是2
	}
}

4,最大值

static T max(Collection<? extendsT> coll)

根据元素的自然顺序,返回给定collection的最大元素,collection中的元素必须具备比较功能,该方法是依据Comparable比较的。

static T max(Collection<? extendsT> coll,Comparator<? super T> comp)

根据指定的比较器产生的顺序,返回给定collection的最大值。

例如:

String max = Collections.max(list,newComparatorByLength())

返回最长的字符串。

 

18-12,逆序&替换

1,static <T> Comparator<T>reverseOrder()

返回一个比较器,它强行反转实现Comparable接口那些对象collection上的自然顺序,他是根据Comparable比较的。

TreeSet<String> ts = new TreeSet<String>(Collections.reverseOrder(newComparatorByLength()))

这个方法强行反转比较器指定排序后的反转。

2,替换

public class Demo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("abcde");
		list.add("cba");
		list.add("aa");
		//将list集合中的"cba"替换成"nba"
		//原理:list.set(indexOf("cba"),"nba")
		Collections.replaceAll(list,"cba","nba");
	}
}

 

18-13,其他方法,将非同步集合转成同步集合的方法

1,fill方法,可将集合中的所有的值都重新赋值(重新初始化)

public class Demo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("abcde");
		list.add("cba");
		list.add("aa");
		Collections.fill(list, "cc");
		System.out.println(list);//[cc,cc,cc] 将所有元素初始化为cc
	}
}

2,Collections.shuffle(list);//将集合中的元素随机重新排列

3,static <T> ArrayList<T>list(Enumeration<T> e)

返回一个数组列表,它按返回顺序包含指定枚举返回的元素,将枚举转换成ArrayList。

4,static <T> Enumeration<T>enumeration(Collection<T> c)

将指定集合转成枚举。

5,static <T> Enumeration<T>synchronizedList(List<T> list)

返回指定列表支持的(线程安全的)列表。

除此之外可以将Collection、Map、Set,等转成线程安全的。

实现原理:

List list = new ArrayList();//是非同步的

list = MyCollections.synList(list);//返回一个同步的list

给非同步的集合加锁:

//保证在添加时不能删除,删除时不能添加
class MyCollections {
	public static List synList(List list) {
		return new MyList(list);
	}
	private class MyList implements List {
		private List list;
		private static final Object lock = new Object();
		MyList(List list) {
			this.list = list;
		}
		public boolean add(Object obj) {
			synchronized(lock) {
				return list.add(obj);
			}
		}
		public boolean remove(Object obj) {
			synchronized(lock) {
				return list.remove(obj);
			}
		}
	}
}

 

18-14,Arrays数组工具类方法介绍

1,binarySearch(...)二分查找法,返回索引。

2,equals(...)覆写了Object中的equals,比较内容是否相同,即长度相同且对应位上的元素相同。

3,static void fill(byte[] a,byte val)将val赋给byte[]中的每一个位置。

4,sort(...)将传入的数组排序。

5,toString(),覆写了Object中的toString(),直接打印数组中的元素。

6,toString()的经典实现:

public static String myToString(int[] a) {
	int iMax = a.length - 1;
	if(iMax == -1) {
		return "[]";
	}
	StringBuilder b = new StringBuilder();
	b.append('[');
	for(int i=0;;i++) {
		b.append(a[i]);
		if(i == iMax) {
			return b.append(']').toString();
		}
		b.append(",");
	}
}

 

18-15,Arrays-asList方法

1,static<T> List<T> asList(T... a)

返回一个受指定数组支持的固定大小的列表。

用asList的原因:Arrays中提供的方法也是相当有限的,如果把数组转换成集合操作就方便多了,比如判断一个元素是否在数组中,Arrays中没有这个方法,就需要自己写一个判断方法,但是如果将这个数组转换成List就可以直接用contains判断了。

2,好处:可以使用集合中的方法操作数组中的元素。

注意:数组的长度是固定的,所以对于集合的增删方法是不能使用的,否则会发生UnsupportedOperationException异常,只要不改变集合长度的方法都可以使用。

但是可以使用set方法用指定元素替换指定的元素。

3,如果数组中的元素是对象,那么转成集合时,直接将数组中的元素作为集合中的元素进行存储。如果数组中的数据是基本数据类型,那么会将该数组作为集合中的元素进行存储。

例如:

int[] arr = {31,11,51,61};

List<int[]> list =Arrays.asList(arr);

System.out.prtinln(list);//[[I@C17164

这时list集合的长度是1,打印结果是打印数组对象。

但是如果定义一个Integer类型数组,即非基本数据类型数组:

Integer[] arr = {31,11,51,61};

List<int[]> list =Arrays.asList(arr);

System.out.println(list);//打印[31,11,51,61]

说明,如果存入非基本数据类型数据,打印的是每个对象的toString()。

 

18-16,Collection-->toArray方法

1,如何将集合转化成数组呢?

使用的是Collection接口中的toArray方法。

2,集合转成数组:可以对集合中的元素操作的方法进行限定,不允许对其进行增删。

3,toArray方法需要传入一个指定类型的数组。

长度该如何定义呢?

如果长度小于集合的size,那么该方法会创建一个同类型并和集合相同size的数组。

如果长度大于集合的size,那么该方法就会使用指定的数组,存储集合的元素,其他位置默认为null。

所以建议,最好将长度指定为集合的size。

List<String> list = new ArrayList<String>();
list.add("abc1");
list.add("abc2");
list.add("abc3");
String[] arr = list.toArray(new String[list.size()]);
System.out.println(Arrays.toString(arr));

 

18-17,JDK1.5新特性-ForEach循环

1,Collection的父接口是Iterable接口,该接口中只有一个iterator迭代器方法,在JDK1.5才出现,因为以后还有可能出现像Collection的顶层父类,都希望他们具备迭代功能,所以抽取出来,提高了扩展性。

2,ForEach循环,增强For循环

格式:for(类型 变量 :Collection集合或数组){...}

 

传统for和高级for的区别:

(1)传统for可以完成对语句执行很多次,因为可以定义控制循环的增量和条件。

(2)高级for是一种简化形式,它必须有被遍历的目标。该目标要么是数组,要么是Collection单列集合。

对数组的遍历如果仅仅是获取数组中的元素,可以使用高级for。

如果要对数组的角标进行操作建议使用传统for。

可以使用高级for遍历map集合么?

不能直接用,但是可以将map转换成单列的set就可以使用了。

4,实例:

public class Demo1 {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("abc1");
		list.add("abc2");
		list.add("abc3");
		for(String s : list) {//简化了书写,底层其实是iterator
			System.out.println(s);
		}
	}
}

public class Demo2 {
	public static void main(String[] args) {
		int[] arr = {3,1,5,7,4};
		for(int i : arr) {
			System.out.println(i);
		}
	}
}

遍历Map示例:

public class Demo3 {
	public static void main(String[] args) {
		Map<Integer,String> map = new HashMap<Integer,String>();
		map.put(3,"zhangsan");
		map.put(1,"wangyi");
		map.put(5,"wangcai");
		map.put(7,"xiaoqiang");
		for(Integer key : map.keySet()) {
			String value = map.getKey();
			System.out.println(key + "::" + value);
		}
		for(Map.Entry<Integer,String> me : map.entrySet()) {
			Integer key = me.getKey();
			String value = me.getValue();
			System.out.println(key + ":" + value);
		}
	}
}

 

18-18,JDK1.5新特性-函数的可变参数

1,其实就是一个数组,但是接收的是数组的元素。自动将这些元素封装成数组。简化了调用者的书写。

注意:可变参数类型,必须定义在参数列表的结尾。

public static newAdd(int... arr) {
	int sum = 0;
	for(int i=0;i<arr.length;i++) {
		sum += arr[i];
	}
	return sum;
}
int sum = newAdd(5,1,7,4,3,6);//可以传入任意长度的参数
System.out.println(sum);
int sum = newAdd(7,4,6,5,1,8,97,6);
System.out.println(sum);

注意:

public static newAdd(int... arr,int a)

{ ... code ...} //写法错误,传入的参数会全都赋给arr,a会没有值

public static newAdd(int a,int... arr)

{ ... code ...} //写法正确,第一个值传给a,后面的传给arr

 

18-19,JDK1.5新特新-静态导入

静态导入,其实导入的类中的静态成员。

在import后面加上static,示例如下:

import java.util.ArrayList;
import java.util.Collections;
import static java.util.Collections.*;//导入Collections中的所有的静态成员,可简化书写
import static java.lang.System.*;//导入System中的所有静态成员
public class Demo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("abc3");
		list.add("abc7");
		list.add("abc1");
		out.println(list);//out是System类中的静态成员,静态导入System后,就不用通过System调用了
		sort(list);//Collections中的静态方法
		out.println(list);
		String max = max(list);//Collections中的静态方法
		out.println("max = " + max);
	}
}


猜你喜欢

转载自blog.csdn.net/xzm_rainbow/article/details/18080191