@w460461339
2016-11-16T07:03:48.000000Z
字数 5994
阅读 728
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;
}
//这个也都是自动生成的,我们只需要知道原理就好,重写就让他们做
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public 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,world
System.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的
@Override
public 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>() {
@Override
public 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,它保留了集合的唯一性,但通过加入了链表,使得它变得有序了。