12. JavaAPI

编程入门 行业动态 更新时间:2024-10-09 09:24:04

12. <a href=https://www.elefans.com/category/jswz/34/1684585.html style=JavaAPI"/>

12. JavaAPI


 

一    TreeSet

首先,对Set集合进行回顾。

|---- Set:无序、不可重复元素

        |---- HashSet:数据结构是哈希表,线程是非同步的;

                        保证元素唯一性的原理是判断元素的hashcode值是否相等,hashcode相等还会继续判断equals方法是否为true

        |---- TreeSet:可以对Set集合中的元素进行排序,例如字母会按自然顺序排序

“因为它们数据结构不一样,所以保证元素唯一的方式也不一样。”

import java.util.*;
public class TreeSetDemo {public static void main(String[] args) {TreeSet ts = new TreeSet();ts.add("fabc");ts.add("eabec");ts.add("abfc");ts.add("avbc");ts.add("acbc");Iterator it = ts.iterator();while (it.hasNext()) {System.out.println(it.next());}/** 输出如下: abfc acbc avbc eabec fabc*/}
}

输出:

abfc
acbc
avbc
eabec
fabc

二    TreeSet存储自定义对象

1、需求及错误示例

需求:往 TreeSet 集合中存储自定义对象,并按照年龄进行排序。

根据以往的知识,我们会按照下面的方法写:

import java.util.*;class Person{private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}}public class TreeSetDemo {public static void main(String[] args) {TreeSet ts = new TreeSet();ts.add(new Person("lilei02", 14));ts.add(new Person("lili02", 12));ts.add(new Person("lilei02", 12));ts.add(new Person("hanmeimei02", 13));ts.add(new Person("lilei02", 12));Iterator it = ts.iterator();while (it.hasNext()) {System.out.println(it.next());}}
}


然后,程序就抛异常了:

Exception in thread "main" java.lang.ClassCastException: package2.Person cannot be cast tojava.lang.Comparable

并且,如果你只存一个Person对象,是可以输出这个对象的哈希值的。

Q:那么问题来了,为什么存一个对象就没事,存两个就有事呢?

A:TreeSet集合能够排序,但要求你传入的对象必须具备比较性,对象的类必须实现Comparable<T>接口,强行让对象具备比较性。

      换个说法,就是:你往TreeSet集合存对象,它是要帮你排序的,你没把怎么排序方式告诉它,它不能帮你办这个事。实现接口就能具备比较性。

2、关于Comparable接口

public interface Comparable<T>  此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法

API文档截图:

注意:

  • 像String、Integer、Double这些类,本身已经实现了Comparable接口,默认是按自然顺序排列的。
  • 如果compareTo()方法返回0,会判断为比较对象为相同的对象,不进行存储。这是个问题,因此,主要条件判断完,要判断次要条件。
  • 一般HashSet存储自定义对象,需要重写equals;但是TreeSet存的时候会判断大小,就相当于去判断对象是否相等,所以TreeSet不用重写equals。

“记住,排序时,当主要条件相同时,一定要判断一下次要条件。”

3、正确示例代码

import java.util.*;class Person implements Comparable {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public int compareTo(Object obj) {// 按照年龄比较自定义的存储对象if (!(obj instanceof Person)) {new RuntimeException("您存入的不是Person类的对象.");}Person p = (Person) obj;if (p.age > this.age) {return 1;} else if (p.age < this.age) {return -1;}return p.namepareTo(this.name);}public String toString() {return "姓名:" + this.name + " 年龄:" + this.age;}
}public class TreeSetDemo {public static void main(String[] args) {TreeSet ts = new TreeSet();ts.add(new Person("lilei02", 14));ts.add(new Person("lili02", 12));ts.add(new Person("lilei02", 12));ts.add(new Person("hanmeimei02", 13));ts.add(new Person("lilei02", 12));Iterator it = ts.iterator();while (it.hasNext()) {System.out.println(it.next());}}
}

输出:

姓名:lilei02 年龄:14

姓名:hanmeimei02 年龄:13

姓名:lili02 年龄:12

姓名:lilei02 年龄:12


 

三    二叉树


TreeSet底层是怎么存的呢?
它的结构能够通过减少比较次数,(例如小的放左边,大的放右边) 来提高效率。
TreeSet底层用到的结构就是 : 二叉树,也叫 红黑树。(二叉树如果数据多了,会取分支的中间值先比较)


 


现在我们改一下需求:TreeSet存自定义对象,怎么存进去的,就怎么取出来。


思考:
TreeSet底层数据结构是二叉树,保证元素唯一性的依据是:CompareTo方法return1、0或-1。
如果想要按照存入顺序取出,可以让 compareTo方法恒返回 1;这样存在于二叉树的右侧,按存入顺序输出了。
 
public int compareTo(Object obj) {return 1;
}

四    实现Comparator方式排序

万一元素真的不具备比较性呢?另外,假设具备比较性,但后来需求变了,例如从年龄比较变为姓名比较呢?

当元素自身不具备比较性时,或者具备的比较性不是所需要的。

这时就需要让集合自身具备比较性。

由此我们需要引入TreeSet还有另一种实现排序方式:java.util Comparator<T> 接口

观察TreeSet的另一个构造方法:

TreeSet(Comparator<? super E> comparator)

构造一个新的空 TreeSet,它根据指定比较器进行排序。

在TreeSet初始化时,就有了比较方式。

比喻:之前元素自身比较,就像军训排队比较高矮。Comparator方式,就是给TreeSet提供一个刻度尺,让每个元素自己去量身高。

两种方式,只要掌握基本原理,会用就行,原理都是二叉树。

当两种方式都存在时,以Comparator方式为主。

/*对容器:定义一个类,实现Comparator接口,覆盖compare方法对元素:定义一个类,实现Comparable接口,覆盖compareTo方法*/
import java.util.*;public class Demo {public static void main(String args[]) {TreeSet ts = new TreeSet(new MyCompare());ts.add(new Student("lisi02", 22));ts.add(new Student("lisi007", 20));ts.add(new Student("lisi09", 19));ts.add(new Student("lisi09", 11));ts.add(new Student("lisi02", 40));ts.add(new Student("lisi01", 40));for (Iterator it = ts.iterator(); it.hasNext();) {Student s = (Student) it.next();sop(s.getName() + " :: " + s.getAge());}}public static void sop(Object obj) {System.out.println(obj.toString());}
}class 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;}}class MyCompare implements Comparator {public int compare(Object o1, Object o2) {Student s1 = (Student) o1;Student s2 = (Student) o2;int num = s1.getName()pareTo(s2.getName());if (num == 0) {return new Integer(s1.getAge())pareTo(new Integer(s2.getAge()));}return num;}
}

public int compare(Object obj1,Object obj2){   //注意:这里obj1和obj2意思是两个对象怎样比较的意思,不要理解为传参数。

输出:

lisi007 :: 20
lisi01 :: 40
lisi02 :: 22
lisi02 :: 40
lisi09 :: 11
lisi09 :: 19

五    TreeSet练习

练习:按照字符串长度排序
思考:字符串本身具备比较器,但是他的比较方式不是所需要的,这时就只能使用比较器。

      也可以不定义类,写匿名内部类也可以。

import java.util.Comparator;
import java.util.TreeSet;/**练习:按照字符串长度排序.* */
public class TreeSetDemo2 {public static void sop(Object obj) {System.out.println(obj);}public static void main(String[] args) {TreeSet ts = new TreeSet(new StrLenComparator());ts.add("aa");ts.add("asa");ts.add("afsdf");ts.add("aafff");ts.add("agsa");ts.add("aaaaabvbb");sop("ts: " + ts);}
}class StrLenComparator implements Comparator {@Overridepublic int compare(Object o1, Object o2) {String str1 = (String) o1;String str2 = (String) o2;if (str1.length() > str2.length()) {return 1;}if (str1.length() == str2.length()) {return str1pareTo(str2);}return -1;}
}

输出:ts: [aa, asa, agsa, aafff, afsdf, aaaaabvbb]

六    泛型概述

1、引入泛型

import java.util.*;public class Demo {public static void main(String args[]){ArrayList al = new ArrayList();al.add("String1");al.add("String2");al.add(3);Iterator it = al.iterator();while(it.hasNext()){System.out.println(((String)it.next()).indexOf('S'));}}
}

输出:

0
0
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

假设: 往一个原来全部存String类型对象的ArrayList,(并且后面还用了String的方法),
突然加存一个数字,运行会出错,程序抛 ClassCastException 类型转换异常 编译没问题,运行才出问题,用户解决不了,存在安全隐患。 原因是存了不同类型的数据。

数组由于在定义时就需要明确元素类型,因此没有上述这个类型安全问题,
而集合却不可以定义时指定类型,因此JDK1.5版本后出现了新特性 —— 泛型。
  泛型:JDK1.5版本后出现的新特性,用于解决安全问题,是一个类型安全机制。类似于数组,限定了你能存入的类型。  

2、使用泛型的好处

(1)  将运行时期可能出现的 ClassCastException  ,转移到了编译时期,      方便于程序员解决问题,让运行时期问题减少,提高了安全性。 (2)  避免了强制转换的麻烦。(1.4没有泛型之前,是主观限制传入的类型,事先知道传入什么类型的) 注意:集合在1.5版本后才能添加基本数据类型,例如上面al.add(3);,因为有了自动封箱、拆箱功能。

七    泛型使用

泛型格式:通过<>来定义要操作的引用数据类型。

Q:在使用Java提供的对象时,什么时候写泛型呢?

A:通常在集合框架中很常见,API文档中只要见到<>就是能定义泛型。

   <>其实尖括号就是用来接收类型的,当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。

API文档中,接口 Collection<E>那个E就是Element(元素)的意思,比较器接口 Comparator<T> 那个T是Type(类型)的意思。

import java.util.*;public class Demo {public static void main(String args[]) {TreeSet<String> ts = new TreeSet<String>(new myComparator());ts.add("asdfdsa");ts.add("sad");ts.add("sdfsd");ts.add("sdf4d");ts.add("sdsa");ts.add("s");for (Iterator<String> it = ts.iterator(); it.hasNext();) {String s = it.next();sop(s + " : " + s.length());}}public static void sop(Object obj) {System.out.println(obj.toString());}
}class myComparator implements Comparator<String> {public int compare(String s1, String s2) {int num = new Integer(s1.length())pareTo(new Integer(s2.length()));if (num == 0) {return s1pareTo(s2);}return num;}
}
输出:

s : 1
sad : 3
sdsa : 4
sdf4d : 5
sdfsd : 5
asdfdsa : 7

八    泛型类

什么是泛型类?private E o; → 简单来说,那个E就是泛型类。

那我们能不能定义自己的泛型类?什么时候定义泛型类呢?

当类中要操作的引用数据类型不确定的时候,就需要泛型类。

早期通过定义Object 来完成扩展,但存在安全隐患。详见下面代码:

class Student {
}class Worker {
}class Tool {Object obj = new Object();public void setObject(Object obj) {System.out.println("setObject");this.obj = obj;}public Object getObject() {System.out.println("getObject");return obj;}
}public class Test {public static void sop(Object obj) {System.out.println(obj);}public static void main(String args[]) {Tool t = new Tool();t.setObject(new Student());Worker w = (Worker) t.getObject();}
}

上面的程序抛出 java.lang.ClassCastException异常,

如果你不用泛型,就有可能出现这种问题。

编译能通过,但运行会出现转换异常。

因此现在通过泛型来完成扩展:

class Student {
}class Worker {
}class Utils<myType> {private myType mT;public void setObject(myType mT) {System.out.println("setObject");this.mT = mT;}public myType getObject() {System.out.println("getObject");return mT;}
}public class Test {public static void sop(Object obj) {System.out.println(obj);}public static void main(String args[]) {Utils<Student> t = new Utils<Student>();t.setObject(new Student());Student s = t.getObject();}
}

上面的程序,如果有问题,编译时就已经不能通过了。

九    泛型方法

class Demo<T>{public void show(T t){System.out.println("show:"+t);}
}

看上面的方法,以前要show(String s)这样,现在只需要show(T t)这样,
方法中也能够操作不确定类型的数据了。

泛型类定义的泛型,在整个类中有效,如果被方法使用,

那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。

为了让不同方法可以操作不同类型,而且类型还不确定。

那么可以将泛型定义在方法上:

class AAA<T> // 泛型类
{public void show(T t) {System.out.println(t);}public void print(T t) {System.out.println(t);}
}class BBB // 只定义泛型方法
{public <T> void show(T t) {System.out.println(t);}public <T> void print(T t) {System.out.println(t);}
}class CCC<R> // 泛型类加泛型方法
{public <T> void show(T t) {System.out.println(t);}public <Q> void print(Q t) {System.out.println(t);}public void print2(R t) {System.out.println(t);}
}public class Test2 {public static void sop(Object obj) {System.out.println(obj);}public static void main(String args[]) {AAA<String> a = new AAA<String>();a.show("AAA"); // OKa.print("XXX"); // OK// a.print(234); //转换失败BBB b = new BBB();b.show("AAA"); // OKb.print("XXX"); // OKb.print(234); // OKCCC<Integer> c = new CCC<Integer>();c.show("AAA"); // OKc.print("XXX"); // OKc.print2(234); // OK}
}

输出:

AAA
XXX
AAA
XXX
234
AAA
XXX
234

十    静态方法泛型

Q:既有泛型类,类里面又有泛型方法,重复吗?

A:不重复。因为泛型类中也可以定义泛型方法。

看下面的代码。

class Demo<T>{public static void method(T t){System.out.println(t);}
}

程序提示无法从静态上下文中引用非静态 类T;因为<T>类型的明确,要先有对象。

特殊之处:静态方法不可以访问类上定义的泛型,因为只有在实例化的时候,才会使用泛型。

          如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上。

         放到返回类型的前面,修饰符的后面,也就是void前面,static后面。如下面代码所示:

public static <T> void setName(T t){}

十一    泛型接口

泛型还能定义在接口上。不过和 泛型类和泛型方法 相比,泛型接口用得就没那么多。示例代码如下:

interface Inter<T> {void show(T t);
}class Impl implements Inter<String> {public void show(String t) {System.out.println("show:" + t);}
}

interface inter<T> {void show(T t);
}class AAA implements inter<String> {public void show(String t) {System.out.println("T : " + t); //T : aaa}
}public class Test3 {public static void main(String args[]) {new AAA().show("aaa");}
}
interface inter<T> {void show(T t);
}class AAA<T> implements inter<T> {public void show(T t) {System.out.println("T : " + t);//T : aaa}
}public class Test3 {public static void main(String args[]) {AAA<String> a = new AAA<String>();a.show("aaa");}
}

另外,如果事先泛型接口的时候,还是不知道要什么类型,

子类就可以继续定义为泛型类,让它的实现对象去确定要什么类型。

十二    泛型限定

1、引入<?>的概念

import java.util.*;public class Demo {public static void main(String args[]) {ArrayList<String> al = new ArrayList<String>();al.add("abc");al.add("sadf");al.add("sa");ArrayList<Integer> al1 = new ArrayList<Integer>();al1.add(1);al1.add(2);al1.add(3);printColl(al);printColl(al1);}public static void printColl(ArrayList<?> al) { //注意这里Iterator<?> it = al.iterator();while (it.hasNext()) {System.out.print(it.next()+" ");}}
}//输出:abc sadf sa 1 2 3 

上面的程序,如果printColl()方法的参数是“ArrayList<String> al”,那么就只能打印String类型的对象,

而我们想要printColl()方法能够打印任意类型的对象,虽然可以用“ArrayList al”作参数,但之前说过这样并不严谨。

此时,我们可以这样写:public static void printColl(ArrayList<?> al){……},

<?>通配符(或称“占位符”)用于接收不明确的类型

Q:也可以这样写:public static <T> void printColl(ArrayList<T> al){……},两种写法有什么区别呢?

A:<T>可以接收并定义对象  T t = it.next(); , ?就不行了。

   另外,如果用<?>就不可以打印长度 System.out.println(it.next().length);

   因为传入的类型可能没有length属性。<?>不能使用类型特有的方法。像toString()这种定义在Object的就可以。

   也就是,<T>代表了具体类型,而<?>代表了不明确的类型。

注意:printColl(ArrayList<?> al)这样加个<?>还不是泛型限定。

2、泛型限定

import java.util.*;public class Demo {public static void main(String args[]) {ArrayList<Person> al = new ArrayList<Person>();al.add(new Person("Person1"));al.add(new Person("Person2"));al.add(new Person("Person3"));ArrayList<Student> al1 = new ArrayList<Student>();al1.add(new Student("Student1"));al1.add(new Student("Student2"));al1.add(new Student("Student3"));//若通过printColl(ArrayList<Person> al)打印Student集合al1,//则相当于 ArrayList<Person> al = new ArrayList<Student>();//错误,编译器是不允许的。printColl(al1);//这是错误的,不能直接写}// public static void printColl(ArrayList<Person> al){ 这样只能接受personpublic static void printColl(ArrayList<? extends Person> al) {// 这样可以接收别的子类Iterator<? extends Person> it = al.iterator();while (it.hasNext()) {System.out.println(it.next().getName());}}
}class Person {private String name;Person(String name) {this.name = name;}public String getName() {return name;}
}class Student extends Person {Student(String name) {super(name);}
}

上面的程序,因为Student extends Person,

开始我们想通过printColl(ArrayList<Person> al)接收Student类型的集合对象,

但编译器不允许,为什么呢?

因为这种操作,相当于 ArrayList<Person> al = new ArrayList<Student>(); ,

而Person有可能不止Student一个子类,如果这种操作允许则可能出现类型安全问题,

出现类似于“狗放猪圈,猪放狗圈”的情况。

使用泛型,必须保证左右两边的泛型类型一致。

如果想要打印Person及其子类,这时候就需要泛型限定。

public static void printColl(ArrayList<? extends Person> al){……}

将接收对象的类型限定在Person及其子类的范围里面。

? 通配符,也可以理解为占位符。

泛型的限定:

? extends E:可以接收E类型或者E的子类型,限定上限(规定父类只能是E)。

? super E:可以接收E类型或者E的父类型,下限。

3、泛型限定的使用

下面通过比较的例子,进一步说明泛型限定的使用。
之前一直不明白这个例子有什么一样,感觉就是使用了泛型,
而跟泛型限定没什么关系。看代码:
import java.util.*;public class Demo {public static void main(String args[]) {TreeSet<Student> ts = new TreeSet<Student>(new Comp());ts.add(new Student("Student1",11));ts.add(new Student("Student2",12));ts.add(new Student("Student3",13));ts.add(new Student("Student3",14));System.out.println(ts); //输出:[Student@12b6651, Student@4a5ab2, Student@1888759]}
}class Person {private String name;private int age;Person(String name,int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}class Student extends Person implements Comparable<Person> {Student(String name,int age) {super(name,age);}@Overridepublic int compareTo(Person p) {return this.getAge();}
}class Comp implements Comparator<Person> {@Overridepublic int compare(Person p1, Person p2) {return p1.getName()pareTo(p2.getName());}
}
 

现在用的是 TreeSet(Comparator<? super E> comparator) 这个构造方法,

再看Comp类,“Comparator<? super E> comparator”在声明泛型的时候限定了可以用的泛型类型,,

而现在“class Comp implements Comparator<Person>”是在使用的时候限定了引用的类型,使得引用指向继承了某一个类或接口的类型。

也就是声明时限定上、下限,使用时明确引用限定的类型。更简单的例子:

GenericFoo<? extends List> foo = null;
foo = new GenericFoo<ArrayList>();
foo = new GenericFoo<LinkedList>();
 

十三   泛型限定2

import java.util.*;public class Demo {public static void main(String args[]) {// TreeSet(Comparator<? super E> comparator)TreeSet<Student> ts_Stu = new TreeSet<Student>(new PersonComp());// new StuComp()ts_Stu.add(new Student("Student1"));ts_Stu.add(new Student("Student2"));ts_Stu.add(new Student("Student3"));ts_Stu.add(new Student("Student3"));Iterator<Student> it_Stu = ts_Stu.iterator();while (it_Stu.hasNext()) {System.out.println(it_Stu.next().getName());}TreeSet<Worker> ts_Worker = new TreeSet<Worker>(new PersonComp());// new WorkerComp()ts_Worker.add(new Worker("Worker1"));ts_Worker.add(new Worker("Worker2"));ts_Worker.add(new Worker("Worker3"));ts_Worker.add(new Worker("Worker3"));Iterator<Worker> it_Worker = ts_Worker.iterator();while (it_Worker.hasNext()) {System.out.println(it_Worker.next().getName());}}
}/** class StuComp implements Comparator<Student>{* * @Override * public int compare(Student s1, Student s2) { * return s1.getName()pareTo(s2.getName()); * } }* * class WorkerComp implements Comparator<Worker>{* * @Override * public int compare(Worker w1, Worker w2) {* return w1.getName()pareTo(w2.getName()); * }}*/class PersonComp implements Comparator<Person> {@Overridepublic int compare(Person p1, Person p2) {return p1.getName()pareTo(p2.getName());}
}class Person {private String name;public Person(String name) {super();this.name = name;}public String getName() {return name;}@Overridepublic String toString() {return "Person [name=" + name + "]";}}class Student extends Person {public Student(String name) {super(name);}
}class Worker extends Person {public Worker(String name) {super(name);}
}

又把上面 TreeSet(Comparator<? super E> comparator)的例子单独拿出来说明了一次,

上面的例子,因为Student和Worker都是Person的子类,

因此,可以在比较器中接收它们的父类型Person,这样就不用写两个比较器这么麻烦了。

简便当然也带来局限性,只能使用Person的方法。

泛型限定,是泛型进行扩展用的。

更多推荐

12. JavaAPI

本文发布于:2024-02-13 20:44:43,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1760564.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:JavaAPI

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!