java集合之间的关系(详细分析集合的操作为什么需要equals()和hasCode()方法支持)

先看一张图:

 从上图可以清晰的看到:List和Set两个接口最的区别就是List可以存放重复的元素,而Set里面的元素不可以重复。List和Set这两个接口继承自Collection接口,collection接口有一个add()方法,Collection又继承自Iterable,而iterable里面的iterator()方法返回的是Iterator<T>接口。

由以上分析可知,Collection接口里面有add()和iterator()方法,那么它的子接口也有这两个方法.

 

与Collection相比,List接口有两个方法:

public E get(int indext);//根据索引取得数据
public void set(int index,E element);//根据索引修改数据

下面展示使用get()方法取出列表中的数据:
 


import java.util.ArrayList;
import java.util.List;

public class TestDemo{
	public static void main(String args[]) {
		List<String> list=new ArrayList<>();
		list.add("yao");
		list.add("liang");
		list.add("yong");
		for(int i=0;i<list.size();i++) {
			//使用get()方法取出列表中的数据
			System.out.println(list.get(i));
		}
	}
}

在这个Demo中,get是List中才有的方法,如果实现的Collection接口,那么就不能使用get方法了。(这个可以自己写Demo实验)当然了,我们也可以不适用get输出,也可以使用toArray()方法把集合变成数组进行输出,但是要进行向下转型,对我们来说向下转型是不安全的操作,应该尽量避免。如下面例子:


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class TestDemo{
	public static void main(String args[]) {
		List<String> list=new ArrayList<>();
		list.add("yao");
		list.add("liang");
		list.add("yong");
		Object obj[]=list.toArray();//返回的是Object对象,有可能造成向下转型
		System.out.println(Arrays.toString(obj));
	}
}

从理论上讲,集合中的remove()和contains()方法需要equals()方法的支持。所以要在简单java类中重写equals()方法。


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Person {
	private String name;
	private int age;
	public Person(String name,int age) {
		this.name=name;
		this.age=age;
	}
	public String get() {
		return this.name+this.age;
	}
}

public class TestDemo{
	public static void main(String args[]) {
		List<Person> list=new ArrayList<>();
		list.add(new Person("张三",20));
		list.add(new Person("李四",40));
		list.remove(new Person("张三",20));
		for(int i=0;i<list.size();i++) {
			System.out.println(list.get(i).get());
		}
	}
}

以上这个程序没有重写equals()方法,所以即使调用了remove()方法,依然不能删除对象,这是为什么呢?

  1.  

说到这里就插入一个知识点:equals()方法和==之间的区别:

对于==比较来说:

1,基本数据类型比较的就是值是否相等。

2,引用数据类型比较的就是它们堆内存地址是否相等。(除非是同一个new出的对象,否则返回的都是false,因为每次new都会开辟新的堆内存空间)

对于equals()方法来说,它的源码是这样的:


public boolean equals(Object obj) {
    //this - s1
    //obj - s2
    return (this == obj);
}

从而可以看出:默认情况下equals()方法比较的仍然是堆内存地址是否相等。(栈内存保存的是对象名称,堆内存保存的是对象的内容),在一些类库当中这个方法被重写了,如String、Integer、Date。在这些类当中equals有其自身的实现(一般都是用来比较对象的成员变量值是否相同),而不再是比较类在堆内存中的存放地址了。 所以说,对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是内存中的存放位置的地址值,跟双等号(==)的结果相同;如果被复写,按照复写的要求来。

如下是我定义的一个类使用equals()来进行比较:

class Student{
	private String name;
	private int age;
	public Student (String name,int age) {
		this.name=name;
		this.age=age;
	}
}
public class TestDemo{
	public static void main(String args[]) {
		Student stu1=new Student("张三",12);
		Student stu1=new Student("张三",12);
		System.out.println(stu1==stu2);
		System.out.println(stu1.equals(stu2));
	}
}

输出结果:false,false。

但是当我们复写equals()之后,


class Student{
	private String name;
	private int age;
	public Student (String name,int age) {
		this.name=name;
		this.age=age;
	}
	public boolean equals(Object obj) {
		//为了提高效率:如果两个内存地址相等,那么一定是指向同一个对内存中的对象,
		//就无需比较两个对象的属性值(自己跟自己比,没啥意义嘛)
		if(this==obj) {
			return true;
		}
		 //为了提供程序的健壮性
		//我先判断一下,obj是不是学生的一个对象,如果是,再做向下转型,如果不是,直接返回false。
		if(!(obj instanceof Student)) {
			return false;
		}
		Student stu=(Student)obj;
		return this.name.equals(stu.name) && this.age == stu.age;//判断两个对象的属性值是否相等

	}
}
public class TestDemo{
	public static void main(String args[]) {
		Student stu1=new Student("张三",12);
		Student stu2=new Student("张三",12);
		System.out.println(stu1==stu2);
		System.out.println(stu1.equals(stu2));
	}
}

返回结果false,true。

由此我们也就得出:基本数据类型如:int,long,char,,,比较相等一般不会用到equals()方法,用==就可以了。对于引用数据类型,比较相等就会用到equals(),因为使用==来比较只是比较的对象的堆内存地址,默认的equals()方法虽然比较的是堆内存地址,但是可以在我们定义的类中复写这个方法,按照我们的要求来比较。

 

1,基本数据类型:已经


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Person {
	private String name;
	private Integer age;
	//Integer和int的区别就是Integer有空,而int没有空,Integer还可以有equals操作
	public Person(String name,Integer age) {
		this.name=name;
		this.age=age;
	}
	public String get() {
		return this.name+this.age;
	}
	public boolean equals(Object obj) {
		if(this==obj) {
			return true;
		}
		if(obj==null) {
			return false;
		}
		if(!(obj instanceof Person)) {
			return false;
		}
		Person per=(Person)obj;//向下转型,这里可以
		return this.name.equals(per.name) && this.age.equals(per.age);
	}
}

public class TestDemo{
	public static void main(String args[]) {
		List<Person> list=new ArrayList<>();
		list.add(new Person("张三",20));
		list.add(new Person("李四",40));
		list.remove(new Person("张三",20));
		for(int i=0;i<list.size();i++) {
			System.out.println(list.get(i).get());
		}
	}
}

Vector

下面介绍Vector类,Vector子类是从JDK1.0就开始提出的,而ArrayList从JDK1.2才提出。把以上代码中的ArrayList全部换成Vector也可以得到同样的输出,但是两者还是有点区别的,区别就在于ArrayList是异步的,线程不安全的,而Vector是同步的,属于线程安全的操作。在实际操作中,一般都是一个线程对应一个列表。

LinkedList

LinkedList 是 Java 集合中比较常用的数据结构,与 ArrayList 一样,实现了 List 接口,只不过 ArrayList 是基于数组实现的,而 LinkedList 是基于链表实现的。所以 LinkedList 插入和删除方面要优于 ArrayList,而随机访问上则 ArrayList 性能更好。

1,

在查找和删除某元素时,区分该元素为 null和不为 null 两种情况来处理,LinkedList 中允许元素为 null。

3、基于链表实现不存在扩容问题。

4、查找时先判断该节点位于前半部分还是后半部分,加快了速度

5、因为基于链表,所以插入删除极快,查找比较慢。

6、实现了栈和队列的相关方法,所以可作为栈,队列,双端队列来用

Set

Set接口跟List接口还有一个区别就是Set接口没有对Colletion接口进行扩充,跟Collection中的方法一模一样的。Set有两个重要的子类就是HashSet和TreeSet;

javaHashSet与TreeSet的区别:https://blog.csdn.net/coding_1994/article/details/80553554

javaHashSet与TreeSet的区别:https://blog.51cto.com/13579086/2069837

HashSet和TreeSet两者的区别在于:HashSet是无序保存,而TreeSet是有序保存。

Hash算法初步解释:Hash算法就相当于某一天你去公园看演出,观众台有很多作为,你去的时候还有好多座位,你就随便选了一个,这跟你拿着电影票选座位是不一样的,电影院是你自己去对号入座。TreeSet是使用升序的方式来排列的。TreeSet需要Compareable的接口支持,所有属性必须参与比较。正是因为TreeSet使用起来太麻烦,所以一般不使用。

TreeSet在判断段重复元素的时候使用的是Compaeable的支持,而HashCode在判断重复元素元素的时候使用的是Object类里面的两个方法:

public int hashCode()//Hash码
public boolean equals(Object obj)

在java中对象的比较分为两步:第一步通过对象的唯一编码找到对象的信息。第二部,当编码匹配之后再调用equals()方法进行内容比较。只有这两部一样的话才能确定是一样的。所以,以后开发中,如果要标识对象的唯一性,一定要得到hashCode()和equales()两个方法的支持。但是eclipse已经可以自动生成了,不需要我们自己去写。

在很多时候使用Set集合并不是为了排序,而是让其进行重复元素的过滤。重复元素又需要依靠hashCode()和equals()支持,多以不是必须的时候,在使用Set接口的时候尽量使用系统提供的类实现。例如String ,Integer。

原则:保存自定义对象一般使用List接口,保存系统类信息的时候使用Set接口可以消除掉重复。

集合输出使用iterator和foreach,还有两种。

Map的主要功能是为了查找数据,而Collection主要是为了进行输出,所以这两者有着本子的区别。当然,Map集合也是可以输出的,Collection也是可以查找到,但是两者的重点是不一样的,

下面是Map集合的主要方法:

public V put(K key,V value)---向集合之中添加数据

public V get(Object key)---根据key取得对应的value,如果没有就返回null

public Set<k> keySet()---取得所有的key的信息,key不能重复

public Collection<V> values()---取得所有的values,不关注内容是否重复

public Set<Map.Entry<K,V>> entrySet()---将Map集合变成Set集合

Map主要的子类有:HashMap,HashTable,TreeMap其中,TreeMap主要是用来排序才能使用到。其余两个的区别请看下面:

 

HashMap和HashTable在存储自定义对象的时候也需要覆写Object对象总的hashCode和equales这两个方法。因为要确定对象的唯一性。但是知道这没什么用,因为在实际开发过程中,Map中的key主要就是String,Integer这两种类型,这两个都是系统类,所以系统就帮我们覆写好了这两个方法,不用我们操心了。

与HashTable相比,在数据量小的时候HashMap是按照链表的模式进行存储的,当数据量变大的时候,为了快速将进行查找,(因为链表查找的时间复杂度是N,而二叉树查找的是按复杂度是N的对数),那么会将这个链表变成红黑树,使用哈希码作为数据定位,来进行保存。HashMap还有一个特征就是它的所有的方法都是线程不安全的,都是线程异步的,也就意味着他是线程不安全的,虽然在高并发的时候考虑到访问速度,但是他是线程不安全的。(在这里线程安全是指多个线程可以同时操作HashTable,前提是必须等待上一个线程操作完成之后下一个线程才能操作,这样既保证了线程安全,而HashMap每次只能保证一个线程来访问集合,如果访问这个集合的线程比较多的的话,就可能出现线程不安全问题。)

其次就是HashMap可以接受null的键和值,而HashTable就不行

接下来就介绍一个结合了HashTable的安全性和HashMap的高性能的一个东西,这个东西就叫做ConcurrentHash,在使用ConcurrentHashMap的时候既可以保证多个线程更新数据的同步,又可以保证很高效的查询速度,这就是ConcurrentHashMap。

 

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值