day15_集合框架2(TreeSet,泛型)
1.TreeSet排序两种方式:
/* set:无序,元素不重复 <-HashSet:数据结构是哈希表,线程是非同步的 保证元素唯一性的原理:判断元素的hashCode值是否相同 如果相同,还会继续判断元素的equals方法,是否为true. <-TreeSet:可以对set集合中的元素进行排序,线程是非同步的. 底层数据结构是二叉树(更确切说是红黑树:红黑树的算法特别复杂,以后有时间单独写一篇进行讨论) 保证数据元素唯一性的依据: compareTo方法. remove,contains在查找元素过程中需要使用compareTo方法判断. TreeSet排序的第一种方式: 让元素自身具备比较性,元素需要实现Comparable接口,复写compareTo方法 这种方式叫元素的自然顺序/默认顺序 (排序示意图) */ package treeset; import java.util.*; class TreeSetDemo{ public static void main(String[] args){ /* TreeSet ts=new TreeSet(); ts.add("cdef"); ts.add("aaa"); ts.add("bca"); ts.add("Dbcd"); for(Iterator it=ts.iterator();it.hasNext();) System.out.println(it.next()); */ /* String类中实现了Comparable接口,复写了接口中的toCompare方法 使字符串按照字符的ASCII码从小到大排序. */ stuMethod(); } static void stuMethod(){ TreeSet ts=new TreeSet(); ts.add(new Student("zhang",15)); ts.add(new Student("wang",12)); ts.add(new Student("liu",20)); ts.add(new Student("zong",12));//仅年龄相同 ts.add(new Student("liu",20));//姓名年龄均相同 for(Iterator it=ts.iterator();it.hasNext();){ Student stu=(Student)it.next(); System.out.println(stu.getName()+" "+stu.getAge()); } } } //自定义类,向TreeSet集合中存储自定义对象学生, //希望按照学生的年龄进行排序,当年龄和姓名均相同时,视为同一个人(对象) //该接口强制让学生具备比较性 //排序时,当主要条件相同时,一定要判断下次要条件 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; } //复写compareTo public int compareTo(Object obj){ if(!(obj instanceof Student)) throw new RuntimeException("不是Student对象"); Student stu= (Student)obj; //为了查看怎么排序的 System.out.println(this.name+" "+this.age+ "...compareTo..."+stu.name+" "+stu.age); if(this.age>stu.age) return 1; else if(this.age==stu.age)//可以在判断条件加上&&this.name==stu.name //更好的方法,当年龄相同时,对姓名进行排序 return this.name.compareTo(stu.name); //return 0; /* 负整数、零或正整数,根据此对象是小于、等于还是大于指定对象。 return 0 ,TreeSet认为相同对象,不再存入集合(但第一次,自身与自身比较 个人认为为了判断是否具有可比性, 即使返回0,底层通过某种判断->该集合中没有该元素->加入集合) ) */ return -1; } } /* (API)当使用TreeSet()构造函数时: 构造一个新的空 set, 该 set 根据其元素的自然顺序进行排序。(字典顺序,数字大小顺序等等) 插入该 set 的所有元素都必须实现 Comparable 接口。 另外,所有这些元素都必须是可互相比较的:对于 set 中的任意两个元素 e1 和 e2, 执行 e1.compareTo(e2) 都不得抛出 ClassCastException。 如果用户试图将违反此约束的元素添加到 set (例如,用户试图将字符串元素添加到其元素为整数的 set 中), 则 add 调用将抛出 ClassCastException。 */
二叉排序树: (为了清晰,下面多加了两个元素)
两种极端情况:
第二种排序方式: (implements Comparator)
/* TreeSet: 第一种排序方式: 让元素自身具备比较性,元素需要实现Comparable接口,复写compareTo方法 这种方式叫元素的自然顺序/默认顺序 第二种排序方式: 当元素自身不具备比较性时,或者具备的比较性不是所需要的. (例如我不想按年龄排,按姓名排序,当实现的Comparable 此时代码已封装不能改动) 这时就需要让集合自身具备比较性. 在集合初始化时,就有了比较方式. 当两种方式都存在时,以比较器为主.(根据需要采用不同的比较方式) 定义一个类,实现Comparator接口,覆写compare方法. */ package treeset; import java.util.TreeSet; import java.util.Comparator; import java.util.Iterator; class MyCompare implements Comparator{ public int compare(Object obj_1,Object obj_2){ //利用String类的compareTo方法,此时代码不健壮 //可能出现姓名相同而年龄不同的人->视为不同人 //因此同样判断次要条件->年龄 Student stu_1=(Student)obj_1; Student stu_2=(Student)obj_2; int result=stu_1.getName().compareTo(stu_2.getName()); if(result==0){ return stu_1.getAge()-stu_2.getAge(); //或利用Integer封装整数 //return new Integer(stu_1.getAge()).compareTo(stu_2.getAge()); } return result; } } class TreeSetDemo2{ public static void main(String[] args){ TreeSet ts=new TreeSet(new MyCompare());//该位置传入指定比较器 ts.add(new Student("zhang",15)); ts.add(new Student("wang",12)); ts.add(new Student("liu",20)); ts.add(new Student("zong",14)); ts.add(new Student("zong",12)); for(Iterator it=ts.iterator();it.hasNext();){ Student stu=(Student)it.next(); System.out.println(stu.getName()+" "+stu.getAge()); } } }
TreeSet两种排序方式对比:
/* 两种排序方式对比: ①实现Comparable,复写compareTo: 排序算法和Student类绑定(封装在Student类中), Student只有一种排序算法并且只能有一种,这是因为 我只能复写一次compareTo.这时,这种方法局限性体现出来了. ②自定义类实现Comparator,复写compare: 之所以提供比较器排序接口, 是因为有时需要对同一对象进行多种不同方式的排序, 这点自然排序Comparable不能实现。 另外,Comparator 接口的一个好处是将比较排序算法和实体类分离了(而不再其内部),更加灵活。 1、TreeSet():根据TreeSet中元素实现的 Comparable 接口的 compareTo 方法比较排序 2、TreeSet(Comparator comparator):根据给定的 comparator 比较器,对 TreeSet 中的元素比较排序 */
2.Comparator简单练习:
/* 按照字符串长度排序 return s1.length()-s2.length(); 字符串本身具备比较性,它的比较方式不是所需要的. 这时只能采用实现Comparator */ package treeset; import java.util.*; class StringLenCom implements Comparator{ public int compare(Object obj_1,Object obj_2){ String str_1=(String)obj_1; String str_2=(String)obj_2; int num=str_1.length()-str_2.length(); System.out.println(str_1+"...compare..."+str_2+" "+num); if(num==0){//两个字符串相同,不处理,认为相同对象,不再存入集合 return str_1.compareTo(str_2); } return num; } } class TreeSetTest{ public static void main(String[] args){ TreeSet ts=new TreeSet(new StringLenCom()); ts.add("a"); ts.add("bc"); ts.add("de"); ts.add("c"); ts.add("d"); ts.add("f"); for(Iterator it = ts.iterator();it.hasNext(); ){ System.out.println(it.next()); } } } /* 在写排序算法时,注意重复元素处理! */
3.泛型概述:
/* 泛型: JDK 1.5之前,对象保存到集合中就会失去其特性,取出时要手动进行人工强制类型转化, 集合元素中的数据类型可以不统一 例:List集合 list=new ArrayList(); list.add("aa"); list.add(1); list.add(12.5); 加入泛型之后,list集合只能放同一类型的数据,就避免了list元素类型不统一的现象出现。 JDK1.5版本以后出现新特性,用于解决安全问题,是一个类型安全机制. 类比:数组 int[]arr=new int[3]; arr[0]=1; arr[1]=3.5//编译不能通过,报损失精度 数组在定义时已指定要存入的类型 优点: 1.将运行时期出现问题ClassCastException,转移到了编译时期 有利于程序员发现问题,解决问题->使运行时期问题减少->减少安全隐患 2.避免了强制转换的麻烦 */ package generic; import java.util.*; class GenericDemo{ public static void main(String[] args){ //定义了一个as容器,强制限定(<String>)只能存放String类型的对象 ArrayList<String> as= new ArrayList<String>(); //ArrayList as =new ArrayList(); as.add("abc"); as.add("bef"); //as.add(4);//Integer.valueOf(4) /* 如果向集合中添加整形对象,和字符串 属于不同类型的对象,在运行时,下面的强转会发生异常 ClassCastException 存在安全隐患,能不能再编译时期发生问题? ->引出泛型 */ //集合中的元素通过迭代器取,迭代器可以操作(Object)任意对象 //因此,明确迭代器操作的类型,更方便操作//其实在上面已指明String,iterator方法返回就是Iterator<String>
//需要一个相同类型进行接收. for(Iterator<String>it = as.iterator();it.hasNext(); ){ //String str= (String)(it.next()); String str = it.next(); System.out.println(str+" "+str.length()); } } } /* 泛型格式:通过<>来定义要操作的引用数据类型 在使用java提供的对象时,是么时候写泛型呢? 通常在集合框架中和常见,只要见到<>就要定义泛型 例如: Collection<E>:E->Element 当使用集合时,将集合中要存储的数据类型(引用类型)作为参数传递到<>(如同函数传参)中即可 ArrayList<String> as=new ArrayList<String>(); *//*
泛型与原数据:
ArrayList as= new ArrayList<String>();
as.add(1);
会报警告,但可以存入 as.add(1)->说明此时E的类型没有传入ArrayList<String>as= new ArrayList();
会发出警告,如果加上 as.add(1)时编译报错->此时E的类型已为String
*/
4.将Comparator的例子利用泛型改写:
package generic; import java.util.*; class MyComp implements Comparator<String>{//Comparator<T>:T->Type 数据类型 public int compare(String obj_1,String obj_2){ //int num=obj_1.length()-obj_2.length(); int num=obj_2.length()-obj_1.length(); System.out.println(obj_1+"..."+obj_2+" "+num); /* 比较: a,a 0 bc,a a-bc<0 def a a-def<0 def bc bc-def<0*/ /* 可通过这样方式,逆序输出(长度从大到小) 也可用更简便的方式如上. if(num>0) return -1; if(num<0) return 1; */ if(num==0) return obj_2.compareTo(obj_1); return num; } } class GenericDemo2{ public static void main(String[] args){ TreeSet<String> ts = new TreeSet<String>(new MyComp()); ts.add("a"); ts.add("bc"); ts.add("def"); ts.add("e"); for(Iterator<String> it = ts.iterator();it.hasNext();){ System.out.println(it.next()); } } } /* 注意在使用HashSet集合中, 存放的对象所属的类复写hashCode和equals 注意equals原型为Object中public boolean equals(Object obj)//Object不能变 */ /* 目前为止:自定义一个类 ①需要复写Object中的int hashCode()和equals 因为:可能存入HashSet集合 ②需要实现Comparable复写compareTo 因为:需要使该类对象具备比较性->存入TreeSet集合 用不用,另说. */此时,没有了编译时的警告:使用了未经检查或不安全操作
5.在自定义的类中使用泛型:
/* 在自定义的类中使用泛型 什么时候定义泛型类? 当类中要操作的引用数据类型(不能是基本数据类型)不确定的时候 早期定义Object来完成扩展. 现在定义泛型来完成扩展 */ package generic; class Worker{ } class Student{ } //没有泛型前的做法 class Tool{ private Object obj; public void setObject(Object obj){ this.obj=obj; } public Object getObject(){ return obj; } /* 之所以定义为Object,接收后期的任意对象(后期传入的不确定) */ } //泛型类 class Utils<Q>{//在后面会说到通配符?在这里不能换成<?> //编译会报需要<标示符>,如果这里用?->传过来的类型也无法在类中使用 private Q q; public void setObject(Q q){ this.q=q; } public Q getObject(){ return q; } } class GenericDemo3{ public static void main(String[] args){ /* //未使用泛型 Tool t = new Tool(); t.setObject(new Worker()); //t.setObject(new Student());当传入学生对象,编译时期没错,但是运行时期发生转换异常 Woker w = t.getObject(); */ //使用泛型 Utils<Worker> u = new Utils<Worker>(); u.setObject(new Worker()); Worker w = u.getObject(); } }
6.泛型方法:
/* 泛型方法引入: 泛型类定义的泛型,在整个类中有效.如果被方法使用 那么泛型类的对象明确要操作的具体类型后,所有要操作的类型 就已经固定了. 为了让不同方法可以操作不同的类型,而且类型还不确定 那么可以将泛型定义在方法上.格式:
修饰符 <T,S...> 返回值类型 方法名(形参表){}
*/ /* 特殊之处: 静态方法不可以访问类上定义的泛型. 这是因为泛型类上的类型参数(<T>)只有在建立对象时,才能明确其引用类型,静态方法(随着类加载而加载)存在时,可能还没有对象 也就是说此时类型参数不能被明确. 如果静态方法操作的应用数据类型不确定,只能将泛型定义在方法上. */ package generic; /* class Test<T>{//<T>对整个类有效,也就是说,一旦传入实参,只可能有一种引用类型 public void show(T t){ System.out.println("show "+t); } public void print(T t){ System.out.println("print "+t); } } */ //1.泛型方法 class Demo{ public <T> void show(T t){ System.out.println("show "+t); } //注意<T>与上面的<T>互不影响,因为只在该方法体中有效//T类型 根据传入的实参的类型由编译器确定public <T> void print(T t){ System.out.println("print "+t); } } //2.泛型类中包含泛型方法(对以上略作修改) class Demo2<T>{ public void show(T t){ System.out.println("show "+t); } public <Q> void print(Q t){ System.out.println("print "+t); } //3.泛型静态方法 public static <S> void printStatic(S t){ //注意泛型(<S>)定义在方法上 //放在返回值类型前面,修饰符后面 System.out.println("print "+t); } } class GenericDemo4{ public static void main(String[] args){ /* Test<String> t=new Test<String>();//限定为String类型 t.show("12"); t.print(new Integer(13));//错误 */ Demo d=new Demo(); d.show("haha"); d.show(new Integer(12)); d.print(new Double(1.3)); System.out.println(); Demo2<String> d2=new Demo2<String>(); d2.show("123");//show只能操作String类型->因为其随着泛型类的类型确定而确定 d2.print("haha");//而print可以传入任意引用类型->随着泛型方法的类型确定而确定 d2.print(new Integer(12)); System.out.println(); Demo2.printStatic("static"); } }
7.泛型定义在接口上:
/* 泛型定义在接口上 */ package generic; interface Inter<T>{ public abstract void show(T t); } /* class InterImp1 implements Inter<String>{//使用接口时,往里面传参数String public void show(String q){ System.out.println("show "+q); } } */ //第二种方式:在实现时,操作的类型依然不确定 // 由调用者传入类型 class InterImp2<Q> implements Inter<Q>{ public void show(Q t){//复写父接口方法 System.out.println("show "+t); } } class GenericDemo5{ public static void main(String[] args){ new InterImp1().show("abcd"); new InterImp2<Integer>().show(12);//Integer-传给>InterImp2<Integer> //-传给>Inter<Integer> } }
8.?通配符/占位符
package generic; import java.util.*; class GenericDemo6{ public static void main(String[] args){ ArrayList<String> as = new ArrayList<String>(); as.add("abc"); as.add("def"); ArrayList<Integer> al = new ArrayList<Integer>(); al.add(10); al.add(12); print(al); print(as); } //public static void print(ArrayList<String> as){//只能接收new ArrayList<String>(),实际类型参数固定为String //而不能ArrayList<String> as= new ArrayList<Integer>(); //此时为了提高复用性->使用通配符(占位符)? public static void print(ArrayList<?> as){ //也可使用ArrayList as,因为:jdk1.4还没有泛型,老版本为了兼容新版本 //但是ArrayList as不严谨 for(Iterator<?> it=as.iterator();it.hasNext(); ) System.out.println(it.next()); //System.out.println(it.next().length());//如果传入Integer,则运行失败,Integer中无Length()方法 }/*
如果以上使用了形参:
public static<T> void print(ArrayList<T> as)
for(Iterator<T> it=as.iterator();it.hasNext(); ){
T t=it.next();//如果是?没法使用 T t;
System.out.println(t);
}*/
}
9.泛型上限与下限:
上限:
/* ? 通配符,或理解为占位符 泛型的限定: ? extends E: 可以接收E类型或者E的子类型->上限(限定的E(父类型),子类任意扩展) ? super E:可以接收E类型或者E的父类型->下限(限定的E(子类型),父类型任意) */ package generic; import java.util.*; class Animal{ private String name; Animal(String name){ this.name=name; } public String getName(){ return name; } } class Pig extends Animal{ Pig(String name){ super(name); } public void Method(){ System.out.println("method"); } } class Test{ } class GenericDemo7{ public static void main(String[] args){ ArrayList<Animal> as =new ArrayList<Animal>(); as.add(new Animal("a1")); as.add(new Animal("a2")); //as.add(new Pig("pig1"));//向上提升,可以存入 print(as); ArrayList<Pig>at = new ArrayList<Pig>(); at.add(new Pig("a1")); at.add(new Pig("a2")); //①print(at);不行//ArrayList<Animal> as=new ArrayList<Pig>(); /* ①ArrayList<Animal> as:我指定要接收一个能存储Animal引用类型的 集合,也就是说所既可以存储Animal对象,也可以存储其子类对象 ②new ArrayList<Pig>():传入了一个只能存储Pig引用类型的集合实体 只能存储Pig对象,如果Animal再有一个子类Bird,其对象将不能存储在该集合 实体中. 当然ArrayList<Pig> as=new ArrayList<Animal>();也不行 指定只能存Pig对象,而传入的集合可以存Animal及其子类对象 也就是说左右两边类型保持一致 */ print(at);//② } /* ① public static void print(ArrayList<Animal> as){ for(Iterator<Animal> it=as.iterator();it.hasNext(); ) System.out.println(it.next().getName()); } } */ //② 修改后 public static void print(ArrayList<? extends Animal> as){ //此时我想让其既打印Animal,又打印 //Pig,可以采用ArrayList<?> as //但是这样写:可以接收任意引用类型, //如果我只想让其接收Animal及其Animal子类型呢?? //此时必须做泛型限定即:ArrayList<? extends Animal> as //当传入new ArrayList<Pig>(),相当于ArrayList<Pig> as=new ArrayList<Pig>(); for(Iterator<? extends Animal> it=as.iterator();it.hasNext(); ) System.out.println(it.next().getName()); } }
下限:
package test; import java.util.*; class Animal{ private String name; Animal(String name){ this.name=name; } public String getName(){ return name; } } class Pig extends Animal{ Pig(String name){ super(name); } } class Bird extends Animal{ Bird(String name){ super(name); } } class Demo{ public static void main(String[] args){ TreeSet<Pig>ts1 = new TreeSet<Pig>(new Com());//在传入的时候类型提升((Comparator<? extends Animal>)new Com())ts1.add(new Pig("P1")); ts1.add(new Pig("P2")); TreeSet<Bird>ts2 = new TreeSet<Bird>(new Com()); ts2.add(new Bird("B1")); ts2.add(new Bird("B2")); method(ts1); method(ts2); /* 构造函数: TreeSet(Comparator<? super E> comparator) 例如:E为Pig TreeSet(Comparator<? super Pig> comparator) 传入的比较器限定:比较的Pig对象或Animal对象或Object对象 也就是说对于以上其可传入类型为:Comparator<Pig>或Comparator<Animal>或Comparator<Object> */ } public static <T extends Animal> void method(TreeSet<T> ts ){ for(Iterator<T>it=ts.iterator();it.hasNext();){ System.out.println(it.next().getName()); } } } class Com implements Comparator<Animal>{ public int compare(Animal a1,Animal a2){ return a1.getName().compareTo(a2.getName()); } }
10.泛型小结:
1.自定义类中的泛型 ① class Utils<Q>{ private Q q; } ② class Utils2<Q extends Animal>{ private Q q; } 类和接口上均不能使用<?>:传过来的类型无法再类中使用 编译时会报需要<标识符> 2.泛型定义在接口上: ① interface Inter<T>{ } class Imp<T> implements Inter<String>{ } ② interface Inter<T>{ } class Imp<Q> implements Inter<Q>{ } ③ //经过测试不能使用下限,无法通过编译(原因编译器会报语法错误,不知到为什么)->例如:T super Stringinterface Inter<T extends Animal>{ } class Imp<Q extends Animal> implements Inter<Q>{//该位置不能写成<Q extends Animal> //该位置相当于实参传递给泛型定义的形参 } 3.?与泛型方法对比(来源疯狂java) 注意:当使用 public <T> void method(ArrayList<T> as){//通配符?也是 as.add(添加的元素);//在这里不知道T的具体类型,无法添加元素 } 大多数时候可以使用泛型方法来代替类型通配符: boolean containsAll(Collection<?> c); boolean <T> containsAll(Collection<T> c); boolean addAll(Collection<? extends E> c); boolean <T extens E> addAll(Collection<T> c);//<T extens E>为声明 上面的两个方法中类型形参T只使用了一次->完全可以使用?代替 ①泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系, ②或者方法返回值与参数之间的类型依赖关系.③同时泛型方法声明的类型如果在方法中使用,此时不能使用? 如: //将源集合中的元素(T类型对象或其子类对象)拷贝到目的集合(可以存放T类型对象)中 public static <T> void copy(List<T> dest,List<? extends T> src)