@w460461339
2016-11-15T23:03:48.000000Z
字数 5994
阅读 920
Java基础
今天学习的主要是Java下Set的几个子类及其特点。不过在那,先看看一个登录注册的实际案例。

具体的要求见上图,这里不贴源码,只是想说明下, 怎么运用面向对象的方法对该问题进行分析。
运用面向对象的方法,我们需要考虑这么几个问题:
A:有哪些类呢?
B:每个类有哪些东西?
C:类与类之间有什么关系?
一般是这样,我们在写出所需要的类之后,还需要对它进行测试(当然你在自己所创建的类中写main方法测试也是可以的, 但是这样今后还要删除,比较麻烦),所以一般都会大致上分为两个类:
1 实际所需类
2 测试类
至于在实际所需类别中如何进行划分,又怎么分出若干类以及子类,却又另说了。因此,我们在这个例子中,先大致划分为两类:
1 用户类
2 测试类
在大致区分出类别之后,我们可以开始划分每个类中都包含什么东西。
由之前画的图可以看到,我们的用户类需要包含这么几个东西
成员变量:用户名,密码
成员方法:登陆,注册;获取用户名,密码;设置用户名,密码
但这样的设置有一个问题,以后操作进行添加时,需要在用户类中进行修改,没有达到对象,数据,操作三者分离的目标。因此,我们将针对用户的操作提取出来,将用户类分为:用户基本信息类,用户操作类。在用户基本信息类中,提供用户的用户名和密码,以及相应的设置和获取方法;在用户操作类中,我们接口和相应的子类实现,提供注册,登录的方法。
同时,需要在用户操作类中,提供一个静态变量,以储存所有的用户信息。
另外,在测试类中,我们只要在mian方法中将上述内容进行测试即可。
在这里,关系很简单,用户基本信息类提供了用户操作类所需要的对象;用户操作类中储存了用户基本信息类的对象,并对外提供针对用户基本信息类的方法;在测试类中,可以调用这些方法对用户信息类的对象进行操作。
或者这么理解,用户基本信息类提供了针对单个用户基本信息类对象的操作,比如设置,获取等(层面是在对象内部);用户操作类提供了对用户基本信息类对象的方法(层面是在对象对象之间),比如登陆,注册等等;而测试类则提供了测试这些方法的区域。
分包主要有这么几种方法:
A 功能划分
B 模块划分
C 先按模块划分,再按功能划分
这次,我们按照功能划分,有:
用户基本描述类包
用户操作接口包
用户操作类包(实现用户操作接口)
用户测试类
HashSet是Set接口的实现类。它可以利用add,put等方法往里添加元素。具体自己查API。它有两点特性,分别为无序性和唯一性。这和数学中集合的定义基本一致。它的无序性值得是,它并不按照你的输入顺序去储存,它内部自有一套存储规范。
HashSet的底层是HashMap< K,P >,而HashMap又是通过链表加数组实现,和数据结构中的图基本是一个意思。
HashSet的add方法是通过HashMap的put方法来实现的。put方法中包含有HashCode以及euqals方法,基本上就是通过比较HashCode,地址值以及equals方法来进行比较。
public V put(K key, V value) {if (key == null)return putForNullKey(value);int hash = hash(key.hashCode());//获取hash值//散列表冲突处理方法中的分离链接法int i = indexFor(hash, table.length);//散列表的第一个索引for (Entry<K,V> e = table[i]; e != null; e = e.next) {Object k;//先比较Hash值,再比较地址值,最后比较内容if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}modCount++;addEntry(hash, key, value, i);return null;}
其实看的明白的话,这其实的数据结构中的散列表,首先通过不同的hash值将待储存元素挂在不同的类别下(其实是散列表冲突处理方法中的分离链接法)。之后若存在相同hash的元素,只要把这一串上的元素逐一进行比较,就可以判定是否可以把这个元素进行添加,省去了全局进行比较的麻烦。
储存字符串是没有什么问题的,这个试了就很明了,这里也不写代码了。麻烦的是自定义对象,由于在添加的时候,会自动调用对象的equals以及hashCode方法,而自定义类若没有重写这些方法的话,会自动调用Object的equals以及hashCode方法,最后转化成地址的比较,会使得唯一性特性失效。因此,若想利用集合的唯一性对自定义对象进行储存,就需要在自定义类中重写equals以及hashCode方法。当然,贴心的java以及Eclipse早就帮我们做好了,只要右击添加即可。
public class myHashSet {private String name;private int age;public myHashSet() {super();// TODO Auto-generated constructor stub}//楼下的get以及set函数都是自动生成的public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}//这个也都是自动生成的,我们只需要知道原理就好,重写就让他们做@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + age;result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;myHashSet other = (myHashSet) obj;if (age != other.age)return false;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;}}
TreeSet底层是依赖TreeMap实现的(还是实现了接口navigableMap)。具体来说,TreeSet的add方法是利用一个NavigableMap接口的具体实现类来实现的,而这个具体实现类就是TreeMap。数据结构是红黑树。红黑树是AVL树(自平衡二叉树)的变化版。两者是在查找和添加删除元素的时间上有差别,这里不细说,在时间不敏感的程序中意义不大。
TreeSet除了保留了Set的无序性以及唯一性以外,它可以根据用户选择,对所储存的元素按照一定规则进行排序,然后输出。它排序的方法主要有两种,分别是:自然排序,比较器排序。
自然排序即按照其内置的顺序对元素进行排序。若要实现自然排序,在创建TreeSet对象的时候,直接使用默认构造器就可以了。
import java.util.TreeSet;public class myTreeSet {public static void main(String[] args) {TreeSet<String> ts=new TreeSet<String>();ts.add("hello");ts.add("world");ts.add("aloha");for(String s:ts){//输出:aloha,hello,worldSystem.out.println(s);}}}
而我们通过查阅TreeMap的put方法可以看到,它的自然排序主要是通过一个名为compareTo的方法来进行的。我们在String类中也找到了这个方法,那么大致可以猜到,TreeMap是调用每个特定类中的compareTo方法来进行比较。而object中并不包含这个方法,所以若不在自定义类中重写这个方法,就会报错。
class Student{private int age;public Student(int age) {super();this.age = age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}public class myTreeSet {public static void main(String[] args) {TreeSet<Student> ts2=new TreeSet<Student>();ts2.add(new Student(1));ts2.add(new Student(2));ts2.add(new Student(3));ts2.add(new Student(4));for(Student s : ts2){//报错,大概就是说,Student不能被转型成为comparable的对象System.out.println(s.getAge());}}}
解决办法,需要让自定义子类实现Comparable接口,才可以进行比较。而Comparable接口中只有一个方法,就是compareTo方法:
class Student implements Comparable<Student>{//点明是与Student类比较private int age;public Student(int age) {super();this.age = age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Override//实现compareTo方法public int compareTo(Student o) {int number=age-o.getage();return number;}}public class myTreeSet {public static void main(String[] args) {TreeSet<Student> ts2=new TreeSet<Student>();ts2.add(new Student(1));ts2.add(new Student(2));ts2.add(new Student(3));ts2.add(new Student(4));for(Student s : ts2){//这样就不会报错了。并且按照年龄排序System.out.println(s.getAge());}}}
之前我们是将我们想要的比较方法定义在我们的自定义类中,在TreeSet想要进行自然排序的时候就调用它。但若是我们想把这个排序方法单独拿出来,让类显得更加的精简,就需要用到比较器排序。
要想使用比较器排序,我们需要在创建TreeSet对象的时候就传入一个Comparator的对象,让TreeSet利用我们传入的Comparator进行排序。
由于Comparator是一个接口,我们需要自己去实现它具体写法:
class MyComparator implements Comparator<Student> {//表明比较的对象一定是Student的@Overridepublic int compare(Student s1, Student s2) {// int num = this.name.length() - s.name.length();// this -- s1// s -- s2// 姓名长度int num = s1.getName().length() - s2.getName().length();// 姓名内容int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;// 年龄int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;return num3;}}class Student {private String name;private int age;public Student() {super();}public Student(String name, int age) {super();this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}public class myComparator {TreeSet<Student> ts= new TreeSet<Student>(new MyComparator());}
当然,若这个比较器只要用一次,可以用匿名内部类的方式实现(匿名内部类主要要去实现一个接口或者抽象类哦):
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {@Overridepublic int compare(Student s1, Student s2) {// 姓名长度int num = s1.getName().length() - s2.getName().length();// 姓名内容int num2 = num == 0 ? s1.getName().compareTo(s2.getName()): num;// 年龄int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;return num3;}});
这个就是用链表以及HashMap实现的HashSet,它保留了集合的唯一性,但通过加入了链表,使得它变得有序了。