集合框架(二)
一、TreeSet
1.TreeSet集合特点
可以对Set
集合中的元素进行自然排序。往TreeSet
里面的对象,必须具有比较性,才可以实现排序。
2.TreeSet集合排序实现方式
1)元素实现Comparable接口
要获得元素的比较性,需要进行存储的对象可以实现Comparable
接口,并复写compareTo()
方法,自定义比较方式;这是通过让元素获得比较性的方法,完成TreeSet
排序功能。
2)集合初始化Comparator
当元素自身不具备比较性时,或者具备的比较性不是所需要的,这时可以让集合调用具备比较功能的构造方法,将比较器对象作为参数传给TreeSet
集合的构造方法;这是让集合自身具备比较功能,完成TreeSet
排序。
注意:
-TreeSet集合保证元素唯一新的依据是comparteTo()方法的返回值为0;
-当元素和集合都具备比较功能时,以集合的比较器为主
3.TreeSet集合运用
1)继承Comparable接口的方法实现元素的可比性
使用TreeSet
集合存储自定义对象。
示例代码:
package com.heisejiuhuche.Collection;
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
/*
* 声明TreeSet集合
*/
TreeSet ts = new TreeSet();
/*
* 添加Student对象
*/
ts.add(new Student("lisi01", 11));
ts.add(new Student("lisi08", 23));
ts.add(new Student("lisi07", 19));
ts.add(new Student("lisi09", 19));
ts.add(new Student("lisi02", 225));
/* 取出所有元素并打印 */
for(Iterator it = ts.iterator(); it.hasNext(); ) {
Student stu = (Student)it.next();
System.out.println(stu.getName() + "..." + stu.getAge());
}
}
}
class Student implements Comparable {
private String name;
private int age;
Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
/*
* 实现comparaTo接口,使Student对象具有可比性
*/
public int compareTo(Object obj) {
if(!(obj instanceof Student)) {
throw new RuntimeException("Not Student...");
}
Student stu = (Student)obj;
if(this.age > stu.age) {
return 1;
} else if(this.age == stu.age) {
//在主要条件相同时,要比较次要条件,完成排序
//避免不同名但同年龄的对象无法储存的现象
return this.name.compareTo(stu.name);
}
return -1;
}
}
程序输出结果:
lisi01...11
lisi07...19
lisi09...19
lisi08...23
lisi02...225
注意:
排序时,当主要条件相同时,一定要判断次要条件
2)调用TreeSet具备比较器构造方法
示例代码:
package com.heisejiuhuche.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSetDemo2 {
public static void main(String[] args) {
/*
* 将Comparator对象床给TreeSet构造方法,让TreeSet具备比较功能
*/
TreeSet ts = new TreeSet(new MyComparator());
ts.add(new Student("lisi01", 11));
ts.add(new Student("lisi01", 9));
ts.add(new Student("lisi08", 23));
ts.add(new Student("lisi007", 19));
ts.add(new Student("lisi09", 19));
ts.add(new Student("lisi09", 19));
ts.add(new Student("lisi02", 225));
ts.add(new Student("lisi04", 225));
for (Iterator it = ts.iterator(); it.hasNext();) {
Student stu = (Student) it.next();
System.out.println(stu.getName() + "..." + stu.getAge());
}
}
}
class Student {
private String name;
private int age;
Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return age;
}
}
/*
* 实现Comparator接口
*/
class MyComparator implements Comparator {
public int compare(Object o1, Object o2) {
Student stu1 = (Student) o1;
Student stu2 = (Student) o2;
int num = stu1.getName().compareTo(stu2.getName());
/*
* 如果姓名相同,比较年龄
*/
if (num == 0) {
return new Integer(stu1.getAge()).compareTo(new Integer(stu2.getAge()));
}
return num;
}
}
程序输出结果:
lisi007...19
lisi01...9
lisi01...11
lisi02...225
lisi04...225
lisi08...23
lisi09...19
4.二叉树数据结构
1)定义
二叉树数据结构的原则是将小的元素依次放在左边,大的元素依次放在右边。在取数据时,二叉树从最小的元素开始,依次往上取到最大值。这样的数据结构,可以减少比较次数,提高运行效率。
2)二叉树运用
使用二叉树原理实现数据存储和取出顺序一致。
示例代码:
public int compareTo(Object obj) {
return 1;
}
要使存储和取出的顺序一致,只需在复写的compareTo()
方法中,return 1;
即可;存储与取出成倒序,只需return -1;
即可
5.TreeSet练习
1)按照字符串的长度排序
示例代码:
package com.heisejiuhuche.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSetTest {
public static void main(String[] args) {
TreeSet ts = new TreeSet(new StrLenComparator());
ts.add("abc");
ts.add("z");
ts.add("addbc");
ts.add("aaa");
ts.add("abdfsc");
ts.add("abcfsa3r");
for(Iterator it = ts.iterator(); it.hasNext(); ) {
String str = (String)it.next();
System.out.println(str);
}
}
}
class StrLenComparator implements Comparator {
public int compare(Object o1, Object o2) {
String str1 = (String)o1;
String str2 = (String)o2;
/*
* 比较字符串的长度
*/
int num = new Integer(str1.length()).compareTo(new Integer(str2.length()));
/*
* 如果字符串长度相等,比较字符串自然顺序
*/
if(num == 0) {
return str1.compareTo(str2);
}
return num;
}
}
程序输出结果:
z
aaa
abc
addbc
abdfsc
abcfsa3r
二、泛型
1.概述
由一个小例子引出泛型:
package com.heisejiuhuche.Collection;
import java.util.ArrayList;
import java.util.Iterator;
public class GenericDemo {
public static void main(String[] args) {
ArrayList arr = new ArrayList();
/*
* 添加字符串
*/
arr.add("asdf");
arr.add("adfsdf");
arr.add("asdfgsf");
arr.add("as346ndfdf");
/*
* 添加整型数据4
*/
arr.add(4);
for(Iterator it = arr.iterator(); it.hasNext(); ) {
String str = (String)it.next();
/*
* 输出字符串的长度
*/
System.out.println(str.length());
}
}
}
如果没有泛型的支持,后添加的整型数据4
,在运行时会出现运行时期类型转换异常,int
类型无法被转换为String
类型,但是这个异常在编译时期,没有任何提示。为了避免这样的情况,需要程序在编译时期就给出异常,以便开发者解决问题,Java引入了泛型机制。
JDK5.0版本之后的新特性,用于解决集合存储的安全问题。
泛型格式(以储存
String
类型数据为例):ArrayList<String> arrayList = new ArrayList();
声明一个ArrayList容器,存储String类型的元素。这样,上述程序在编译时期就会报出异常。
容器声明加上了泛型,那么迭代器的声明也应加上泛型:Iterator<String> it = arrayList.iterator();
2.泛型的优点
1)将运行时期出现的ClassCastException
转移到编译时期,方便开发者解决问题,更安全;
2)避免了迭代时期类型强制转换的麻烦;
3)泛型类的出现优化了程序设计
3.泛型的应用
当在声明容器的时候指定了泛型,后期实现Comparator
接口和Comparable
接口的时候,可以指定Comparator
接口的泛型;这样做,避免了比较时的类型转换。
Comparator接口泛型示例代码:
/*
* 加上了泛型的Comparator接口
*/
class MyComparator implements Comparator<Student> {
public int compare(Student o1, Student o2) {
/*
* 不需要做类型转换
*/
int num = o1.getName().compareTo(o2.getName());
/*
* 如果姓名相同,比较年龄
*/
if (num == 0) {
return new Integer(o1.getAge()).compareTo(new Integer(o2
.getAge()));
}
return num;
}
}
Comparable接口泛型示例代码:
/*
* 加上了泛型的Comparable接口
*/
class Student implements Comparable<Student> {
private String name;
private int age;
Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int compareTo(Student obj) {
/*
* 无须类型转换
*/
if(this.age > obj.age) {
return 1;
} else if(this.age == obj.age) {
return this.name.compareTo(obj.name);
}
return -1;
}
}
注意:
复写equals()方法的时候,传的参数必须是Object obj;因为Object类没有泛型
4.泛型类
1)定义
泛型类是在一个类要操作对象,而对象类型又不明确时出现的。
2)泛型类格式
声明泛型类,只需要在类名后加上<T>
即可:class GenericClass<T>
3)泛型类应用
当一个类中要操作的引用数据类型不确定的时候,早期定义Object
来完成扩展,现在定义泛型来完成扩展。例如,当一个工具类中要操作不同的对象Worker
和Student
时,早期通过接收Object
对象,然后通过类型转换类完成操作;现在只需定义泛型,无需类型转换。
早期实现:
package com.heisejiuhuche.Collection;
public class GenericClassDemo {
public static void main(String[] args) {
Utils u = new Utils();
u.setObjcet(new Worker());
/*
* 需要类型转换来完成操作
*/
Worker w = (Worker)u.getObject();
}
}
class Worker {}
class Student {}
class Utils {
private Object obj;
/*
* 接收Object为参数
*/
public void setObjcet(Object obj) {
this.obj = obj;
}
public Object getObject() {
return obj;
}
}
如果现在要操作的是Worker
对象,而在setObject()
方法中误传入了Student
对象,编译不会报错,但是运行时会出现类型转换异常。
泛型实现:
package com.heisejiuhuche.Collection;
public class GenericClassDemo {
public static void main(String[] args) {
Utils<Worker> u = new Utils<Worker>();
u.setObjcet(new Worker());
/*
* 不需要类型转换来完成操作
*/
Worker w = u.getObject();
}
}
class Worker {}
class Student1 {}
class Utils<T> {
private T t;
/*
* 接收泛型T作为参数
*/
public void setObjcet(T t) {
this.t = t;
}
public T getObject() {
return t;
}
}
如果现在要操作的是Worker
对象,而在setObject()
方法中误传入了Student
对象,编译无法通过;并且泛型实现无须进行类型转换,既简化了代码,又提升了安全性。
5.泛型方法
1)定义
泛型类在使用的过程中有其局限性。例如一个泛型类被定义了只能操作String
类型的对象,那么该类中需要接收参数的方法,都只能操作String
对象;此时如果给该类中的方法传递了非String
类型数据,编译就会出错。为了程序更加灵活,现在想实现一个类中不同的方法操作不同类型的对象,而要操作的对象不能确定,那么就可以使用泛型方法。
2)泛型方法格式
要声明泛型方法,只需在方法修饰符后面,返回值的前面加上<T>
即可:private <T> void genericMethod() {}
3)泛型方法应用
定义一个类,类中的两个输出方法可以输出指定类型的对象。
示例代码:
package com.heisejiuhuche.Collection;
public class GenericMethodDemo {
public static void main(String[] args) {
GenericMethod gm = new GenericMethod();
//打印字符串类型
gm.show("haha");
//打印Integer类型
gm.show(new Integer(3));
gm.print("heihei");
}
}
class GenericMethod {
/*
* 泛型方法show()
*/
public <T> void show(T t) {
System.out.println("show: " + t);
}
/*
* 泛型方法print()
*/
public <T> void print(T t) {
System.out.println("print: " + t);
}
}
程序输出结果:
show: haha
show: 3
print: heihei
高能:
-可以在泛型类中定义泛型方法,互不影响;
-静态方法不可以访问类上定义的泛型;如果静态方法操作的引用数据类型不确定,可以将其定义为泛型方法
public static <T> void method() {}
三、泛型限定
1.“?”通配符“?”
是一个通配符,也可以理解为占位符。在泛型的使用中,当无法确定接收对象是什么类型的时候,先用“?”
占位。例如,有存放Student
对象和Person
对象的两个ArrayList
,定义一个方法,可以遍历打印这两个集合。
代码示例:
package com.heisejiuhuche.Collection;
import java.util.ArrayList;
import java.util.Iterator;
public class GenericDemo3 {
public static void main(String[] args) {
ArrayList<Programmer> arr1 = new ArrayList<Programmer>();
ArrayList<Manager> arr2 = new ArrayList<Manager>();
arr1.add(new Programmer("aaa1"));
arr1.add(new Programmer("aaa2"));
arr1.add(new Programmer("aaa3"));
arr2.add(new Manager("bbb1"));
arr2.add(new Manager("bbb2"));
arr2.add(new Manager("bbb3"));
printArr(arr1);
printArr(arr2);
}
/* 用通配符 ? 接收任意集合并打印 */
private static void printArr(ArrayList<?> arrayList) {
for(Iterator<?> it = arrayList.iterator(); it.hasNext(); ) {
System.out.println(it.next().toString());
}
}
}
class Programmer {
private String name;
Programmer(String name) {
this.name = name;
}
/*
* 复写toString()方法,输出名字
*/
public String toString() {
return this.name;
}
}
class Manager {
private String name;
Manager(String name) {
this.name = name;
}
/*
* 复写toString()方法,输出名字
*/
public String toString() {
return this.name;
}
}
程序输出结果:
aaa1
aaa2
aaa3
bbb1
bbb2
bbb3
2.泛型的限定:
1)? extends E:可以接收E类型或者E类型的子类型;限定了上限;
示例代码:
package com.heisejiuhuche.Collection;
import java.util.ArrayList;
import java.util.Iterator;
public class GenericDemo4 {
public static void main(String[] args) {
ArrayList<SalesManager> arr1 = new ArrayList<SalesManager>();
ArrayList<Salesman> arr2 = new ArrayList<Salesman>();
arr1.add(new SalesManager("kengdie09"));
arr1.add(new SalesManager("kengdie02"));
arr1.add(new SalesManager("kengdie06"));
arr2.add(new Salesman("kengdie04"));
arr2.add(new Salesman("kengdie08"));
arr2.add(new Salesman("kengdie10"));
printArr(arr1);
printArr(arr2);
}
/*
* 泛型限定,只为Salesman和Salesman的子类提供打印服务
*/
private static void printArr(ArrayList<? extends Salesman> arr) {
for(Iterator<? extends Salesman> it = arr.iterator(); it.hasNext(); ) {
System.out.println(it.next().getName());
}
}
}
class Salesman {
private String name;
Salesman(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class SalesManager extends Salesman {
private String name;
SalesManager(String name) {
super(name);
}
}
程序输出结果:
kengdie09
kengdie02
kengdie06
kengdie04
kengdie08
kengdie10
上述程序,可以在打印方法中使用“?”
通配符,但是如果需求是,要打印一个范围内(Salesman
及Salesman
的子类)对象,那么就使用“? extends E”
泛型限定。
2)? super E:可以接收E类型或者E类型的付类型;限定了下限;
示例代码:
package com.heisejiuhuche.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
public class GenericDemo4 {
public static void main(String[] args) {
TreeSet<SalesManager> ts1 = new TreeSet<SalesManager>(new MyComp());
TreeSet<SalesAssist> ts2 = new TreeSet<SalesAssist>(new MyComp());
ts1.add(new SalesManager("kengdie09"));
ts1.add(new SalesManager("kengdie02"));
ts1.add(new SalesManager("kengdie06"));
ts2.add(new SalesAssist("kengdie04"));
ts2.add(new SalesAssist("kengdie08"));
ts2.add(new SalesAssist("kengdie10"));
printTs(ts1);
printTs(ts2);
}
/*
* 使用泛型限定打印Salesman及Salesman子类对象
*/
private static void printTs(TreeSet<? extends Salesman> ts) {
for(Iterator<? extends Salesman> it = ts.iterator(); it.hasNext(); ) {
System.out.println(it.next().getName());
}
}
}
/*
* 使用泛型限定,此比较器可以比较Salesman及其子类
* 也就是说,在Comparator<>的尖括号内限定父类,那么
* 该比较器就可以比较父类,及其子类,无须定义多个比较器
*/
class MyComp implements Comparator<Salesman> {
public int compare(Salesman s1, Salesman s2) {
return s1.getName().compareTo(s2.getName());
}
}
class Salesman {
private String name;
Salesman(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class SalesAssist extends Salesman {
private String name;
SalesAssist(String name) {
super(name);
}
}
class SalesManager extends Salesman {
private String name;
SalesManager(String name) {
super(name);
}
}
程序输出结果:
kengdie02
kengdie06
kengdie09
kengdie04
kengdie08
kengdie10
泛型限定,用于泛型扩展,让程序更加灵活。