How to Write an Equality Method in Java
2009-06-22 18:02
691 查看
by Martin Odersky, Lex Spoon, and Bill Venners
June 1, 2009
Summary
This article describes a technique for overriding the
In Item 8 of Effective Java1, Josh Bloch describes the difficulty of preserving the
There is no way to extend an instantiable class and add a value component while preserving the equals contract, unless you are willing to forgo the benefits of object-oriented abstraction.
Chapter 28 of Programming in Scala shows an approach that allows subclasses to extend an instantiable class, add a value component, and nevertheless preserve the
This is problematic, because equality is at the basis of many other things. For one, a faulty equality method for a type
Here are four common pitfalls3 that can cause inconsistent behavior when overriding
Defining
Changing
Defining
Failing to define
These four pitfalls are discussed in the remainder of this section.
Pitfall #1: Defining
Consider adding an equality method to the following class of simple points:
A seemingly obvious, but wrong way would be to define it like this:
What's wrong with this method? At first glance, it seems to work OK:
However, trouble starts once you start putting points into a collection:
How can it be that
Now, were you to repeat the first comparison, but with the alias
What went wrong? In fact, the version of
Because the
A better
Now
Pitfall #2: Changing
If you repeat the comparison of
In fact, this outcome is not 100% certain. You might also get
Note that the collection in the example above is a
The problem was that the last implementation of
If two objects are equal according to the
In fact, it's well known in Java that
This is just one of many possible implementations of
Adding
Pitfall #3: Defining
Consider the following slight variation of class
The only difference is that the fields
Now, if you change a field in point
This looks strange. Where did
So here's a set that does not contain
The lesson to be drawn from this example is that when
Pitfall #4: Failing to define
The contract of the
It is reflexive: for any non-null value
It is symmetric: for any non-null values
It is transitive: for any non-null values
It is consistent: for any non-null values
For any non-null value
The definition of
This is what many programmers would likely write. Note that in this case, class
Taking the class
The comparison “
The loss in symmetry can have unexpected consequences for collections. Here's an example:
So even though
How can you change the definition of
The new definition of
Taken individually,
However, comparing
Hence, the transitivity clause of
Making the
You can then revert class
Here, an instance of class
Consider the following slightly roundabout way to define a point at coordinates
Is
The
So it seems we are stuck. Is there a sane way to redefine equality on several levels of the class hierarchy while keeping its contract? In fact, there is such a way, but it requires one more method to redefine together with
The method should return
The
Here's the corresponding implementation of
It can be shown that the new definition of
On the other hand, instances of different subclasses of
These examples demonstrate that if a superclass
One potential criticism of the
The problem with writing an
June 1, 2009
This article describes a technique for overriding the
equalsmethod that preserves the contract of
equalseven when subclassses of concrete classes add new fields.
In Item 8 of Effective Java1, Josh Bloch describes the difficulty of preserving the
equalscontract when subclassing as a “fundamental problem of equivalence relations in object-oriented languages.” Bloch writes:
There is no way to extend an instantiable class and add a value component while preserving the equals contract, unless you are willing to forgo the benefits of object-oriented abstraction.
Chapter 28 of Programming in Scala shows an approach that allows subclasses to extend an instantiable class, add a value component, and nevertheless preserve the
equalscontract. Although in the technique is described in the book in the context of defining Scala classes, it is also applicable to classes defined in Java. In this article, we present the technique using text adapted from the relevant section of Programming in Scala, but with the code examples translated from Scala into Java.
Commmon equality pitfalls
Classjava.lang.Objectdefines an
equalsmethod, which subclasses may override. Unfortunately, it turns out that writing a correct equality method is surprisingly difficult in object-oriented languages. In fact, after studying a large body of Java code, the authors of a 2007 paper concluded that almost all implementations of
equalsmethods are faulty.2
This is problematic, because equality is at the basis of many other things. For one, a faulty equality method for a type
Cmight mean that you cannot reliably put an object of type
Cin a collection. You might have two elements
elem1,
elem2of type
Cthat are equal, i.e., “
elem1.equals(elem2)” yields
true. Nevertheless, with commonly occurring faulty implementations of the
equalsmethod, you could still see behavior like the following:
Set<C> hashSet = new java.util.HashSet<C>(); hashSet.add(elem1); hashSet.contains(elem2); // returns false!
Here are four common pitfalls3 that can cause inconsistent behavior when overriding
equals:
Defining
equalswith the wrong signature.
Changing
equalswithout also changing
hashCode.
Defining
equalsin terms of mutable fields.
Failing to define
equalsas an equivalence relation.
These four pitfalls are discussed in the remainder of this section.
Pitfall #1: Defining equals
with the wrong signature.
Consider adding an equality method to the following class of simple points:public class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } // ... }
A seemingly obvious, but wrong way would be to define it like this:
// An utterly wrong definition of equals public boolean equals(Point other) { return (this.getX() == other.getX() && this.getY() == other.getY()); }
What's wrong with this method? At first glance, it seems to work OK:
Point p1 = new Point(1, 2); Point p2 = new Point(1, 2); Point q = new Point(2, 3); System.out.println(p1.equals(p2)); // prints true System.out.println(p1.equals(q)); // prints false
However, trouble starts once you start putting points into a collection:
import java.util.HashSet; HashSet<Point> coll = new HashSet<Point>(); coll.add(p1); System.out.println(coll.contains(p2)); // prints false
How can it be that
colldoes not contain
p2, even though
p1was added to it, and
p1and
p2are equal objects? The reason becomes clear in the following interaction, where the precise type of one of the compared points is masked. Define
p2aas an alias of
p2, but with type
Objectinstead of
Point:
Object p2a = p2;
Now, were you to repeat the first comparison, but with the alias
p2ainstead of
p2, you would get:
System.out.println(p1.equals(p2a)); // prints false
What went wrong? In fact, the version of
equalsgiven previously does not override the standard method
equals, because its type is different. Here is the type of the
equalsmethod as it is defined in the root class
Object:
public boolean equals(Object other)
Because the
equalsmethod in
Pointtakes a
Pointinstead of an
Objectas an argument, it does not override
equalsin
Object. Instead, it is just an overloaded alternative. Overloading in Java is resolved by the static type of the argument, not the run-time type. So as long as the static type of the argument is
Point, the
equalsmethod in
Pointis called. However, once the static argument is of type
Object, the
equalsmethod in
Objectis called instead. This method has not been overridden, so it is still implemented by comparing object identity. That's why the comparison “
p1.equals(p2a)” yields
falseeven though points
p1and
p2ahave the same
xand
yvalues. That's also why the
containsmethod in
HashSetreturned
false. Since that method operates on generic sets, it calls the generic
equalsmethod in
Objectinstead of the overloaded variant in
Point.
A better
equalsmethod is the following:
// A better definition, but still not perfect @Override public boolean equals(Object other) { boolean result = false; if (other instanceof Point) { Point that = (Point) other; result = (this.getX() == that.getX() && this.getY() == that.getY()); } return result; }
Now
equalshas the correct type. It takes a value of type
Objectas parameter and it yields a
booleanresult. The implementation of this method uses
instanceofand a cast. It first tests whether the
otherobject is also of type
Point. If it is, it compares the coordinates of the two points and returns the result. Otherwise the result is
false.
Pitfall #2: Changing equals
without also changing hashCode
If you repeat the comparison of p1and
p2awith the latest definition of
Pointdefined previously, you will get
true, as expected. However, if you repeat the
HashSet.containstest, you will probably still get
false:
Point p1 = new Point(1, 2); Point p2 = new Point(1, 2); HashSet<Point> coll = new HashSet<Point>(); coll.add(p1); System.out.println(coll.contains(p2)); // prints false (probably)
In fact, this outcome is not 100% certain. You might also get
truefrom the experiment. If you do, you can try with some other points with coordinates 1 and 2. Eventually, you'll get one which is not contained in the set. What goes wrong here is that
Pointredefined
equalswithout also redefining
hashCode.
Note that the collection in the example above is a
HashSet. This means elements of the collection are put in “hash buckets” determined by their hash code. The
containstest first determines a hash bucket to look in and then compares the given elements with all elements in that bucket. Now, the last version of class
Pointdid redefine
equals, but it did not at the same time redefine
hashCode. So
hashCodeis still what it was in its version in class
Object: some transformation of the address of the allocated object. The hash codes of
p1and
p2are almost certainly different, even though the fields of both points are the same. Different hash codes mean with high probability different hash buckets in the set. The
containstest will look for a matching element in the bucket which corresponds to
p2's hash code. In most cases, point
p1will be in another bucket, so it will never be found.
p1and
p2might also end up by chance in the same hash bucket. In that case the test would return
true.
The problem was that the last implementation of
Pointviolated the contract on
hashCodeas defined for class
Object:
If two objects are equal according to the
equals(Object)method, then calling the
hashCodemethod on each of the two objects must produce the same integer result.
In fact, it's well known in Java that
hashCodeand
equalsshould always be redefined together. Furthermore,
hashCodemay only depend on fields that
equalsdepends on. For the
Pointclass, the following would be a suitable definition of
hashCode:
public class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } @Override public boolean equals(Object other) { boolean result = false; if (other instanceof Point) { Point that = (Point) other; result = (this.getX() == that.getX() && this.getY() == that.getY()); } return result; } @Override public int hashCode() { return (41 * (41 + getX()) + getY()); } }
This is just one of many possible implementations of
hashCode. Adding the constant
41to one integer field
x, multiplying the result with the prime number
41, and adding to that result the other integer field
ygives a reasonable distribution of hash codes at a low cost in running time and code size.
Adding
hashCodefixes the problems of equality when defining classes like
Point. However, there are still other trouble spots to watch out for.
Pitfall #3: Defining equals
in terms of mutable fields
Consider the following slight variation of class Point:
public class Point { private int x; private int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } public void setX(int x) { // Problematic this.x = x; } public void setY(int y) { this.y = y; } @Override public boolean equals(Object other) { boolean result = false; if (other instanceof Point) { Point that = (Point) other; result = (this.getX() == that.getX() && this.getY() == that.getY()); } return result; } @Override public int hashCode() { return (41 * (41 + getX()) + getY()); } }
The only difference is that the fields
xand
yare no longer final, and two
setmethods have been added that allow clients to change the
xand
yvalues. The
equalsand
hashCodemethods are now defined in terms of these mutable fields, so their results change when the fields change. This can have strange effects once you put points in collections:
Point p = new Point(1, 2); HashSet<Point> coll = new HashSet<Point>(); coll.add(p); System.out.println(coll.contains(p)); // prints true
Now, if you change a field in point
p, does the collection still contain the point? We'll try it:
p.setX(p.getX() + 1); System.out.println(coll.contains(p)); // prints false (probably)
This looks strange. Where did
pgo? More strangeness results if you check whether the iterator of the set contains
p:
Iterator<Point> it = coll.iterator(); boolean containedP = false; while (it.hasNext()) { Point nextP = it.next(); if (nextP.equals(p)) { containedP = true; break; } } System.out.println(containedP); // prints true
So here's a set that does not contain
p, yet
pis among the elements of the set! What happened, of course, is that after the change to the
xfield, the point
pended up in the wrong hash bucket of the set
coll. That is, its original hash bucket no longer corresponded to the new value of its hash code. In a manner of speaking, the point
p“dropped out of sight” in the set
colleven though it still belonged to its elements.
The lesson to be drawn from this example is that when
equalsand
hashCodedepend on mutable state, it may cause problems for users. If they put such objects into collections, they have to be careful never to modify the depended-on state, and this is tricky. If you need a comparison that takes the current state of an object into account, you should usually name it something else, not
equals. Considering the last definition of
Point, it would have been preferable to omit a redefinition of
hashCodeand to name the comparison method
equalContents, or some other name different from
equals.
Pointwould then have inherited the default implementation of
equalsand
hashCode. So
pwould have stayed locatable in
colleven after the modification to its
xfield.
Pitfall #4: Failing to define equals
as an equivalence relation
The contract of the equalsmethod in
Objectspecifies that
equalsmust implement an equivalence relation on non-
nullobjects:
It is reflexive: for any non-null value
It is symmetric: for any non-null values
It is transitive: for any non-null values
It is consistent: for any non-null values
For any non-null value
x, the expression
x.equals(x)should return
true.
xand
y,
x.equals(y)should return
trueif and only if
y.equals(x)returns
true.
x,
y, and
z, if
x.equals(y)returns
trueand
y.equals(z)returns
true, then
x.equals(z)should return
true.
xand
y, multiple invocations of
x.equals(y)should consistently return
trueor consistently return
false, provided no information used in equals comparisons on the objects is modified.
x,
x.equals(null)should return
false.
The definition of
equalsdeveloped so far for class
Pointsatisfies the contract for
equals. However, things become more complicated once subclasses are considered. Say there is a subclass
ColoredPointof
Pointthat adds a field
colorof type
Color. Assume
Coloris defined as an enum:
public enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET; }
ColoredPointoverrides
equalsto take the new
colorfield into account:
public class ColoredPoint extends Point { // Problem: equals not symmetric private final Color color; public ColoredPoint(int x, int y, Color color) { super(x, y); this.color = color; } @Override public boolean equals(Object other) { boolean result = false; if (other instanceof ColoredPoint) { ColoredPoint that = (ColoredPoint) other; result = (this.color.equals(that.color) && super.equals(that)); } return result; } }
This is what many programmers would likely write. Note that in this case, class
ColoredPointneed not override
hashCode. Because the new definition of
equalson
ColoredPointis stricter than the overridden definition in
Point(meaning it equates fewer pairs of objects), the contract for
hashCodestays valid. If two colored points are equal, they must have the same coordinates, so their hash codes are guaranteed to be equal as well.
Taking the class
ColoredPointby itself, its definition of
equalslooks OK. However, the contract for
equalsis broken once points and colored points are mixed. Consider:
Point p = new Point(1, 2); ColoredPoint cp = new ColoredPoint(1, 2, Color.RED); System.out.println(p.equals(cp)); // prints true System.out.println(cp.equals(p)); // prints false
The comparison “
p equals cp” invokes
p's
equalsmethod, which is defined in class
Point. This method only takes into account the coordinates of the two points. Consequently, the comparison yields
true. On the other hand, the comparison “
cp equals p” invokes
cp's
equalsmethod, which is defined in class
ColoredPoint. This method returns
false, because
pis not a
ColoredPoint. So the relation defined by
equalsis not symmetric.
The loss in symmetry can have unexpected consequences for collections. Here's an example:
Set<Point> hashSet1 = new java.util.HashSet<Point>(); hashSet1.add(p); System.out.println(hashSet1.contains(cp)); // prints false Set<Point> hashSet2 = new java.util.HashSet<Point>(); hashSet2.add(cp); System.out.println(hashSet2.contains(p)); // prints true
So even though
pand
cpare equal, one
containstest succeeds whereas the other one fails.
How can you change the definition of
equalsso that it becomes symmetric? Essentially there are two ways. You can either make the relation more general or more strict. Making it more general means that a pair of two objects,
aand
b, is taken to be equal if either comparing
awith
bor comparing
bwith
ayields
true. Here's code that does this:
public class ColoredPoint extends Point { // Problem: equals not transitive private final Color color; public ColoredPoint(int x, int y, Color color) { super(x, y); this.color = color; } @Override public boolean equals(Object other) { boolean result = false; if (other instanceof ColoredPoint) { ColoredPoint that = (ColoredPoint) other; result = (this.color.equals(that.color) && super.equals(that)); } else if (other instanceof Point) { Point that = (Point) other; result = that.equals(this); } return result; } }
The new definition of
equalsin
ColoredPointchecks one more case than the old one: If the
otherobject is a
Pointbut not a
ColoredPoint, the method forwards to the
equalsmethod of
Point. This has the desired effect of making
equalssymmetric. Now, both “
cp.equals(p)” and “
p.equals(cp)” result in
true. However, the contract for
equalsis still broken. Now the problem is that the new relation is no longer transitive! Here's a sequence of statements that demonstrates this. Define a point and two colored points of different colors, all at the same position:
ColoredPoint redP = new ColoredPoint(1, 2, Color.RED); ColoredPoint blueP = new ColoredPoint(1, 2, Color.BLUE);
Taken individually,
redpis equal to
pand
pis equal to
bluep:
System.out.println(redP.equals(p)); // prints true System.out.println(p.equals(blueP)); // prints true
However, comparing
redPand
bluePyields
false:
System.out.println(redP.equals(blueP)); // prints false
Hence, the transitivity clause of
equals's contract is violated.
Making the
equalsrelation more general seems to lead to a dead end. We'll try to make it stricter instead. One way to make
equalsstricter is to always treat objects of different classes as different. That could be achieved by modifying the
equalsmethods in classes
Pointand
ColoredPoint. In class
Point, you could add an extra comparison that checks whether the run-time class of the other
Pointis exactly the same as this
Point's class, as follows:
// A technically valid, but unsatisfying, equals method public class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } @Override public boolean equals(Object other) { boolean result = false; if (other instanceof Point) { Point that = (Point) other; result = (this.getX() == that.getX() && this.getY() == that.getY() && this.getClass().equals(that.getClass())); } return result; } @Override public int hashCode() { return (41 * (41 + getX()) + getY()); } }
You can then revert class
ColoredPoint's implementation back to the version that previously had violated the symmetry requirement:4
public class ColoredPoint extends Point { // No longer violates symmetry requirement private final Color color; public ColoredPoint(int x, int y, Color color) { super(x, y); this.color = color; } @Override public boolean equals(Object other) { boolean result = false; if (other instanceof ColoredPoint) { ColoredPoint that = (ColoredPoint) other; result = (this.color.equals(that.color) && super.equals(that)); } return result; } }
Here, an instance of class
Pointis considered to be equal to some other instance of the same class only if the objects have the same coordinates and they have the same run-time class, meaning
.getClass()on either object returns the same value. The new definitions satisfy symmetry and transitivity because now every comparison between objects of different classes yields
false. So a colored point can never be equal to a point. This convention looks reasonable, but one could argue that the new definition is too strict.
Consider the following slightly roundabout way to define a point at coordinates
(1, 2):
Point pAnon = new Point(1, 1) { @Override public int getY() { return 2; } };
Is
pAnonequal to
p? The answer is no because the
java.lang.Classobjects associated with
pand
pAnonare different. For
pit is
Point, whereas for
pAnonit is an anonymous subclass of
Point. But clearly,
pAnonis just another point at coordinates
(1, 2). It does not seem reasonable to treat it as being different from
p.
The canEqual
method
So it seems we are stuck. Is there a sane way to redefine equality on several levels of the class hierarchy while keeping its contract? In fact, there is such a way, but it requires one more method to redefine together with equalsand
hashCode. The idea is that as soon as a class redefines
equals(and
hashCode), it should also explicitly state that objects of this class are never equal to objects of some superclass that implement a different equality method. This is achieved by adding a method
canEqualto every class that redefines
equals. Here's the method's signature:
public boolean canEqual(Object other)
The method should return
trueif the
otherobject is an instance of the class in which
canEqualis (re)defined,
falseotherwise. It is called from
equalsto make sure that the objects are comparable both ways. Here is a new (and final) implementation of class
Pointalong these lines:
public class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } @Override public boolean equals(Object other) { boolean result = false; if (other instanceof Point) { Point that = (Point) other; result = (that.canEqual(this) && this.getX() == that.getX() && this.getY() == that.getY()); } return result; } @Override public int hashCode() { return (41 * (41 + getX()) + getY()); } public boolean canEqual(Object other) { return (other instanceof Point); } }
The
equalsmethod in this version of class
Pointcontains the additional requirement that the other object can equal this one, as determined by the
canEqualmethod. The implementation of
canEqualin
Pointstates that all instances of
Pointcan be equal.
Here's the corresponding implementation of
ColoredPoint:
public class ColoredPoint extends Point { // No longer violates symmetry requirement private final Color color; public ColoredPoint(int x, int y, Color color) { super(x, y); this.color = color; } @Override public boolean equals(Object other) { boolean result = false; if (other instanceof ColoredPoint) { ColoredPoint that = (ColoredPoint) other; result = (that.canEqual(this) && this.color.equals(that.color) && super.equals(that)); } return result; } @Override public int hashCode() { return (41 * super.hashCode() + color.hashCode()); } @Override public boolean canEqual(Object other) { return (other instanceof ColoredPoint); } }
It can be shown that the new definition of
Pointand
ColoredPointkeeps the contract of
equals. Equality is symmetric and transitive. Comparing a
Pointto a
ColoredPointalways yields
false. Indeed, for any point
pand colored point
cp, “
p.equals(cp)” will return
falsebecause “
cp.canEqual(p)” will return
false. The reverse comparison, “
cp.equals(p)”, will also return
false, because
pis not a
ColoredPoint, so the first
instanceofcheck in the body of
equalsin
ColoredPointwill fail.
On the other hand, instances of different subclasses of
Pointcan be equal, as long as none of the classes redefines the equality method. For instance, with the new class definitions, the comparison of
pand
pAnonwould yield true. Here are some examples:
Point p = new Point(1, 2);
ColoredPoint cp = new ColoredPoint(1, 2, Color.INDIGO);
Point pAnon = new Point(1, 1) { @Override public int getY() { return 2; } };
Set<Point> coll = new java.util.HashSet<Point>();
coll.add(p);
System.out.println(coll.contains(p)); // prints true
System.out.println(coll.contains(cp)); // prints false
System.out.println(coll.contains(pAnon)); // prints true
These examples demonstrate that if a superclass
equalsimplementation defines and calls
canEqual, then programmers who implement subclasses can decide whether or not their subclasses may be equal to instances of the superclass. Because
ColoredPointoverrides
canEqual, for example, a colored point may never be equal to a plain-old point. But because the anonymous subclass referenced from
pAnondoes not override
canEqual, its instance can be equal to a
Pointinstance.
One potential criticism of the
canEqualapproach is that it violates the Liskov Substitution Principle (LSP). For example, the technique of implementing
equalsby comparing run-time classes, which led to the inability to define a subclass whose instances can equal instances of the superclass, has been described as a violation of the LSP.5 The reasoning is that the LSP states you should be able to use (substitute) a subclass instance where a superclass instance is required. In the previous example, however, “
coll.contains(cp)” returned
falseeven though
cp's
xand
yvalues matched those of the point in the collection. Thus it may seem like a violation of the LSP, because you can't use a
ColoredPointhere where a
Pointis expected. We believe this is the wrong interpretation, though, because the LSP doesn't require that a subclass behaves identically to its superclass, just that it behaves in a way that fulfills the contract of its superclass.
The problem with writing an
equalsmethod that compares run-time classes is not that it violates the LSP, but that it doesn't give you a way to create a subclass whose instances can equal superclass instances. For example, had we used the run-time class technique in the previous example, “
coll.contains(pAnon)” would have returned
false, and that's not what we wanted. By contrast, we really did want “
coll.contains(cp)” to return
false, because by overriding
equalsin
ColoredPoint, we were basically saying that an indigo-colored point at coordinates (1, 2) is not the same thing as an uncolored point at (1, 2). Thus, in the previous example we were able to pass two different
Pointsubclass instances to the collection's
containsmethod, and we got back two different answers, both correct.
相关文章推荐
- How to Write an Equality Method in Java(之三)Java中如何写equals()方法
- How to solve "java.lang.VerifyError: Expecting a stackmap frame at branch target 6 in method"
- How to read and write JSON files in Java(Gson)
- JAVA错误:Cannot refer to a non-final variable * inside an inner class defined in a different method
- A good blog about how to write an Hadoop MapReduce program in Python
- Java - How to write Thread-Safe Code in Java
- All-In-One Code Framework(AIO): 如何使用C#编写进程外的COM组件 (How to write an out-of-proc COM server in C#)
- java 更换皮肤问题Cannot refer to a non-final variable inside an inner class defined in a different method
- How to implement an ArrayList structure in Java - Tutorial
- An ffmpeg and SDL Tutorial or How to Write a Video Player in Less Than 1000 Lines
- How to Check if an Array Contains a Value in Java Efficiently?---reference
- How to call getClass() from a static method in Java?
- An ffmpeg and SDL Tutorial or How to Write a Video Player in Less Than 1000 Lines
- How to Check if an Array Contains a Value in Java Efficiently?
- In Java, how do I read/convert an InputStream to a String? - Stack Overflow
- How to Check if an Array Contains a Value in Java Efficiently?
- JAVA错误:Cannot refer to a non-final variable * inside an inner class defined in a different method
- JAVA错误:Cannot refer to a non-final variable * inside an inner class defined in a different method
- how to increase an regular array length in java?
- an oracle article in high level to descibe how to archtichre operator JAVA relevet project