Effective java笔记-对于所有对象都通用的方法
来源:互联网 发布:大学的意义知乎 编辑:程序博客网 时间:2024/04/30 04:28
对于所有对象都通用的方法
第8条 覆盖equals时请遵守通用约定
满足以下任何一个条件,可以不覆盖equals:类的每个实例本质上都是唯一的。(如Thread)不关心是否提供了“逻辑相等”的测试功能(如Random)超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的。(如Set)类是私有的或包级私有的,可以确定他的equals方法永远不会被调用
当类有自己的逻辑相等的概念且父类没有实现期望的功能时要覆盖equals,且覆盖之后还可以使这个“类的值”可以作为key,一般这种类都是一些“值类”,如String,Integer。不过有一种“值类”不需要覆盖equals,就是实例受控的类,这种类只会存在一个对象,所以对象相等和值相等就是一回事了。覆盖equals方法要遵守一些规范(equals方法实现了等价关系):自反性,对称性,传递性,一致性(非null x,y只要equals的比较操作在对象中所用的信息没有被修改,则多次调用x。equals(y)返回值时一致的),x.equals(null)必需返回false(x非null)
对称性例子:
// Broken - violates symmetry! - Pages 36-37package org.effectivejava.examples.chapter03.item08;public final class CaseInsensitiveString { private final String s; public CaseInsensitiveString(String s) { if (s == null) throw new NullPointerException(); this.s = s; } // Broken - violates symmetry! @Override public boolean equals(Object o) { if (o instanceof CaseInsensitiveString) return s.equalsIgnoreCase(((CaseInsensitiveString) o).s); if (o instanceof String) // One-way interoperability! return s.equalsIgnoreCase((String) o); return false; } // This version is correct. // @Override public boolean equals(Object o) { // return o instanceof CaseInsensitiveString && // ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); // } public static void main(String[] args) { CaseInsensitiveString cis = new CaseInsensitiveString("Polish"); String s = "polish"; System.out.println(cis.equals(s) + " " + s.equals(cis)); }}
传递性例子:
// Simple immutable two-dimensional integer point class - Page 37package org.effectivejava.examples.chapter03.item08;public class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point) o; return p.x == x && p.y == y; } // See Item 9 @Override public int hashCode() { return 31 * x + y; }}
上面代码定义了一个Point类,它们的逻辑相等条件是x,y相等,现在再定义一个ColorPoint,继承自Point:
public enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET}// Attempting to add a value component to Point - Pages 37 - 38package org.effectivejava.examples.chapter03.item08;public class ColorPoint extends Point { private final Color color; public ColorPoint(int x, int y, Color color) { super(x, y); this.color = color; } // Broken - violates symmetry! @Override public boolean equals(Object o) { if (!(o instanceof ColorPoint)) return false; return super.equals(o) && ((ColorPoint) o).color == color; } public static void main(String[] args) { // First equals function violates symmetry Point p = new Point(1, 2); ColorPoint cp = new ColorPoint(1, 2, Color.RED); System.out.println(p.equals(cp) + " " + cp.equals(p)); }}
你会发现这样违反了对称性,改成这样,又会违反传递性
// Broken - violates transitivity! @Override public boolean equals(Object o) { if (!(o instanceof Point)) return false; If o is a normal Point, do a color-blind comparison if (!(o instanceof ColorPoint)) return o.equals(this); o is a ColorPoint; do a full comparison return super.equals(o) && ((ColorPoint)o).color == color; }// Second equals function violates transitivity ColorPoint p1 = new ColorPoint(1, 2, Color.RED); Point p2 = new Point(1, 2); ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE); System.out.printf("%s %s %s%n", p1.equals(p2), p2.equals(p3), p1.equals(p3));
你可能这样实现Point的equals方法:
// Broken - violates Liskov substitution principle - Pages 39-40 // @Override public boolean equals(Object o) { // if (o == null || o.getClass() != getClass()) // return false; // Point p = (Point) o; // return p.x == x && p.y == y; // }
这样看起来还可以,但结果是不可接受的,假设我们通过不添加值组件的方式扩展了Point
// Trivial subclass of Point - doesn't add a value component - Page 39package org.effectivejava.examples.chapter03.item08;import java.util.concurrent.atomic.AtomicInteger;public class CounterPoint extends Point { private static final AtomicInteger counter = new AtomicInteger(); public CounterPoint(int x, int y) { super(x, y); counter.incrementAndGet(); } public int numberCreated() { return counter.get(); }}
假设又有一个方法是用来判断某个Point是否是在单位圆中的整值点:
// Test program that uses CounterPoint as Pointpackage org.effectivejava.examples.chapter03.item08;import java.util.HashSet;import java.util.Set;public class CounterPointTest { // Initialize UnitCircle to contain all Points on the unit circle private static final Set<Point> unitCircle; static { unitCircle = new HashSet<Point>(); unitCircle.add(new Point(1, 0)); unitCircle.add(new Point(0, 1)); unitCircle.add(new Point(-1, 0)); unitCircle.add(new Point(0, -1)); } public static boolean onUnitCircle(Point p) { return unitCircle.contains(p); } //里氏替换法则认为一个类型的任何重要属性也将适用于其子类型,因此为该方法定义的方法也应该在子类型上运行地很好,可是: public static void main(String[] args) { Point p1 = new Point(1, 0); Point p2 = new CounterPoint(1, 0); // Prints true System.out.println(onUnitCircle(p1)); // Should print true, but doesn't if Point uses getClass-based equals System.out.println(onUnitCircle(p2)); }}
到这里就说明了Point不能使用基于getClass地equals方法,那这样ColorPoint又怎么办呢,也就是说如何即扩展不可实例化的(?)类,又增加值组件(ColorPoint增加了值组件,CounterPointTest没有增加),有一个权宜之计:使用组合,以下是最终的完整解决方案:
// Simple immutable two-dimensional integer point class - Page 37package org.effectivejava.examples.chapter03.item08.composition;public class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point) o; return p.x == x && p.y == y; } // See Item 9 @Override public int hashCode() { return 31 * x + y; }}package org.effectivejava.examples.chapter03.item08.composition;public enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET}// Adds a value component without violating the equals contract - Page 40package org.effectivejava.examples.chapter03.item08.composition;public class ColorPoint { private final Point point; private final Color color; public ColorPoint(int x, int y, Color color) { if (color == null) throw new NullPointerException(); point = new Point(x, y); this.color = color; } /** * Returns the point-view of this color point. */ public Point asPoint() { return point; } @Override public boolean equals(Object o) { if (!(o instanceof ColorPoint)) return false; ColorPoint cp = (ColorPoint) o; return cp.point.equals(point) && cp.color.equals(color); } @Override public int hashCode() { return point.hashCode() * 33 + color.hashCode(); }}
一致性:不要使equals方法依赖于不可靠的资源。java.net.URL就依赖于URL中主机IP地址的比较,这违反了一致性,不过因为兼容性的需要无法改变这个行为。覆盖equals时总是覆盖hashCode方法;不要将equals声明中的Object对象替换为其他类型
第九条 覆盖equals时总是覆盖hashCode
覆盖equals时如果不覆盖hashCode就会导致该类无法结合所有基于散列的集合一起工作。Object规范:1.equals基于的比较信息不变则hashCode也不能变2.x.equals(y)==true,则x.hashCode()==y.hashCode()
3.x.equals(y)==false,x.hashCode()和y.hashCode()不一定不同,但要知道尽可能使得两个不相等(equals返回false)的对象的hashCode不同可提高散列表性能
package org.effectivejava.examples.chapter03.item09;// Shows the need for overriding hashcode when you override equals - Pages 45-46import java.util.HashMap;import java.util.Map;public final class PhoneNumber { private final short areaCode; private final short prefix; private final short lineNumber; public PhoneNumber(int areaCode, int prefix, int lineNumber) { rangeCheck(areaCode, 999, "area code"); rangeCheck(prefix, 999, "prefix"); rangeCheck(lineNumber, 9999, "line number"); this.areaCode = (short) areaCode; this.prefix = (short) prefix; this.lineNumber = (short) lineNumber; } private static void rangeCheck(int arg, int max, String name) { if (arg < 0 || arg > max) throw new IllegalArgumentException(name + ": " + arg); } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof PhoneNumber)) return false; PhoneNumber pn = (PhoneNumber) o; return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode; } // Broken - no hashCode method! // A decent hashCode method - Page 48 // @Override public int hashCode() { // int result = 17; // result = 31 * result + areaCode; // result = 31 * result + prefix; // result = 31 * result + lineNumber; // return result; // } // Lazily initialized, cached hashCode - Page 49 // private volatile int hashCode; // (See Item 71) // // @Override public int hashCode() { // int result = hashCode; // if (result == 0) { // result = 17; // result = 31 * result + areaCode; // result = 31 * result + prefix; // result = 31 * result + lineNumber; // hashCode = result; // } // return result; // } public static void main(String[] args) { Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>(); m.put(new PhoneNumber(707, 867, 5309), "Jenny"); System.out.println(m.get(new PhoneNumber(707, 867, 5309))); }}
在散列码的计算过程中,可以把冗余域排除在外(冗余域就是可以通过其他域的值计算出来的),必需排除equals比较计算中没有用到的域,否则会违反上面第二条。
第十条 始终要覆盖toString
比较简单,直接给个例子好了:
// Adding a toString method to PhoneNumber - page 52package org.effectivejava.examples.chapter03.item10;import java.util.HashMap;import java.util.Map;public final class PhoneNumber { private final short areaCode; private final short prefix; private final short lineNumber; public PhoneNumber(int areaCode, int prefix, int lineNumber) { rangeCheck(areaCode, 999, "area code"); rangeCheck(prefix, 999, "prefix"); rangeCheck(lineNumber, 9999, "line number"); this.areaCode = (short) areaCode; this.prefix = (short) prefix; this.lineNumber = (short) lineNumber; } private static void rangeCheck(int arg, int max, String name) { if (arg < 0 || arg > max) throw new IllegalArgumentException(name + ": " + arg); } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof PhoneNumber)) return false; PhoneNumber pn = (PhoneNumber) o; return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode; } @Override public int hashCode() { int result = 17; result = 31 * result + areaCode; result = 31 * result + prefix; result = 31 * result + lineNumber; return result; } /** * Returns the string representation of this phone number. The string * consists of fourteen characters whose format is "(XXX) YYY-ZZZZ", where * XXX is the area code, YYY is the prefix, and ZZZZ is the line number. * (Each of the capital letters represents a single decimal digit.) * * If any of the three parts of this phone number is too small to fill up * its field, the field is padded with leading zeros. For example, if the * value of the line number is 123, the last four characters of the string * representation will be "0123". * * Note that there is a single space separating the closing parenthesis * after the area code from the first digit of the prefix. */ @Override public String toString() { return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber); } public static void main(String[] args) { Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>(); m.put(new PhoneNumber(707, 867, 5309), "Jenny"); System.out.println(m); }}
第十一条 谨慎地覆盖clone
Cloneable接口的目的是作为对象的一个mixin接口(见18条),表明这样的对象允许克隆。既然Cloneable并没有包含任何方法,那他到底有什么用呢:他会改变Object中受保护的clone方法,如果一个类实现了Cloneable,Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedException异常,这是接口的一种极端非典型用法,不值得效仿。还有Object的clone方法是protected的,那我们要怎么用,只要子类继承,并将子类中的clone改为public就可。(我们一般会在子类的clone中调用super。clone,后面会说)Colne方法的通用约定是非常弱的: 一般:x.clone()!=x为true;x.clone().getClass()==x.getClass() 为true;x。clone().equals(x)为true;这些不是绝对的要求。 还有要求这个过程中没有调用构造器。这个约定存在几个问题: 1.不调用构造器太强硬了 2.x.clone().getClass()通常应该等于x.getClass()又太软弱了(??)在实践中,程序员会假设:如果它们扩展了一个类,并且从子类中调用了super.clone(),返回的对象将是该子类的实例。超类能够提供这种功能的唯一途径是,返回一个通过调用super.clone而得到的对象。如果clone方法返回一个由构造器创建的对象,他就得到有错误的类。如果所有的超类都遵守这条规则,那么调用super.clone最终会调用Object的clone方法,从而创建出正确类的实例。(书上这里没怎么看懂,但可以知道的是,要遵守规则:返回的对象是通过super.clone创建的,而不是自己调用构造器创建的)例子:
// Adding a clone method to PhoneNumber - page 55package org.effectivejava.examples.chapter03.item11;import java.util.HashMap;import java.util.Map;public final class PhoneNumber implements Cloneable { private final short areaCode; private final short prefix; private final short lineNumber; public PhoneNumber(int areaCode, int prefix, int lineNumber) { rangeCheck(areaCode, 999, "area code"); rangeCheck(prefix, 999, "prefix"); rangeCheck(lineNumber, 9999, "line number"); this.areaCode = (short) areaCode; this.prefix = (short) prefix; this.lineNumber = (short) lineNumber; } private static void rangeCheck(int arg, int max, String name) { if (arg < 0 || arg > max) throw new IllegalArgumentException(name + ": " + arg); } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof PhoneNumber)) return false; PhoneNumber pn = (PhoneNumber) o; return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode; } @Override public int hashCode() { int result = 17; result = 31 * result + areaCode; result = 31 * result + prefix; result = 31 * result + lineNumber; return result; } /** * Returns the string representation of this phone number. The string * consists of fourteen characters whose format is "(XXX) YYY-ZZZZ", where * XXX is the area code, YYY is the prefix, and ZZZZ is the line number. * (Each of the capital letters represents a single decimal digit.) * * If any of the three parts of this phone number is too small to fill up * its field, the field is padded with leading zeros. For example, if the * value of the line number is 123, the last four characters of the string * representation will be "0123". * * Note that there is a single space separating the closing parenthesis * after the area code from the first digit of the prefix. */ @Override public String toString() { return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber); } @Override public PhoneNumber clone() { try { return (PhoneNumber) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); // Can't happen } } public static void main(String[] args) { PhoneNumber pn = new PhoneNumber(707, 867, 5309); Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>(); m.put(pn, "Jenny"); System.out.println(m.get(pn.clone())); }}
对象中有可变的对象时clone,例子:
// A cloneable version of Stack - Pages 56-57package org.effectivejava.examples.chapter03.item11;import java.util.Arrays;public class Stack implements Cloneable { private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack() { this.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; // Eliminate obsolete reference return result; } public boolean isEmpty() { return size == 0; } @Override public Stack clone() { try { Stack result = (Stack) super.clone(); result.elements = elements.clone(); return result; } catch (CloneNotSupportedException e) { throw new AssertionError(); } } // Ensure space for at least one more element. private void ensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); } // To see that clone works, call with several command line arguments public static void main(String[] args) { Stack stack = new Stack(); for (String arg : args) stack.push(arg); Stack copy = stack.clone(); while (!stack.isEmpty()) System.out.print(stack.pop() + " "); System.out.println(); while (!copy.isEmpty()) System.out.print(copy.pop() + " "); }}
实际上,clone方法就是另一个构造器;你必须确保它不会伤害到原始的对象,并确保正确地创建被克隆对象中的约束条件(invariant)如果elements域是final的,上述方案就不能正常工作,因为clone方法是被禁止给elements域赋新值。这是个根本的问题:clone架构与引用可变对象的final域的正常用法是不相兼容的,除非在原始对象和克隆对象之间安全地共享此可变对象。有时候这样还要这样:
public class HashTable implements Cloneable{ private Entry[] buckets = ...; private static class Entry{ final Object key; Object value; Entry next; Entry(Object key,Object value,Entrt next){ this.key = key; this.value=value; this.next = next; } protected Object deepCopy() { return new Entry(hash, key, value, (next==null ? null : next,deepCopy())); } } public HashTable clone(){ try{ HasbTable result = (HasbTable)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(。。。){ 。。。 } }}
不过看了这段代码,到不知道为什么上面那个类不用deepCopy呢???和构造器一样,clone方法不应该在构造地过程中,调用新对象中任何非final的方法。如果clone调用了一个被覆盖的方法,那么在该翻噶发所在的子类有机会修正他在克隆对象的状态前,该方法就会被执行,这样可能导致克隆对象和原始对象之间的不一致。Object的clone方法被声明为可抛出CloneNotSupportedException,公有的clone应该省略这个声明(为了好用),如果专门为了继承而设计的类覆盖了clone方法,这个clone方法就应该声明为protected和可抛出CloneNotSupportedException,并且不实现Cloneable。
实现拷贝还可以用拷贝构造器或拷贝工厂:拷贝构造器只是一个构造器,它唯一的参数类型是包含该构造器的类,如public Yum(Yum yum);拷贝工厂是类似于拷贝构造器的静态工厂,如:public static Yum newInstance(Yum yum);拷贝构造器和拷贝工厂比clone方法有更多优势 而且使用拷贝构造器或者拷贝工厂来代替clone方法时,并没有放弃接口的功能特性,更进一步,拷贝构造器或者拷贝工厂可以带一个参数,参数类型是通过该类实现的接口。例如所有通用集合实现都提供了一个拷贝构造器,它的参数类型为Collection或Map。基于接口的拷贝构造器或拷贝工厂(更准确地叫法是转换构造器和转换工厂),允许客户选择拷贝地实现类型,而不是强迫客户接受原始地实现类型。例如:假设你有一个HashSet,并且希望把它拷贝成一个TreeSet可以使用转换构造器:new TreeSet(s).由于clone方法具有那么多缺点,有些专家级的程序员干脆从来不去覆盖clone方法,也从来不去调用它,除非拷贝数组。
第十二条 考虑实现Comparable接口
下面的程序依赖于String实现了Comparable接口,它去掉了命令行参数列表中的重复参数,并按字母顺序打印出来:
public class WordList { public static void main(String[] args) { Set<String> s = new TreeSet<String>(); Collections.addAll(s, args); System.out.println(s); }}
java平台类库中所有值类(value classes)都实现了Comparable接口规范:1.sgn(x.compareTo(y))==-sgn(y.compareTo(x))为true2.(x.compareTo(y)>0&&y.compareTo(z)>0)=>x.compareTo(z)>03.x.compareTo(y)==0=>所有z满足sgn(x.compareTo(z))==sgn(y.compareTo(z)) 4.强烈建议:(x.compareTo(y)==0)==(x.equals(y)),不过这并非绝对必要,一般来说若违反这条规定,应当说明,推荐这样的说法:“注意:该类具有内在的排序功能,但是与equals不一致(inconsistent with equals)“
针对equals的的权宜之计也同样适用于compareTo方法。如果你想为一个实现了Compareable接口的类增加值组件,请不要扩展这个类,而是要编写一个不相关的类,其中包含第一个类的一个实例。然后提供一个视图(view)方法返回这个实例。这样既可以让你自由地在第二个类上实现compareTo方法,同时也允许它的客户端在必要的时候,把第二个类的实例视同第一个类的实例关于第四条建议: 如果一个类的equals和compareTo不一致,如果有一个有序集合包含这个类的元素,那这个集合就无法遵守相关集合接口(Collection,Set,Map)的通用约定,因为对于这些接口的约定是根据equals来定义的,而有序集合使用了compareTo的等同性测试,举个例子: BigDecimal的equals和compareTo不一致,如果你将new BigDecimal("1.0")和new BigDecimal("1.00")到HashSet中,这个HashSet将包含两个元素,但如果将它们add入TreeSet中,TreeSet将只包含一个元素。
一种compareTo实现方式:
// Making PhoneNumber comparable - Pages 65-66package org.effectivejava.examples.chapter03.item12;import java.util.NavigableSet;import java.util.Random;import java.util.TreeSet;public final class PhoneNumber implements Cloneable, Comparable<PhoneNumber> { private final short areaCode; private final short prefix; private final short lineNumber; public PhoneNumber(int areaCode, int prefix, int lineNumber) { rangeCheck(areaCode, 999, "area code"); rangeCheck(prefix, 999, "prefix"); rangeCheck(lineNumber, 9999, "line number"); this.areaCode = (short) areaCode; this.prefix = (short) prefix; this.lineNumber = (short) lineNumber; } private static void rangeCheck(int arg, int max, String name) { if (arg < 0 || arg > max) throw new IllegalArgumentException(name + ": " + arg); } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof PhoneNumber)) return false; PhoneNumber pn = (PhoneNumber) o; return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode; } @Override public int hashCode() { int result = 17; result = 31 * result + areaCode; result = 31 * result + prefix; result = 31 * result + lineNumber; return result; } /** * Returns the string representation of this phone number. The string * consists of fourteen characters whose format is "(XXX) YYY-ZZZZ", where * XXX is the area code, YYY is the prefix, and ZZZZ is the line number. * (Each of the capital letters represents a single decimal digit.) * * If any of the three parts of this phone number is too small to fill up * its field, the field is padded with leading zeros. For example, if the * value of the line number is 123, the last four characters of the string * representation will be "0123". * * Note that there is a single space separating the closing parenthesis * after the area code from the first digit of the prefix. */ @Override public String toString() { return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber); } @Override public PhoneNumber clone() { try { return (PhoneNumber) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); // Can't happen } } // Works fine, but can be made faster // public int compareTo(PhoneNumber pn) { // // Compare area codes // if (areaCode < pn.areaCode) // return -1; // if (areaCode > pn.areaCode) // return 1; // // // Area codes are equal, compare prefixes // if (prefix < pn.prefix) // return -1; // if (prefix > pn.prefix) // return 1; // // // Area codes and prefixes are equal, compare line numbers // if (lineNumber < pn.lineNumber) // return -1; // if (lineNumber > pn.lineNumber) // return 1; // // return 0; // All fields are equal // } public int compareTo(PhoneNumber pn) { // Compare area codes int areaCodeDiff = areaCode - pn.areaCode; if (areaCodeDiff != 0) return areaCodeDiff; // Area codes are equal, compare prefixes int prefixDiff = prefix - pn.prefix; if (prefixDiff != 0) return prefixDiff; // Area codes and prefixes are equal, compare line numbers return lineNumber - pn.lineNumber; } public static void main(String[] args) { NavigableSet<PhoneNumber> s = new TreeSet<PhoneNumber>(); for (int i = 0; i < 10; i++) s.add(randomPhoneNumber()); System.out.println(s); } private static final Random rnd = new Random(); private static PhoneNumber randomPhoneNumber() { return new PhoneNumber((short) rnd.nextInt(1000), (short) rnd.nextInt(1000), (short) rnd.nextInt(10000)); }}
0 0
- Effective java笔记-对于所有对象都通用的方法
- Effective Java:对于所有的对象都通用的方法
- effective java(对于所有对象都通用的方法)
- Effective Java:对于所有对象都通用的方法
- Effective Java Note(对于所有对象都通用的方法)
- Effective Java:对于所有对象都通用的方法
- Effective Java学习笔记(二)对于所有对象都通用的方法
- Effective Java读书笔记(3对于所有对象都通用的方法)
- Effective Java读书笔记(第3章-对于所有对象都通用的方法)
- effective java-读书笔记-第三章 对于所有对象都通用的方法
- Effective Java(二) 对于所有对象都通用的方法
- 【读书笔记】《Effective Java》(2)--对于所有对象都通用的方法
- 《Effective Java》第3章 对于所有对象都通用的方法
- Effective Java读书笔记——第三章 对于所有对象都通用的方法
- effective java 读书笔记---第三章对于所有对象都通用的方法
- Effective Java系列读后感(二)-对于所有对象都通用的方法
- 《Effective Java》 第二讲:对于所有对象都通用的方法
- 《Effective Java》读书笔记(二)之对于所有对象都通用的方法
- 中断法定时
- 2016年上海SODA开放数据大赛进入复赛作品《安全橙子》网页源码(利用百度echart框架热力图展示城市安全信息)
- 重构:引入NULL对象
- mat相关函数
- 2017-03-11
- Effective java笔记-对于所有对象都通用的方法
- 递归生成格雷码
- 凸优化笔记(一):仿射集,凸集与锥
- POJ
- 设计模式之建造者模式(Builder Pattern)
- 二叉树深度优先遍历
- 关于python类和实例的一些尝试
- 基于C++的任意格式图片显示及RGB读取
- OpenGL——光照系统