Google test是一种比较方便的C++测试框架, 它能够帮助我们比较方便的进行测试代码的编写, 以及输出尽可能详细的失败信息。能够大大缩短我们测试代码的编写效率, 而且该框架的使用方法也比较简单, 能够降低我们学习新框架的负担。
$ git clone https://github.com/google/googletest.git
$ cd googletest
$ mkdir mybuild
$ cd mybuild
$ cmake -Dgtest_build_tests=on -DCMAKE_INSTALL_PREFIX=. ..
$ make; make install
经过编译之后, 在mybuild目录下面, 产生了lib 和include目录, 有我们需要的头文件:include/gtest/gtest.h 以及静态库: lib/libgtest.a。
-rw-r--r-- 1 root root 18974 Jul 17 10:44 CMakeCache.txt
drwxr-xr-x 4 root root 4096 Jul 17 10:46 CMakeFiles
-rw-r--r-- 1 root root 329 Jul 17 10:44 CTestTestfile.cmake
-rw-r--r-- 1 root root 35112 Jul 17 10:44 Makefile
drwxr-xr-x 2 root root 4096 Jul 17 10:44 bin
-rw-r--r-- 1 root root 1774 Jul 17 10:44 cmake_install.cmake
drwxr-xr-x 3 root root 4096 Jul 17 10:44 googlemock
drwxr-xr-x 4 root root 4096 Jul 17 10:46 googletest
drwxr-xr-x 2 root root 4096 Jul 17 10:46 lib
gtest的官方网站是:
http://code.google.com/p/googletest/
从官方的使用文档里,你几乎可以获得你想要的所有东西:
http://code.google.com/p/googletest/wiki/GoogleTestPrimer
http://code.google.com/p/googletest/wiki/GoogleTestAdvancedGuide
下面是该系列的目录:
额外篇:
1.gtest中如何跳出当前测试案例
2.编写优美的GTest测试案例
3.gtest 参数化测试代码示例 (内含完整工程示例)
参考: https://www.cnblogs.com/coderzh/archive/2009/04/06/1426755.html
一直都只是简单的使用C++11
的auto关键字
,没有去系统的学习auto关键字
的正确使用方式,
目前希望通过深入应用C++11:代码优化与工程级应用
这本书系统的学些一下auto
相关的用法。
auto变量能自动根据初始化的值推导出变量类型; 不同与Python等动态类型语言的运行时变量类型推导,隐式类型定义的类型推导发生在编译期。 它的作用是让编译器自动推断出这个变量的类型,而不需要显示指定类型。 auto基本用法:
auto x = 5; // OK: x是int类型
auto pi = new auto(1); // OK: pi被推导为int*
const auto *v = &x, u = 6; // OK: v是const int*类型,u是const int类型
static auto y = 0.0; // OK: y是double类型
auto int r; // error: auto不再表示存储类型指示符
auto s; // error: auto无法推导出s的类型
在上面的代码示例中:字面量5 是一个const int 类型,变量x 将被推导为int 类型(const 被丢弃,后面说明),并被初始化为5 ;pi 的推导说明auto 还可以用于new 操作符。在例子 中,new 操作符后面的auto(1) 被推导为int(1),因此pi 的类型是int* ;接着,由&x 的类型 为int,推导出const auto 中的auto 应该是int,于是v 被推导为const int*,而u 则被推导 为const int v 和u 的推导需要注意两点:
const auto *v = &x, u = 6.0;
error: inconsistent deduction for 'const auto': 'int' and then
'double'
由上面的例子可以看出来,auto 并不能代表一个实际的类型声明(如s 的编译错误),只 是一个类型声明的“占位符”。 使用auto 声明的变量必须马上初始化,以让编译器推断出它的实际类型,并在编译时将 auto 占位符替换为真正的类型
eg:
int x = 0;
auto * a = &x; // a -> int*,auto 被推导为int
auto b = &x; // b -> int*,auto 被推导为int*
auto & c = x; // c -> int&,auto 被推导为int
auto d = c; // d -> int ,auto 被推导为int
const auto e = x; // e -> const int
auto f = e; // f -> int
const auto& g = x; // e -> const int&
auto& h = g; // f -> const int&
通过上面的一系列示例,可以得到下面这两条规则: 1)当不声明为指针或引用时,auto 的推导结果和初始化表达式抛弃引用和cv 限定符后 类型一致。 2)当声明为指针或引用时,auto 的推导结果将保持初始化表达式的cv 属性。 看到这里,对函数模板自动推导规则比较熟悉的读者可能会发现,auto 的推导和函数模板参数 的自动推导有相似之处。比如上面例子中的auto,和下面的模板参数自动推导出来的类型是一致的:
template <typename T> void func(T x) {} // T -> auto
template <typename T> void func(T * x) {} // T * -> auto *
template <typename T> void func(T & x) {} // T & -> auto &
template <typename T> void func(const T x) {} // const T ->
const auto
template <typename T> void func(const T * x) {} // const T * ->
const auto *
template <typename T> void func(const T & x) {} // const T & ->
const auto &
注意:auto 是不能用于函数参数的。上面的示例代码只是单纯比较函数模板参数推导和 auto 推导规则的相似处。 因此,在熟悉auto 推导规则时,可以借助函数模板的参数自动推导规则来帮助和加强理解。
eg:
void func(auto a = 1) {} // error: auto 不能用于函数参数
struct Foo
{
auto var1_ = 0; // error: auto 不能用于非静态成员变量
static const auto var2_ = 0; // OK: var2_ -> static const int
};
template <typename T>
struct Bar {};
int main(void)
{
int arr[10] = {0};
auto aa = arr; // OK: aa -> int *
auto rr[10] = arr; // error: auto 无法定义数组
Bar<int> bar;
Bar<auto> bb = bar; // error: auto 无法推导出模板参数
return 0;
}
在Foo 中,auto 仅能用于推导static const 的整型或者枚举成员(因为其他静态类型在
C++ 标准中无法就地初始化),虽然C++11 中可以接受非静态成员变量的就地初始化,但却
不支持auto 类型非静态成员变量的初始化。
在main 函数中,auto 定义的数组rr 和Bar
注意:main 函数中的aa 不会被推导为int[10],而是被推导为int*。这个结果可以通过上一 节中auto 与函数模板参数自动推导的对比来理解。
在C++11 之前,定义了一个stl 容器以后,在遍历的时候常常会写这样的代码:
#include <map>
int main(void)
{
std::map<double, double> resultMap;
// ...
std::map<double,double>::iterator it = resultMap.begin();
for(; it != resultMap.end(); ++it)
{
// do something
}
return 0;
}
观察上面的迭代器(iterator)变量it 的定义过程,总感觉有点憋屈。其实通过resultMap. begin(),已经能够知道it 的具体类型了,却非要书写上长长的类型定义才能通过编译。 来看看使用了auto 以后的写法:
#include <map>
int main(void)
{
std::map<double, double> resultMap;
// ...
for(auto it = resultMap.begin(); it != resultMap.end(); ++it)
{
// do something
}
return 0;
}
再次观察it 的定义过程,是不是感到清爽了很多? 再看一个例子,在一个unordered_multimap 中查找一个范围,代码如下:
#include <map>
int main(void)
{
std::unordered_multimap<int, int>resultMap;
// ...
std::pair<std::unordered_multimap<int,int>::iterator,
std::unordered_multimap<int, int>::iterator>
range = resultMap.equal_range(key);
return 0;
}
这个equal_range 返回的类型声明显得烦琐而冗长,而且实际上并不关心这里的具体类 型(大概知道是一个std::pair 就够了)。这时,通过auto 就能极大的简化书写,省去推导具体 类型的过程:
#include <map>
int main(void)
{
std::unordered_multimap<int, int> map;
// ...
auto range = map.equal_range(key);
return 0;
}
另外,在很多情况下我们是无法知道变量应该被定义成什么类型的
class Foo
{
public:
static int get(void)
{
return 0;
}
};
class Bar
{
public:
static const char* get(void)
{
return "0";
}
};
template <class A>
void func(void)
{
auto val = A::get();
// ...
}
int main(void)
{
func<Foo>();
func<Bar>();
return 0;
}
在这个例子里,我们希望定义一个泛型函数func,对所有具有静态get 方法的类型A, 在得到get 的结果后做统一的后续处理。若不使用auto,就不得不对func 再增加一个模板参 数,并在外部调用时手动指定get 的返回值类型。
注意:auto 是一个很强大的工具,但任何工具都有它的两面性。不加选择地随意使用auto, 会带来代码可读性和维护性的严重下降。因此,在使用auto 的时候,一定要权衡好 它带来的“价值”和相应的“损失”。
int a = 5
,那么也就是给int
型变量a
赋值,值为5
.如果一个方法,将这个变量传进方法的内部,则进行的就是传值。在Java
中,有8
种基本数据类型,它们分别为:int、long、float、double、char、boolean、short、byte
.这8
种基本的数据类型作为参数进行传递是,都是进行的传值。·除此之外,还有一种特殊的情况,String
。本身String
是一个引用类型,很多人认为在向方法中传递String
类型参数时,是进行传引用。其实在这里,String
可以看做为一个包装类,因为String
其本身就是通过字符数组实现的,那么它在实现参数传递的时候,也就是以char
型数据的方式进行的,也就是进行传值。java
中的引用可以初步的理解为地址。也是在new
了一个对象后,该对象是存在JVM
的Heap
区,也就是堆。那么必然有一个地址要指向这个对象的在JVM
中的位置,那么,指向这个对象的这个地址就可以简单的理解为“引用”。Java
确实使用对象的引用来做计算的,所有的对象变量都是引用。但是,Java
在向方法传递参数时传的不是引用,是值。
以 swap()
函数为例:
public void swap(int var1, int var2)
{
int temp = var1;
var1 = var2;
var2 = temp;
}
当swap
方法返回时,被当作参数传入的变量仍然保持了原来的值不变。如果我们把传入的int
型变量改为Object
型也是一样的,因为Java
通过传值来传递引用的。
Java
复制并传递了“引用”的值,而不是对象。因此,方法中对对象的计算是会起作用的,因为引用指向了原来的对象。但是因为方法中对象的引用是“副本”,所以对象交换就没起作用。交换动作只对方法中的引用副本起作用了,不影响方法外的引用。所以不好意思,方法被调用后,改变不了方法外的对象的引用。如果要对方法外的对象引用做交换,我们应该交换原始的引用,而不是它的副本。
静态变量是和类绑定到一起的,而不是类的实例对象。每一个实例对象都共享同样一份静态变量。也就是说,一个类的静态变量只有一份,不管它有多少个对象。类变量或者说静态变量是通过static
这个关键字来声明的。类变量通常被用作常量。静态变量通常通过类名字来进行访问。当程序运行的时候这个变量就会创建直到程序结束后才会被销毁。类变量的作用域和实例变量是一样的。它的初始值和成员变量也是一样的,当变量没被初始化的时候根据它的数据类型,会有一个默认值。类似的,静态方法是属于类的方法,而不是类对象,它的调用并不作用于类对象,也不需要创建任何的类实例。静态方法本身就是final
的,因为重写只会发生在类实例上,静态方法是和类绑定在一起的,不是对象。父类的静态方法会被子类的静态方法屏蔽,只要原来方法没有声明为final
。非静态方法不能重写静态方法,也就是说,你不能在子类中把一个静态方法改成实例方法。
equals()
与hashCode()
方法浅析java.lang.Object
类中有两个非常重要的方法:
public boolean equals(Object obj)
public int hashCode()
Object
类是类继承结构的基础,所以是每一个类的父类。所有的对象,包括数组,都实现了在Object
类中定义的方法。
equals()
方法详解equals()
方法是用来判断其他的对象是否和该对象相等.
equals()
方法在object
类中定义如下:
public boolean equals(Object obj) {
return (this == obj);
}
很明显是对两个对象的地址值进行的比较(即比较引用是否相同)。但是我们知道,String 、Math、Integer、Double
等这些封装类在使用equals()
方法时,已经覆盖了object
类的equals()
方法。
比如在String
类中如下:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n– != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
很明显,这是进行的内容比较,而已经不再是地址的比较。依次类推Math、Integer、Double
等这些类都是重写了equals()
方法的,从而进行的是内容的比较。当然,基本类型是进行值的比较。
它的性质有:
自反性(reflexive
)。对于任意不为null
的引用值x
,x.equals(x)
一定是true
。
对称性(symmetric
)。对于任意不为null
的引用值x
和y
,当且仅当x.equals(y)
是true
时,y.equals(x)
也是true
。
传递性(transitive
)。对于任意不为null
的引用值x
、y
和z
,如果x.equals(y)
是true
,同时y.equals(z)
是true
,那么x.equals(z)
一定是true
。
一致性(consistent
)。对于任意不为null
的引用值x
和y
,如果用于equals
比较的对象信息没有被修改的话,多次调用时x.equals(y)
要么一致地返回true
要么一致地返回false
。
对于任意不为null
的引用值x,x.equals(null)
返回false
。
对于Object
类来说,equals()
方法在对象上实现的是差别可能性最大的等价关系,即,对于任意非null
的引用值x
和y
,当且仅当x
和y
引用的是同一个对象,该方法才会返回true
。
需要注意的是当equals()
方法被override
时,hashCode()
也要被override
。按照一般hashCode()
方法的实现来说,相等的对象,它们的hash code
一定相等。
hashcode()
方法详解hashCode()
方法给对象返回一个hash code
值。这个方法被用于hash tables
,例如HashMap
。
它的性质是:
在一个Java
应用的执行期间,如果一个对象提供给equals
做比较的信息没有被修改的话,该对象多次调用hashCode()
方法,该方法必须始终如一返回同一个integer
。
如果两个对象根据equals(Object)
方法是相等的,那么调用二者各自的hashCode()
方法必须产生同一个integer
结果。
并不要求根据equals(java.lang.Object)
方法不相等的两个对象,调用二者各自的hashCode()
方法必须产生不同的integer
结果。然而,程序员应该意识到对于不同的对象产生不同的integer
结果,有可能会提高hash table
的性能。
大量的实践表明,由Object
类定义的hashCode()
方法对于不同的对象返回不同的integer
。
在object
类中,hashCode
定义如下:
public native int hashCode();
说明是一个本地方法,它的实现是根据本地机器相关的。当然我们可以在自己写的类中覆盖hashcode()
方法,比如String、Integer、Double
等这些类都是覆盖了hashcode()
方法的。例如在String
类中定义的hashcode()
方法如下:
public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31 * h + val[off++];
}
hash = h;
}
return h;
}
解释一下这个程序(String
的API
中写到):s[0]* 31^(n-1) + s[1] * 31^(n-2) + … + s[n-1]
使用 int
算法,这里 s[i]
是字符串的第 i
个字符,n
是字符串的长度,^
表示求幂(空字符串的哈希码为 0
)。
想要弄明白hashCode
的作用,必须要先知道Java
中的集合。
总的来说,Java
中的集合(Collection
)有两类,一类是List
,再有一类是Set
。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。这里就引出一个问题:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?
这就是Object.equals
方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000
个元素,那么第1001
个元素加入集合时,它就要调用1000
次equals
方法。这显然会大大降低效率。
于是,Java
采用了哈希表的原理。哈希(Hash
)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上,初学者可以简单理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。
这样一来,当集合要添加新的元素时,先调用这个元素的hashCode
方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals
方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals
方法的次数就大大降低了,几乎只需要一两次。
简而言之,在集合查找时,hashcode
能大大降低对象比较次数,提高查找效率!
Java
对象的eqauls
方法和hashCode
方法是这样规定的:
1、相等(相同)的对象必须具有相等的哈希码(或者散列码)。
2、如果两个对象的hashCode相同,它们并不一定相同。
以下是Object
对象API
关于equal
方法和hashCode
方法的说明:
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
以上API
说明是对之前2
点的官方详细说明
关于第一点,相等(相同)的对象必须具有相等的哈希码(或者散列码),为什么?
想象一下,假如两个Java
对象A
和B
,A
和B
相等(eqauls
结果为true
),但A
和B
的哈希码不同,则A
和B
存入HashMap
时的哈希码计算得到的HashMap
内部数组位置索引可能不同,那么A
和B
很有可能允许同时存入HashMap
,显然相等/
相同的元素是不允许同时存入HashMap
,HashMap
不允许存放重复元素。
关于第二点,两个对象的hashCode
相同,它们并不一定相同
也就是说,不同对象的hashCode
可能相同;假如两个Java
对象A
和B
,A
和B
不相等(eqauls
结果为false
),但A
和B
的哈希码相等,将A
和B
都存入HashMap
时会发生哈希冲突,也就是A
和B
存放在HashMap
内部数组的位置索引相同这时HashMap
会在该位置建立一个链接表,将A
和B
串起来放在该位置,显然,该情况不违反HashMap
的使用原则,是允许的。当然,哈希冲突越少越好,尽量采用好的哈希算法以避免哈希冲突。
所以,Java
对于eqauls
方法和hashCode
方法是这样规定的:
hashCode
值一定要相同;hashCode
相同,它们并不一定相同(这里说的对象相同指的是用eqauls
方法比较)。如不按要求去做了,会发现相同的对象可以出现在Set
集合中,同时,增加新元素的效率会大大下降。equals()
相等的两个对象,hashcode()
一定相等;equals()
不相等的两个对象,却并不能证明他们的hashcode()
不相等。换句话说,equals()
方法不相等的两个对象,hashcode()
有可能相等(我的理解是由于哈希码在生成的时候产生冲突造成的)。反过来,hashcode()
不等,一定能推出equals()
也不等;hashcode()
相等,equals()
可能相等,也可能不等。
在object
类中,hashcode()
方法是本地方法,返回的是对象的地址值,而object
类中的equals()
方法比较的也是两个对象的地址值,如果equals()
相等,说明两个对象地址值也相等,当然hashcode()
也就相等了;在String
类中,equals()
返回的是两个对象内容的比较,当两个对象内容相等时,Hashcode()
方法根据String
类的重写代码的分析,也可知道hashcode()
返回结果也会相等。以此类推,可以知道Integer、Double
等封装类中经过重写的equals()
和hashcode()
方法也同样适合于这个原则。当然没有经过重写的类,在继承了object
类的equals()
和hashcode()
方法后,也会遵守这个原则。
Hashset、Hashmap、Hashtable与hashcode()
和equals()
的密切关系Hashset
是继承Set
接口,Set
接口又实现Collection
接口,这是层次关系。那么Hashset、Hashmap、Hashtable
中的存储操作是根据什么原理来存取对象的呢?
下面以HashSet
为例进行分析,我们都知道:在hashset
中不允许出现重复对象,元素的位置也是不确定的。在hashset
中又是怎样判定元素是否重复的呢?在java
的集合中,判断两个对象是否相等的规则是:
1.判断两个对象的hashCode
是否相等
如果不相等,认为两个对象也不相等,完毕
如果相等,转入2
(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大 大降低,所以我们这里将其做为必需的。)
2.判断两个对象用equals
运算是否相等
如果不相等,认为两个对象也不相等
如果相等,认为两个对象相等(equals()
是判断两个对象是否相等的关键)
为什么是两条准则,难道用第一条不行吗?不行,因为前面已经说了,hashcode()
相等时,equals()
方法也可能不等,所以必须用第2
条准则进行限制,才能保证加入的为非重复元素。
例1:
package com.bijian.study;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class HashSetTest {
public static void main(String args[]) {
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
Set hashset = new HashSet();
hashset.add(s1);
hashset.add(s2);
Iterator it = hashset.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
运行结果:
false
true
96321
96321
aaa
这是因为String
类已经重写了equals()
方法和hashcode()
方法,所以hashset
认为它们是相等的对象,进行了重复添加。
例2:
package com.bijian.study;
import java.util.HashSet;
import java.util.Iterator;
public class HashSetTest {
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add(new Student(1, "zhangsan"));
hs.add(new Student(2, "lisi"));
hs.add(new Student(3, "wangwu"));
hs.add(new Student(1, "zhangsan"));
Iterator it = hs.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
class Student {
int num;
String name;
Student(int num, String name) {
this.num = num;
this.name = name;
}
public String toString() {
return num + ":" + name;
}
}
运行结果:
zhangsan
wangwu
lisi
zhangsan
为什么hashset
添加了相等的元素呢,这是不是和hashset
的原则违背了呢?回答是:没有。因为在根据hashcode()
对两次建立的new Student(1,“zhangsan”)
对象进行比较时,生成的是不同的哈希码值,所以hashset
把他当作不同的对象对待了,当然此时的equals()
方法返回的值也不等。
为什么会生成不同的哈希码值呢?上面我们在比较s1
和s2
的时候不是生成了同样的哈希码吗?原因就在于我们自己写的Student
类并没有重新自己的hashcode()
和equals()
方法,所以在比较时,是继承的object
类中的hashcode()
方法,而object
类中的hashcode()
方法是一个本地方法,比较的是对象的地址(引用地址),使用new
方法创建对象,两次生成的当然是不同的对象了,造成的结果就是两个对象的hashcode()
返回的值不一样,所以Hashset
会把它们当作不同的对象对待。
怎么解决这个问题呢?答案是:在Student
类中重新hashcode()
和equals()
方法。
class Student {
int num;
String name;
Student(int num, String name) {
this.num = num;
this.name = name;
}
public int hashCode() {
return num * name.hashCode();
}
public boolean equals(Object o) {
Student s = (Student) o;
return num == s.num && name.equals(s.name);
}
public String toString() {
return num + ":" + name;
}
}
运行结果:
1:zhangsan
3:wangwu
2:lisi
可以看到重复元素的问题已经消除,根据重写的方法,即便两次调用了new Student(1,"zhangsan")
,我们在获得对象的哈希码时,根据重写的方法hashcode()
,获得的哈希码肯定是一样的,当然根据equals()
方法我们也可判断是相同的,所以在向hashset
集合中添加时把它们当作重复元素看待了。
重写equals()
和hashcode()
小结:
equals
,重写hashCode
只是技术要求(为了提高效率)equals
呢?因为在java
的集合框架中,是通过equals
来判断两个对象是否相等的hibernate
中,经常使用set
集合来保存相关对象,而set
集合是不允许重复的。在向HashSet
集合中添加元素时,其实只要重写equals()
这一条也可以。但当hashset
中元素比较多时,或者是重写的equals()
方法比较复杂时,我们只用equals()
方法进行比较判断,效率也会非常低,所以引入了hashCode()
这个方法,只是为了提高效率,且这是非常有必要的。比如可以这样写:
public int hashCode(){
return 1; //等价于hashcode无效
}
这样做的效果就是在比较哈希码的时候不能进行判断,因为每个对象返回的哈希码都是1
,每次都必须要经过比较equals()
方法后才能进行判断是否重复,这当然会引起效率的大大降低。