《Effective Java 3rd》读书笔记——对于所有对象都通用的方法
覆盖equals方法时遵守通用约定
覆盖equals()
方法看起来似乎简单,但是许多覆盖方式会导致错误,并且后果非常严重。最能避免这些问题的办法就是不覆盖equals()
方法,在这种情况下,类的每个实例都只与它自己相等。如果满足了以下任意一个条件,那可以不覆盖equals()
方法:
- 类的每个实例本质上都是唯一的
- 类没有必要提供逻辑相等的功能
- 超类已经覆盖了
equals()
,超类的行为对于子类也是合适的。
例如,大多数的Set
实现都从AbstractSet
继承equals()
实现 - 类是私有的,或包级私有的,可以确定它的
equals()
方法永远不会被调用
那么,什么时候应该覆盖equals()
方法呢?如果类具有自己特有的逻辑相等概念,而且超类还没有覆盖equals()
方法。这通常属于值类的情形。值类仅仅是一个表示值的类,例如Integer
或String
。
程序员在利用equals()
方法来比较值对象的引用时,希望知道它们在逻辑上是否相等,而不是想了解它们是否指向同一个对象。 此时,不仅必须覆盖equals()
方法,而且这样做也使得这个类的实例可以被用作Map
的Key
,或集合的元素,使Map
或集合表现出预期的行为。
在覆盖equals()
方法的时候,必须要遵循它的通用约定。下面是约定的内容,来自Object
的规范
equals
方法实现了等价关系,其属性如下:
- 自反性:对于任何为
null
的引用值x
,x.equals(x)
必须返回true
- 对称性:对于任何非
null
的引用值x
和y
,当且仅当y.equals(x)
返回true
时,x.equals(y)
必须返回true
- 传递性:对于任何非
null
的引用值x
、y
和z
,如果x.equals(y)
返回true
,y.equals(z)
也返回true
,那么x.equals(z)
也必须返回true
- 一致性:对于任何非
null
的引用值x
和y
,只要equals
的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)
就会一致地返回true
,或者一致地返回false
- 对于任何非
null
的引用值x
,x.equals(null)
必须返回false
有许多类,包括所有的集合类,都依赖于传递给它们的对象是否遵守了equals
约定。
我们按照顺序逐一查看以下5个要求:
- 自反性——第一个要求仅仅说明对象必须等于其自身。这是很容易理解的
- 对称性——第二个要求是说,任何两个对象对于"它们是否相等"的问题都必须保持一致。这是很容易无意中违反的。例如下面的例子实现一个区分大小写的字符串。字符串由
toString
保存,但在equals
操作中被忽略:
public class CaseInsensitiveString {private final String s;public CaseInsensitiveString(String s) {this.s = Objects.requireNonNull(s);}/*** 违反对称性* @param that* @return*/@Overridepublic boolean equals(Object that) {if (that instanceof CaseInsensitiveString) {return s.equalsIgnoreCase(((CaseInsensitiveString) that).s);}/*** 单方面的想与String比较*/if (that instanceof String) {return s.equalsIgnoreCase((String) that);}return false;}
}
在这个类中,equals
方法企图与普通的String
对象进行互操作。假设我们有一个CaseInsensitiveString
和一个普通的String
:
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
不出所料,cis.equals(s)
返回true
,但是String
不知道CaseInsensitiveString
,因此s.equals(cis)
返回false
,显然违反了对称性。
一旦违反了
equals
约定,当其他对象面对你的对象时,你完成不知道这些对象的行为会怎样
为了修复上面这个类的问题,把企图与String
对象互操作的代码删掉即可。
- 传递性——第三个要求是,如果一个对象等于第二个对象,而第二个对象又等于第三个对象,则第一个对象一定等于第二个对象。同样的,无意中违反这条规则也不难想象。用子类举个例子。假设它将一个新的值组件添加到了超类中。换句话说,子类增加的信息会影响
equals
的比较结果。我们首先以一个简单的不可变的二维整数型Point
类 作为开始:
public class Point {private final int x;private final int y;public Point(int x,int y) {this.x = x;this.y = y;}@Overridepublic boolean equals(Object that) {if (!(that instanceof Point)) {return false;}Point p = (Point) that;return p.x == x && p.y == y;}
}
假设你想要扩展这个类,为Point
添加颜色信息:
public class ColorPoint extends Point {private final Color color;public ColorPoint(int x, int y,Color color) {super(x, y);this.color = color;}
}
如果完全不提供equals
方法,而是直接从Point
继承过来,在equals
做比较的时候颜色信息就被忽略掉了。虽然这不会违反equals
约定,但明显这是无法接受的。
假设编写了一个equals
方法,只有当它的参数是另一个ColorPoint
,并且具有同样的位置和颜色时,它才会返回true
:
@Override
public boolean equals(Object that) {if (!(that instanceof ColorPoint)) {return false;}return super.equals(that) && ((ColorPoint) that).color == color;
}
该方法的问题在于,在比较Point
和ColorPoint
,以及相反的情形时,可能会得到不同的结果。前一种比较忽略了颜色信息,而后一种比较总是返回false
,因为参数的类型不正确。
public static void main(String[] args) {Point p = new Point(1,2);ColorPoint cp = new ColorPoint(1,2,Color.RED);System.out.println(p.equals(cp));//trueSystem.out.println(cp.equals(p));//false
}
可以通过这样的尝试来修复这个问题,让ColorPoint.equals
在进行混合比较时忽略颜色信息:
//ColorPoint的方法
@Override
public boolean equals(Object that) {if (!(that instanceof Point)) {return false;}//如果that是Point,则进行忽略Color的比较if (!(that instanceof ColorPoint)) {return that.equals(this);}//that是ColorPointreturn super.equals(that) && ((ColorPoint) that).color == color;
}
这种方法虽然提供了对称性,但牺牲了传递性:
ColorPoint p1 = new ColorPoint(1,2,Color.RED);
Point p2 = new Point(1,2);
ColorPoint p3 = new ColorPoint(1,2,Color.BLUE);
System.out.println(p1.equals(p2));//true
System.out.println(p2.equals(p3));//true
System.out.println(p1.equals(p3));//false
从上面输出可以看出,违反了传递性。此外,这种方法还会导致无限递归问题:假设Point
有两个子类,如ColorPoint
和SmellPoint
,它们各自都带有这种equals
方法。那么对colorPoint.equals(smellPoint)
的调用将会抛出StackOverflowError
异常。
那么该如何解决呢?
其实这是面向对象语言中关于等价关系的一个基本问题。我们无法在扩展可实例化的类的同时,既增加新的值组件,同时又保留equals
约定,除非愿意放弃面向对象的抽象所带来的优势。
你可能听过在equals
方法中用getClass
测试代替instanceof
测试,可以扩展可实例化的类的同时,又保留equals
约定:
@Override
public boolean equals(Object that) {if(that == null || that.getClass() != getClass()) {return false;}Point p = (Point)that;return p.x == x && p.y == y;
}
这段程序只有当对象具有相同的实现类是,才能使对象相等。虽然这样也不算太糟糕,但结果却是无法接受的:Point
子类的实例仍然是一个Point
,它仍然需要发挥作用,但如果采用了这种方法,它就无法完成任务。
假定我们要编写一个方法,以检验某个点是否处在圆中:
private static final Set<Point> unitCircle = Set.of(new Point(1,0),new Point(0,1),new Point(-1,0),new Point(0,-1));
public static boolean onUnitCircle(Point p) {return unitCircle.contains(p);
}
假设你通过某种不添加值组件的方法扩展了Point
,并创建了多个实例:
public class CounterPoint extends Point {private static final AtomicInteger counter = new AtomicInteger();public CounterPoint(int x,int y) {super(x,y);counter.incrementAndGet();}public static int numberCreated() {return counter.get();}
}
里氏替换原则认为,一个类的任何重要属性也将使用于它的子类。因此为该类编写的任何方法,在它的子类上也应该同样运行得很好。
假设我们将CounterPoint
实例传递给onUnitCircle
方法。如果Point
使用了基于getClass
的equals
方法,无论CounterPoint
实例的x
和y
值是什么,该方法都会返回false
。
虽然没有一种令人满意的方法既可以扩展不可实例化的类,又增加值组件,但还是有一种不错的权宜之计:遵从复合优于继承的建议。不再让ColorPoint
继承Point
,而是添加一个私有的Point
属性,以及一个公有的视图方法,该方法返回一个与该ColorPoint
在相同位置的普通Point
对象:
public class ColorPoint {private final Point point;private final Color color;public ColorPoint(int x, int y,Color color) {point = new Point(x,y);this.color = color;}public Point asPoint() {return point;}@Overridepublic boolean equals(Object that) {if (!(that instanceof ColorPoint)) {return false;}ColorPoint cp = (ColorPoint) that;return cp.point.equals(point) && cp.color.equals(color);}
}
一致性——
equals
约定的第四个要求是,如果两个对象相等,它们就始终保持相等,除非它们中有一个对象被修改了。
如果你设计的某个类是不可变的:那么应该满足,相等的对象永远相等,不相等的对象永远不相等。无论类是否是不可变的,都不要使equals
方法依赖于不可靠(可变)的资源。非空性——所有的对象都不能等于
null
许多类的equals
方法都通过一个显式的null
测试来防止这种情况:
@Override
public boolean equals(Object that) {if(that == null) {return false;}...
}
这项测试其实是不必要的。为了测试其参数的等同性,equals
方法必须先把参数转换成适当的类型,以便可以调用它的访问方法,或者访问它的属性。在进行转换之前,equals
方法必须使用instanceof
操作符,以检查其参数的类型是否正确:
@Override
public boolean equals(Object that) {if(!(that instanceof MyType) {return false;}MyType mt = (MyType) that;...
}
如果instanceof
的第一个操作数为null
,那么不管第二个操作数是什么类型,该操作符都会返回false
。因此不需要显示地null
检查。
结合所有这些要求,得出了一下实现高质量equals
方法的诀窍:
- 使用
==
操作符检查"参数是否为这个对象的引用",如果是则返回true
。这只是一种性能优化,如果比较操作可能很昂贵,就值得这么做。 - 使用
instanceof
操作符检测"参数是否为正确的类型"。如果不是则返回false
。 - 把参数转换成正确的类型。因为转换之前进行过
instanceof
测试,所以确保会成功。 - 对于该类中的每个关键属性,检查参数中的属性是否与该对象中对应的属性相匹配。如果这些测试全部成功,则返回
true
,否则返回false
。
对于既不是float
也不是double
的原生类型属性,可以使用==
操作符进行比较;对于对象引用属性,可以递归地调用equals
方法;对于float
属性,可以使用静态的Float.compare(float,float)
方法;
对于double
属性,则使用Double.compare(double,double)
;对于数组属性,则要把以上原则应用到每一个元素上。如果数组属性中的每个元素都很重要,就可以使用其中一个Arrays.equals
方法。
有些对象引用属性包含null
可能是合法的,所以,为了避免可能导致空指针异常,则使用静态方法Objects.equals(Object,Object)
来检测。
在编写equals
方法之前,应该问自己三个问题:它是否是对称的、传递的、一致的?
根据上面的诀窍构造equals
方法的具体例子,看下面这个类:
public class PhoneNumber {private final short areaCode,prefix,lineNum;public PhoneNumber(int areaCode,int prefix,int lineNum) {this.areaCode = rangeCheck(areaCode,999,"area code");this.prefix = rangeCheck(prefix,999,"prefix");this.lineNum = rangeCheck(areaCode,9999,"line num");}private static short rangeCheck(int val,int max,String arg) {if (val < 0 || val > max) {throw new IllegalArgumentException(arg + ": " + val);}return (short) val;}@Overridepublic boolean equals(Object that) {if (that == this) {return true;}if (!(that instanceof PhoneNumber)) {return false;}PhoneNumber pn = (PhoneNumber) that;return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode;}
}
下面是最后一些告诫:
- 覆盖
equals
时总要覆盖hashCode
- 不要企图让
equals
方法过于智能 - 不要将
equals
声明中的Object
对象替换成其他类型
覆盖equals时总要覆盖hashcode
如果不这样做的话,就会违反hashCode
的通用约定:
- 只要对象的
equals
方法比较所用的信息没有被修改,那么多次调用同一个对象的hashCode
方法都必须始终返回同一个值 - 如果两个对象根据
equals
方法比较是相等的,那么它们的hashCode
方法都必须产生相同的整数值 - 如果两个对象根据
equals
比较是不等的,那么它们的hashCode
不一定产生不同的结果
因为没有覆盖hashCode
会违反第二条:相等的对象必须具有相等的hashCode。
一个好的散列函数通常倾向于为不等的对象产生不等的散列码(hashCode)。下面给出一种简单的解决方法:
- 声明一个
int
变量并命名为result
,将它初始化为对象中第一个关键域的散列码c
,如步骤2.a中计算所示 - 对象中剩下的每一个关键域
f
都完成以下步骤:
a. 为该域计算int
类型的散列码c
* 如果该域是基本类型,则计算Type.hashCode(f)
,Type
是装箱类型
* 如果该域是一个对象引用,且该类的equals
方法通过递归的调用equals
的方式来比较这个域,则同样的为该域递归地调用hashCode
。如果该域的值为null
,则返回0
* 如果该域是一个数组,则要把数组中的每个元素当做单独的域来处理。如果数组中域中的所有元素都很重要,可以使用Arrays.hashCode
方法
b. 安装下面的公式,把步骤2.a中计算得到的散列码c
合并到result
中:result = 31 * result + c
- 返回
result
现在把上述解决办法用到PhoneNumber
类中:
@Override
public int hashCode() {int result = Short.hashCode(areaCode);result = 31 * result + Short.hashCode(prefix);result = 31 * result + Short.hashCode(lineNum);return result;
}
Objects
类有一个静态方法hash
,它带有任意数量的对象,并为它们返回一个散列码。与上面编写出来的对比,质量是相当的。但是运行速度更慢一些,因为它们会引发数组的创建,以便传入数量可变的参数,还可能会装箱和拆箱。建议该静态方法用于不太注重性能的情况:
@Override
public int hashCode() {return Objects.hash(lineNum,prefix,areaCode);
}
如果一个类是不可变的,并且计算散列码的开销较大。建议将散列码缓存在对象内部,如String
。
不要试图从散列码计算中排除一个对象的关键域来提高性能。
始终要覆盖toString
提供好的toString
实现可以使类用起来更加舒适,使用了这个类的系统也更易于测试。
在实际应用中,toString
方法应该返回对象中包含所有值得关注的信息,如果读写中包含的状态信息难以用字符串来表达,此时应该返回一个摘要信息。
下面是PhoneNumber
类的toString
方法:
@Override
public String toString() {return String.format("%03d-%03d-%04d",areaCode,prefix,lineNum);
}
为toString
返回值中包含的所有信息提供一种可以通过编程访问的途径。例如上面的例子中,PhoneNumber
类应该提供对areCode
、prefix
和lineNumber
的访问方法。
谨慎地覆盖clone
Cloneable
接口的目的是作为对象的一个mixin接口,表明这样的对象允许克隆。
如果一个类实现了Cloneable
,Object
的clone
方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedException
。
虽然规范中没有明确指出,事实上,实现Cloneable
接口的类是为了提供一个功能适当的公有clone
方法。由此得到一种语言之外的机制:它无须调用构造器就可以创建对象。
clone
方法的通用约定是非常弱的,下面来自Object
规范中的约定内容:
创建和返回该对象的一个拷贝,这个拷贝的精确含义取决于该对象的类。一个版的含义是,对于任何对象x
,表达式
x.clone() != x
会返回true
,并且表达式x.clone().getClass() == x.getClass()
将会返回true
,但这些都不是绝对的要求。
虽然在通常情况下x.clone().equals(x)
将会返回true
,但是,这也不是一个绝对的要求。
按照约定,这个方法返回的对象应该通过调用super.clone
获得。如果类及其超类遵守这一约定,那么:
x.clone().getClass() == x.getClass()
假设你希望在一个类中实现Cloneable
接口,并且它的超类都提供了行为良好的clone
方法。
首先调用super.clone
方法。由此得到的对象将是原始对象功能完整的克隆。在这个类中声明的域将等同于被克隆对象中相应的域。如果每个域包含一个基本类型的值(不可变的),或包含一个指向不可变对象的引用,那么被返回的对象可能正是你所需要的对象,在这种情况下不要再做进一步处理。
例如PhoneNumber
类正是如此,但要注意,不可变的类永远都不要提供clone
方法。
public class PhoneNumber implements Cloneable{...@Overrideprotected PhoneNumber clone() {try {return (PhoneNumber) super.clone();} catch (CloneNotSupportedException e) {throw new AssertionError();//不会发生}}}
首先要实现Cloneable
接口。虽然Object
的clone
方法返回的是Object
,但这个clone
方法返回的却是PhoneNumber
。这么做是合法的,也是我们所期望的,因为Java支持协变返回类型。换句话说,目前覆盖方法的返回类型可以是被覆盖方法的返回类型的子类。对super.clone
方法的调用应当包含在一个try-catch
块中。由于PhoneNumber
实现了Cloneable
接口,我们知道调用super.clone
方法一定会成功,因此不会抛出CloneNotSupportedException
。
如果对象中包含的域引用了可变的对象,使用上述这种简单的clone
实现可能会导致灾难性的后果。例如Stack
类:
class Stack {private Object[] elements;private int size = 0;private static final int DEFAULT_INITIAL_CAPACITY = 16;public Stack() {elements = new Object[DEFAULT_INITIAL_CAPACITY];}public void push(Object e) {ensureCapacity();elements[size++] = e;}public Object pop() {if (size == 0) {throw new EmptyStackException();}Object result = elements[--size];elements[size] = null;return result;}private void ensureCapacity() {if (elements.length == size) {elements = Arrays.copyOf(elements, 2 * size + 1);}}
}
假设希望把这个类做成可克隆的。如果它的clone
方法仅仅返回super.clone()
,这样得到的Stack
实例,它的elements
域将引用与原始Stack
实例相同的数组。
如果调用Stack
类中唯一的构造器,这种情况就永远不会发生。实际上,clone
方法就是另一个构造器;必须确保它不会伤害到原始的对象,并确保正确地创建被克隆对象中的约束条件。
必须要拷贝栈的内部信息,最容易的做法是在elements
数组中递归地调用clone
:
@Override
protected Stack clone() {try {Stack result = (Stack) super.clone();result.elements = elements.clone();return result;} catch (CloneNotSupportedException e) {throw new AssertionError();}
}
递归地调用clone
有时还不够。例如,假如你正在为一个散列表编写clone
方法,它的内部数据包含一个散列桶数组,每个散列桶都指向“键-值”对链表的第一项:
public class HashTable implemnts Cloneable {private Entry[] buckets = ...;private static class Entry {final Object key;Object value;Entry next;}...
}
假设你仅仅递归地克隆这个散列桶数组:
@Override
public HashTable clone() {try {HashTable result = (HashTable) super.clone();result.buckets = buckets.clone();return result;} catch (CloneNotSupportedException e) {throw new AssertionError();}
}
虽然被克隆对象有它自己的散列桶数组,但是,这个数组引用的链表与原始对象是一样的,从而很容易引起克隆对象和原始对象中不确定的行为。
public class HashTable implemnts Cloneable {private Entry[] buckets = ...;private static class Entry {final Object key;Object value;Entry next;Entry(Object key,Object value,Entry next) {this.key = key;this.value = value;this.next = next;}Entry deepCopy() {return new Entry(key,value,next == null ? null: next.deepCopy());}}@Overridepublic HashTable clone() {try {HashTable result = (HashTable) super.clone();result.buckets = new Entry[buckets.length];for(int i = 0;i < buckets.length; i++) {if(buckets[i] != null) {result.buckets[i] = buckets[i].deepCopy();}}return result;} catch (CloneNotSupportedException e) {throw new AssertionError();}}...
}
虽然这种方法很灵活,但是这样克隆一个链表并不是一种好方法,因为针对列表中的每个元素,它都要消耗一段栈空间。如果链表较长,容易导致栈溢出,可以用迭代来代替递归:
Entry deepCopy() {Entry result = new Entry(key,value,next);for (Entry p = result; p.next != null; p = p.next) {p.next = new Entry(p.next.key,p.next.value,p.next.next);}return result;
}
像构造器一样,clone
方法也不应该在构造的过程中,调用可以覆盖的方法。
对象拷贝的更好的办法是提供一个拷贝构造器或拷贝工厂。拷贝构造器只是一构造器,它唯一的参数类型是包含该构造器的类,例如:
public Yum(Yum yum) {...
}
拷贝工厂是类似于拷贝构造器的静态工厂:
public static Yum newInstance(Yum yum) {...
}
既然所有的问题都与Cloneable
接口有关,新的接口就不应该继承这个接口,新的课扩展的类也不应该实现这个接口。总是,克隆功能最好由构造器或工厂提供。这条规则最绝对的列外是数组,最好利用clone
方法复制数组。
考虑实现Comparable接口
一旦实现了Comparable
接口,它就可以跟许多泛型算法以及依赖于该接口的集合实现进行协作。
compareTo
方法的通用约定于equals
方法的约定相似:
在下面的说明中,符号sgn
表示数学中的signum
函数,它根据表达式的值为负数、零和正值,分别返回-1,0或1。
- 实现者必须确保所有的
x
和y
都满足sgn(x.compareTo(y)) = -sgn(y.compareTo(x))
- 还必须确保这个比较关系是可传递的,
x.compareTo(y) > 0 && y.compareTo(z) > 0
意味着x.compareTo(z) > 0
- 最后,还须确保
x.compareTo(y) == 0
意味着所有的z
都满足sgn(x.compareTo(z)) == sgn(y.compareTo(z))
- 强烈建议
(x.compareTo(y) == 0 ) == (x.equals(y))
,但这并非绝对必要
如果一个类有多个关键域,那么,按什么样的顺序来比较这些域是非常关键的。必须从最关键的域开始,逐步进行到所有的重要域。如果某个域产生了非零的结果,则整个比较操作结束,并返回结果。
在Java8中,Comparable
接口配置了一组比较器构造方法,使得比较器的构造工作变得非常流畅。之后,按照Comparable
接口的要求,这些比较器可以用来实现一个compareTo
方法。下面是使用了这个方法之后的PhoneNumber
的compareTo
方法:
private static final Comparator<PhoneNumber> COMPARATOR = Comparator.comparingInt((PhoneNumber pn) -> pn.areaCode).thenComparingInt(pn -> pn.prefix).thenComparingInt(pn -> pn.lineNum);public int compareTo(PhoneNumber pn) {return COMPARATOR.compare(this,pn);
}
compareTo
或compare
方法偶尔也会依赖于两个值之间的区别,即如果第一个值小于第二个值,则为负;如果两个值相等,则为零;如果第一个值大于第二个值,则为正:
static Comparator<Object> hashCodeOrder = new Comparator<>() {public int compare(Object o1, Object o2) {return o1.hashCode() - o2.hashCode();}
}
千万不要使用这个方法,它很容易造成整数溢出。要么使用静态方法compare
:
static Comparator<Object> hashCodeOrder = new Comparator<>() {public int compare(Object o1, Object o2) {return Integer.compare(o1.hashCode(),o2.hashCode());}
}
要么使用一个比较器构造方法:
static Comparator<Object> hashCodeOrder = Comparator.comparingInt(o -> o.hashCode());
《Effective Java 3rd》读书笔记——对于所有对象都通用的方法相关推荐
- 《Effective Java》读书笔记 Item 1:考虑静态工厂方法,而不是构造器
众所周知,要想能获取一个类的实例,该类得要提供一个public的构造器.但是<Effective Java>书中说还有一个方法,那就是提供静态工厂方法(static factory met ...
- 《Effective Java》读书笔记
引言 1 代码应该被重用 ,而不是被拷贝. 2 错误应该尽早被检测出来,最好是在编译时刻. 3 接口.类.数组是引用类型(对象), 基本类型不是 第二章 创建和销毁对象 1 考虑用静态工厂方法代替构造 ...
- 《Effective Java》读书笔记,flutter游戏开发
1 代码应该被重用 ,而不是被拷贝. 2 错误应该尽早被检测出来,最好是在编译时刻. 3 接口.类.数组是引用类型(对象), 基本类型不是 第二章 创建和销毁对象 1 考虑用静态工厂方法代替构造器. ...
- Effective Java:对于全部对象都通用的方法
前言: 读这本书第1条规则的时候就感觉到这是一本非常好的书.可以把我们的Java功底提升一个档次,我还是比較推荐的.这里我主要就关于覆盖equals.hashCode和toString方法来做一个笔记 ...
- Effective Java:对于所有对象都通用的方法
前言: 读这本书第1条规则的时候就感觉到这是一本很好的书,可以把我们的Java功底提升一个档次,我还是比较推荐的.这里我主要就关于覆盖equals.hashCode和toString方法来做一个笔记总 ...
- 《Effective java》—–读书笔记
2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己!预计在2016年要看12本书,主要涉及java基础.Spring研究.java并 ...
- 《Effective Java》 第二讲:对于所有对象都通用的方法
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 上接<Effective Java> 第一讲:创建和销毁对象 八.覆盖 equals 时 ...
- 《Effective Java》读书笔记--创建和销毁对象
2019独角兽企业重金招聘Python工程师标准>>> 考虑用静态工厂方法代替构造函数. 当我们在写一个工具类时,是不希望用户将该类实例化的,所以应该定义一个private的构造函数 ...
- 《Effective Java》读书笔记 - 11.序列化
Chapter 11 Serialization Item 74: Implement Serializable judiciously 让一个类的实例可以被序列化不仅仅是在类的声明中加上" ...
- 《Effective Java》 读书笔记(持续更新)
2.1 用静态工厂方法代替构造器 静态工厂方法: 不通过 new (如:Date date = new Date();) 而是用一个静态方法来对外提供自身实例的方法叫做静态工厂方法(Static fa ...
最新文章
- BaseActivity的抽取
- C# WinForm开发系列 - WebBrowser
- 电气:6机30节点经济调度(考虑负荷平衡、线路容量、斜坡约束)代码实现
- Web 四种常见的POST提交数据方式
- linux c之管道的介绍、创建关闭和简单读写(父进程向子进程写入数据)
- 通过shell访问hive_【HIVE】SHELL调用Hive查询
- 苹果回应大数据杀熟:罪在开发者,和苹果没关系!
- J2ME游戏开发感想
- 材料力学考研可以用计算机吗,2020海南大学材料力学专业超详细考研经验分享...
- Java 最佳学习途径
- 苹果邮件怎么添加qq邮箱_QQ邮箱为何能收件,不能发邮件啊!??
- Android系统架构和应用程序基本概念详解
- 推荐.Net、C# 逆向反编译四大工具利器(请勿用来非法行为)
- PyTorch神经网络框架
- 『Java CVE』CVE-2022-34169: Xalan-J XSLT整数截断漏洞PoC结构再浅析
- 【附源码】计算机毕业设计SSM我的大学电子相册
- 中科院计算机网络信息中心是一种怎样的存在?
- 汇编语言-int指令
- python有哪些码_Python字节码介绍
- chrome运行 Android,告诉你如何在Chrome上运行Android应用!
热门文章
- 模块电源(三):PCB Layout
- CentOS下安装php gd库报错Error: php56w-common conflicts with php-common-5.3.3-48.el6_8.x86_64
- hightcharts 如何修改legend图例的样式
- 【转】Windows和Ubuntu双系统,修复UEFI引导的两种办法
- AutoCompleteTextView输入汉字拼音首字母实现过滤提示(支持多音字,Filterable的使用)...
- solr处理oracle中CLOB、BLOB
- 鼠标放在一个连接上,会显示图片(类似tooltip)
- Java:下拉列表绑定后台数据
- Animation中的scale、rotate、translate、alpha
- LeetCode Factorial Trailing Zeroes (阶乘后缀零)