1.泛型和类型安全的容器
@SuppressWarnings注解及其参数表示只有有关“不受检查的异常”的警告信息应该被抑制。
例子:
class Apple{
private static long counter;
private final long id = counter++;
public long id(){return id;}
}
class Orange{}
public class ApplesAndOrangesWithoutGenerics{
@SuppressWarnings("unchecked")
public static void main(String[] args){
ArrayList apples = new ArrayList();
for(int i=0; i<3; i++){
apples.add(new Apple());
}
apples.add(new Orange());
for(int i=0;i<apples.size();i++){
((Apple)apples.get(i)).id();
//orange is detected only at run time
}
}
}/*(Execute to see output)*///:~
因为ArrayList保存的是Object,因此你不仅可以通过ArrayList的add()方法将Apple对象放进这个容器,还可以添加Orange对象,而且无论在编译期还是运行时都不会有问题。当你使用ArrayList的get()方法取出你认为是Apple的对象时,你得到的只是Object引用,必须将其转型为Apple,因此,需要将整个表达式括起来,在调用Apple的id()方法之前,强制执行转型。否则,你就会得到语法错误。在运行时,当你试图将Orange对象转型为Apple时,你就会以前面提及的异常的形式得到一个错误。
要想定义用来保存Apple对象的ArrayList,你可以声明ArrayList<Apple>,而不仅仅只是ArrayList,其中尖括号括起来的是类型参数(可以有多个),它指定了这个容器实例可以保存的类型。通过使用泛型,就可以在编译期防止将错误类型的对象放置到容器中。如上面的例子,编译期可以阻止你将Orange放置到apples中,因此它变成一个编译期错误,而不再是运行时错误。你还应该注意到,在将元素从List中取出时,类型转换也不再是必需的了。因为List知道它保存的是什么类型,因此它会在调用get()时替你执行转型。这样,通过使用泛型,你不仅知道编译器将会检查你放置到容器中的对象类型,而且在使用容器中的对象时,可以使用更加清晰的语法。
当你指定了某个类型作为泛型参数时,你并不仅限于只能将该确切类型的对象放置到容器中。向上转型也可以象作用于其他类型一样作用于泛型。如上例,你可以将Apple的子类型添加到被指定为保存Apple对象的容器中。
当打印对象时,如果是默认从Object继承的toString()方法时,将打印出类名,后面跟随该对象的散列码的无符号十六进制表示(这个散列码是通过hashCode()方法产生的)。
2.持有对象基本概念
java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念:
1)Collection。一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而Set不能有重复元素。Quence按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。
2)Map。一组成对的“键值对”对象,允许你使用键来查找值。ArrayList允许你使用数字来查找值,因此在某种意义上讲,它将数字与对象关联在一起。映射表允许我们使用另一个对象来查找某个对象,它也被称为“关联数组”,因为它将某些对象与另外一些对象关联在了一起;或者被称为“字典”,因此你可以使用键对象来查找值对象,就像在字典中使用单词来定义一样。Map是强大的编程工具。
Collection接口概括了序列的概念-一种存放一组对象的方式。ArrayList是最基本的序列类型。
3.添加一组元素
在java.util包中的Arrays和Collections类中都有很多实用的方法,可以在一个Collection中添加一组元素。Arrays.asList()方法接受一个数组或是一个用逗号分隔的元素列表(使用可变参数),并将其转换为一个List对象。Collections.addAll()方法接受一个Collection对象,以及一个数组或是一个用逗号分隔的列表,将元素添加到Collection中。
Collection.addAll()成员方法只能接受另一个Collection对象作为参数,因此它不如Arrays.asList()或Collections.addAll()灵活,这两个方法使用的都是可变参数列表。
你也可以直接使用Arrays.asList()的输出,将其当作List,但是在这种情况下,其底层表示的是数组,因此不能调整尺寸。如果你试图用add()或delete()方法在这种列表中添加或删除元素,就有可能会引发去改变数组尺寸的尝试,因此你将在运行时获得“Unsupported Operation(不支持的操作)”错误。
Arrays.asList()方法的限制是它对所产生的List的类型做出了最理想的假设,而并没有注意你对它会赋予什么样的类型。
class Snow{}
class Powder extends Snow{}
class Light extends Powder{}
class Heavy extends Powder{}
class Crusty extends Snow{}
class Slush extends Snow{}
public class AsListInference{
public static void main(String[] args){
List<Snow> snow1 = Arrays.asList(new Crusty(),new Slush(),new Powder());
//Won't compile:
//List<Snow> snow2 = Arrays.asList(new Light(),new Heavy());
//Compiler says:
//fond:java.util.List<Powder> required:java.util.List<Snow>
//Collections.addAll() doesn't get confused;
List<Snow> snow3 = new ArrayList<Snow>();
Collections.addAll(snow3,new Light(),new Heavy());
//Give a hint using an explicit type argument specification:
List<Snow> snow4 = Arrays.<Snow>asList(new Light(),new Heavy());
}
}
当试图创建snow2时,Arrays.asList()中只有Powder类型,因此它会创建List<Powder>而不是List<Snow>,尽管Collections.addAll()工作的很好,因为它从第一个参数中了解到了目标类型是什么。
正如你从创建snow4的操作中所看到的,可以在Arrays.asList()中间插入一条“线索”,以告诉编译器对于有Arrays.asList()产生的List类型,实际的目标类型应该是什么。这称为显式类型参数说明。
正如你所见,Map更加复杂,并且除了用另一个Map之外,java标准库没有提供其他任何自动初始化它们的方式。
4.容器的打印
你必须使用Arrays.toString()来产生数组的可打印表示,但是打印容器无需任何帮助。
java容器类库中的两种主要类型,它们的区别在于容器中每个“槽”保存的元素个数。Collection在每个槽中只能保存一个元素。此类容器包括:List,它以特定的顺序保存一组元素;Set,元素不能重复;Quence,只允许在容器的一“端”插入对象,并从另一“端”移除对象。Map在每个槽内保存了两个对象,即键和与之相关联的值。
ArrayList和LinkedList都是List类型,从输出可以看出,它们都按照被插入的顺序保存元素。两者的不同之处不仅在于执行某些类型的操作时的性能,而且LinkedList包含的操作也多于ArrayList。
HashSet、TreeSet和LinkedHashSet都是Set类型,输出显示在Set中,每个相同的项只有保存一次,但是输出也显示了不同的Set实现存储元素的方式也不同。HashSet使用的是相当复杂的方式来存储元素,这种方式将在第17章中介绍,此刻你只需要知道这种技术是最快的获取元素方式,因此,存储的顺序看起来并无实际意义。而TreeSet,它按照比较结果的升序保存对象;LinkedHashSet,它按照被添加的顺序保存对象。
note:你不必指定(或考虑)Map的尺寸,因为它自己会自动地调整尺寸。键和值在Map中的保存顺序并不是它们的插入顺序,因为HashMap实现使用的是一种非常快的算法来控制顺序。
HashMap提供了最快的查找技术,也没有按照任何明显的顺序来保存其元素。TreeMap按照比较结果的升序保存键。
LinkedHashMap则按照插入顺序保存键,同时还保留了HashMap的查找速度。
5.List
List接口在Collection的基础上添加了大量的方法,使得可以在List的中间插入和移除元素。
有两种类型的List:
1)基本的ArrayList,它擅长于随机访问元素,但是在List的中间插入和移除元素时较慢。
2)LinkedList,它通过代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问。LinkedList在随机访问方面相对比较慢,但是它的特性集较ArrayList更大。
List允许在它被创建之后添加元素、移除元素,或者自我调整尺寸。这正是它的重要价值所在:一种可修改的序列。
当确定一个元素是否属于某个List,发现某个元素的索引,以及从某个List中移除一个元素时,都会用到equals()方法(它是根类Object的一部分)。详见11.5例子,每个Pet都被定义为唯一的对象,因此即使在列表中已经有两个Cymric,如果我再新创建一个Cymric,并把它传递给indexOf()方法,其结果仍会是-1(表示没找到它),而且尝试调用remove()来删除这个对象,也会返回false。对于其他的类,equals()的定义可能有所不同。例如,两个String只有在内容完全一样的情况下才会是等价的。因此为了防止意外,就必须意识到List的行为根据equals()的行为而有所变化。
对于LinkedList,在列表中间插入和删除都是廉价操作,但是对于ArrayList,这可是代价高昂的操作。这是否意味着你应该永远不要在ArrayList的中间插入元素,并最好是切换到LinkedList?不,这仅仅意味着,你应该意识到这个问题,如果你开始在某个ArrayList的中间执行很多插入操作,并且你的程序开始变慢,那么你应该看看你的List实现有可能是罪魁祸首(发现此类瓶颈的最佳方式是使用仿真器)。优化是一个很棘手的问题,最好的策略就是置之不顾,直到你发现需要担心它了(尽管理解这些问题总是一种好的思路)。
remove(),如果参数是对象引用的话,它所产生的行为依赖于equals()方法,但如果参数是索引值时,就不必担心equals()的行为。
6.迭代器
任何容器类,都必须有某种方式可以插入元素并将它们再次取回。毕竟,持有事物是容器最基本的工作。
迭代器(也是一种设计模式)是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结构。此外,迭代器通常被称为轻量级对象:创建它的代价小。因此,经常可以见到对迭代器有些奇怪的限制;例如,java的Iterator只能单向移动,这个Iterator只能用来:
1)使用方法iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
2)使用next()获得序列中的下一个元素。
3)使用hasNext()检查序列中是否还有元素。
4)使用remove()将迭代器新近返回的元素删除。
如果你只是向前遍历List,并不打算修改List对象本身,那么你可以看到foreach语法会显得更加简洁。
Iterator还可以移除由next()产生的最后一个元素,这意味着在调用remove()之前必须先调用next()(remove()是所谓的“可选”方法(还有一些其它的这种方法),即不是所有的Iterator实现都必须实现该方法)。
Iterator的真正威力:能够将遍历序列的操作与序列底层的结构分离。正由于此,我们有时会说:迭代器统一了对容器的访问方式。详见11.6第二个例子的display()方法。
7.ListIterator
ListIterator是一个更加强大的Iterator的子类型,它只能用于各种List类的访问。尽管Iterator只能向前移动,但是ListIterator可以双向移动。它还可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个元素。你可以通过调用listIterator()方法产生一个指向List开始处的ListIterator,并且还可以通过调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处地ListIterator。
8.LinkedList
LinkedList也像ArrayList一样实现了基本的List接口,但是它执行某些操作(在List的中间插入和移除)时比ArrayList更高效,但在随机访问操作方面却要逊色一些。
LinkedList还添加了可以使其用作栈、队列或双端队列的方法。
Queue接口,它在LinkedList的基础上添加了element()、offer()、peek()、poll()和remove()方法,以使其可以成为一个Queue的实现。这一点有1.5jdk api有出入,在api中,讲的是Queue extends Collection,而LinkedList implements Queue。
9.Stack
“栈”通常是指“后进先出”(LIFO)的容器。有时栈也被称为叠加栈,因为最后“压入”栈的元素,第一个“弹出”栈。经常用来类比栈的事物是装有弹簧的储放器中的自动餐托盘,最后装入的托盘总是最先拿出使用的。
LinkedList具有能够直接实现栈的所有功能的方法,因此可以直接将LinkedList作为栈使用。但是可以直接用Stack类更直接。java.util.Stack实现了List接口,如果你只需要栈的行为,就不需要使用继承了,这样会产生List中的其他方法,可以采用11.8中的栈例子。
10.Set
Set不保存重复的元素。如果你试图将相同对象的多个实例添加到Set中,那么它就会阻止这种重复现象。Set中最常被使用的是测试归属性,你可以很容易地询问某个对象是否在某个Set中。正因为如此,查找就成为了Set中最重要的操作,因此你通常都会选择一个HashSet的实现,它专门对快速查找进行了优化。
Set具有与Collection完全一样的接口,因此没有任何额外的功能,不像前面有两个不同的List。实际上Set就是Collection,只是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)Set是基于对象的值来确定归属性的,而更加复杂的问题将在第17章介绍。
HashSet所维护的顺序与TreeSet或LinkedHashSet都不同,因为它们的实现具有不同的元素存储方式。TreeSet将元素存储在红-黑树数据结构中,而HashSet使用的是散列函数。LinkedHashSet因为查询速度的原因也使用了散列,但是看起来它使用了链表来维护元素的插入顺序。练习16稍后完成。
11.Map
将对象映射到其他对象的能力是一种解决编程问题的杀手锏。例如,可以用来检查java的Random类的随机性,键是由Random产生的数字,而值是该数字出现的次数。详见11.10例子。
Map与数组和其他的Collection一样,可以很容易地扩展到多维,而我们只需将其值设置为Map(这些Map的值可以是其他容器,甚至是其他Map)。因此,我们能够很容易地将容器组合起来从而快速地生成强大的数据结构。例如,假设你正在跟踪拥有多个宠物的人,你所需只是一个Map<Person,List<Pet>>。
12.Queue
队列是一个典型的先进先出(FIFO)的容器。即从容器的一端放入事物,从另一端取出,并且事物放入容器的顺序与取出的顺序是相同的。队列常被当作一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中特别重要,就像你将在第21章中所看到的,因为它们可以安全的将对象从一个任务传输给另一个任务。
LinkedList提供了方法以支持队列的行为,并且它实现了Queue接口,因此LinkedList可以用作Queue的一种实现。通过将LinkedList向上转型为Queue。详见11.11例子
offer()方法是与Queue相关的方法之一,它在允许的情况下,将一个元素插入到队尾,或者返回false。peek()和element()都将在不移除的情况下返回队头,但是peek()方法在队列为空时返回null,而element()会抛出NoSuchElementException异常。poll()和remove()方法将移除并返回队头,但是poll()在队列为空时返回null,而remove()会抛出NoSuchElementException异常。
13.PriorityQueue
先进先出描述了最典型的队列规则。队列规则是指在给定一组队列中的元素的情况下,确定下一个弹出队列的元素的规则。先进先出声明的是下一个元素应该是等待时间最长的元素。
优先级队列声明下一个弹出元素是最需要的元素(具有最高的优先级)。例如,在飞机场,当飞机临近起飞时,这架飞机的乘客可以在办理登机手续时排到队头。如果构建了一个消息系统,某些消息比其他消息更重要,因此应该更快地得到处理,那么它们何时得到处理就与它们何时到达无关。PriorityQueue添加到java se5中,是为了提供这种行为的一种自动实现。
当你在PriorityQueue上调用offer()方法插入一个对象时,这个对象会在队列中被排序(这实际上依赖于具体实现,优先级队列算法通常会在插入时排序-维护一个堆,但是它们也可能在移除时选择最重要的元素。如果对象的优先级在它所在队列中等待时可以进行修改,那么算法的选择就显得很重要了)。默认的排序将使用对象在队列中的自然顺序,但是你可以通过提供自己的Comparator来修改这个顺序。PriorityQueue可以确保当你调用peek()、poll()和remove()方法时,获取的元素将是队列中优先级最高的元素。
详见11.11.1例子你可以看到,重复是允许的,最小的值拥有最高的优先级(如果是String,空格也可以算作值,并且比字母的优先级高)。PriorityQueue与Integer、String和Character一起工作,因此这些类已经内建了自然排序。如果你想在PriorityQueue中使用自己的类,就必须包括额外的功能以产生自然排序,或者必须提供自己的Comparator。
14.Collection和Iterator
Collection是描述所有序列容器的共性的根接口,它可能会被认为是一个“附属接口”,即因为要表示其他若干个接口的共性而出现的接口。另外,java.util.AbstractCollection类提供了Collection的默认实现,使得你可以创建AbstractCollection的子类型,而其中没有不必要的代码重复。
使用接口描述的一个理由是它可以使我们能够创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多的对象类型。因此,如果我编写的方法将接受一个Collection,那么该方法就可以应用于任何实现了Collection的类-这也就使得一个新类可以选择去实现Collection接口,以便我的方法可以使用它。但是,有一点很有趣,就是我们注意到标准C++类库中并没有其容器的任何公共基类-容器之间的所有共性都是通过迭代器达成的。在java中,遵循C++的方式看起来似乎很明智,即用迭代器而不是Collection来表示容器的共性。但是,这两种方法绑定到了一起,因为实现Collection就意味着需要提供iterator()方法。
15.Foreach与迭代器
能够与foreach一起工作是所有Collection对象的特性。之所以能够工作,是因为java se5引入了新的被称为Iterable的接口,该接口包含一个能够产生Iterator的iterator()方法,并且Iterable接口被foreach用来在序列中移动。因此如果你创建了任何实现Iterable的类,都可以将它用于foreach语句中。
在java se5中,大量的类都是Iterable类型,主要包括所有的Collection类(但是不包括各种Map)。例如:
public class EnvironmentVariables{
public static void main(String[] args){
for(Map.Entry entry:System.getenv().entrySet()){
System.out.println(entry.getKey()+": "+entry.getValue());
}
}
}
System.getenv()(在java se5之前还没有它,因为该方法被认为与操作系统的耦合度过紧,因此会违反“编写一次,到处运行”的原则。现在提供它表明java的设计者们更加务实了)返回一个Map,entrySet()产生一个由Map.Entry的元素构成的Set,并且这个Set是一个Iterable,因此它可以用于foreach循环。
foreach语句可以用于数组或其他任何Iterable,但是这并不意味着数组肯定也是一个Iterable,而任何自动包装也不会自动发生。详见11.13例子,尝试把数组当作一个Iterable参数传递会导致失败。这说明不存在任何从数组到Iterable的自动转换,你必须手工执行这种转换。
Arrays.asList()产生的List对象会使用底层数组作为其物理实现是很重要的。只要你执行的操作会修改这个List,并且你不想原来的数组被修改,那么你就应该在另一个容器中创建一个副本。代码:
Integer[] ia = {1,2,3,4,5};
List<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia));
List<Integer> list2 = Arrays.asList(ia);
针对list1的顺序进行操作,不会打乱ia的顺序,打乱的只是list1中引用ia元素的元素,而针对list2的顺序操作,会打乱ia的顺序,因为list2是直接操作数组。
16.持有对象总结
java提供了大量持有对象的方式:
1)数组将数字与对象联系起来。它保存类型明确的对象,查询对象时,不需要对结果做类型转换。它可以是多维的,可以保存基本类型的数据。但是,数组一旦生成,其容量就不能改变。
2)Collection保存单一的元素,而Map保存相关联的键值对。有了java的泛型,你就可以指定容器中存放的对象类型,因此你就不会将错误类型的对象放置到容器中,并且在从容器中获取元素时,不必进行类型转换。各种Collection和各种Map都可以在你向其中添加更多的元素时,自动调整其尺寸。容器不能持有基本类型,但是自动包装机制会仔细地执行基本类型到容器中所持有的包装器类型之间的双向转换。
3)像数组一样,List也建立数字索引与对象的关联,因此,数组和List都是排序好的容器。List能够自动扩充容量。
4)如果要进行大量的随机访问,就是用ArrayList;如果要经常从表中间插入或删除元素,则应该使用LinkedList。
5)各种Queue以及栈的行为,有LinkedList提供支持。
6)Map是一种将对象(而非数字)与对象相关联的设计。HashMap设计用来快速访问;而TreeMap保持“键”始终处于排序状态,所以没有HashMap快。LinkedHashMap保持元素插入的顺序,但是也通过散列提供了快速访问能力。
7)Set不接受重复元素。HashSet提供最快的查询速度,而TreeSet保持元素处于排序状态。LinkedHashSet以插入顺序保存元素。
8)新程序中不应该使用过时的Vector、Hashtable和Stack。
-------------------------------------------------------------------end--------------------------------------------------------------------------------------------------------------------------------------------------------------
thinking:~?
1)当重写对象的equals的时候,为什么要重写hashCode?
直接从程序中看,是没必要的,因为重写equals方法的时候,根本没用到hashCode,但是equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
2)哈希码值的协定?
hashCode 的常规协定是:
. 在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的 一次执行到同一应用程序的另一次执行,该整数无需保持一致。
. 如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
. 以下情况不 是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是 ,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
3)当定义了一个对象,假设:
class A{
private String id;
private String name;
public A(){
}
public A(String name,String id){
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
这时定义两个对象:A a1 = new A("cjh","1");A a2 = new A("cjh","1");
此时打印System.out.println(a1+" , "+a2);
这时打印结果会带上hashCode的十六进制,这时如果A没有重写hashCode方法的话,hashCode根据对象的内部地址码转换成一个整数,如果重写了hashCode方法,会根据重写后的hashCode方法生成新的hashCode。
4)针对equals方法和==的思考。
首先是==操作符。
针对对象而言,是两个对象引用指向的对象的内部地址是否一致,如果一致,就是true,否则就是false,假设,有一个类A,用A定义了一个对象:A a = new A();A a1=a;其中的a和a1就是对象引用,a引用指向的地址是A对象的内部地址,此时a赋值给a1,a1也指向了新对象A的内部地址,所以两个对象引用,是相等的。如果是这种情况:A a = n ew A();A b = new A();实质是创建了两个A对象,开辟了两块内存,具有两个地址(假设d1,d2),a引用指向d1,b引用指向d2,所以a==b的执行结果是false。
针对基本数据类型而言,比较的就是变量代表的值是否相等,假设 int i = 1,j=1;此时i==j的执行结果是true,针对基本数据类型没有引用的概念,假设int i=1;int j=i; 此时只是将i的值copy一份给j,所以j的值改变了,并不会影响i的值。
再解:实质上==,比较的是变量(包括引用变量)中所存储的值是否一致,一致为true,否则为false;对于基本数据类型好理解,因为基本数据类型的赋值操作是copy一份 值赋给另一个变量;针对对象而言,如果一个变量指向的数据是对象类型的,那么,这时候涉及了两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存,例如Objet obj = new Object();变量obj是一个内存,new Object()是另一个内存,此时,变量obj所对应的内存中存储的数值就是对象占用的那块内存的首地址。对于指向对象类型的变量,如果要比较两个变量是否指向同一个对象,即要看这两个变量所对应的内存中的数值是否相等,这时候就需要用==操作符进行比较。
其次是equals方法。
equals方法主要还是从代码来理解,首先来看Object类,这是所有对象的根类,以下是Object类中的equals方法的源码:
public boolean equals(Object obj)
{
return this == obj;
}
从中可以看见Object类中equals方法的本质是==,所以要比较对象的内容的时候,经常需要重写equals方法(如果不想重写equals方法的话,可以引用apache的jar包-commons-lang-2.4.jar,该包的中的EqualsBulider类可供使用,例如:EqualsBuilder.reflectionEquals(a1, a2))。再者根据hashCode的常规协定,就必须再重写hashCode方法。
对String类而言,由于String中针对了equals方法已经进行了重写,所以默认比较的就是String的内容,还有一些基本数据类型的包装类Integer等类,也是对equals方法重 写了,默认比较的就是其中的值。但针对这几种类型,要注意equals和==的区别,假设如下代码:
Integer i = new Integer(1);Integer j = new Integer(1);
i==j的执行结果是false,i.equals(j)的执行结果是true;
改变下以上代码:Integer i = 1;Integer j = 1;
i==j的执行结果是true,i.equals(j)的执行结果是true;
为什么呢?(如果是String类型也是一样的)
情况一:通过new创建了2个对象,这时==操作符,肯定结果就是false,而equals比较的是内容,都是1,就为true;
情况二:1是一个常量,在整个缓冲区中只有一份,所以,i和j实际指向的都是同一个1,所以==操作的结果就是true。