Java memory leak when using an array - java

wanted to confirm my assumption. I am looking at code from another developer who was using an array (not linkedhashset/sorted collection etc) and trying to keep it sorted based on insertion but also keeping it of fixed size. The logic for keeping it of fixed size was to remove the oldest item from the array. However, when removing the oldest item from the array, the object reference was not nulled out i.e. only the array index was written to with another object. I think this might let the old (not nulled out) object hang around in memory longer than needed (if not a memory leak altogether) unless I missed anything with scoping. Any thoughts (I am trying to confirm with a quick test and visualvm as well). Thanks in advance.
public class MemTest {
private TestBuffer testQuotes = new TestBuffer(10); //static inner class
public static void main(String[] args) {
System.out.println("Starting!");
MemTest memTest = new MemTest();
for (int j = 0; j < 10; j++) {
for (int i = 0; i < 2000000000; i++) {
memTest.testQuotes.push(1, 12.3);
}
try {
Thread.sleep(2000);
}
catch (InterruptedException e) {
System.out.println("exception:" + e);
}
}
}
private static class QuoteBuffer {
private Object[] keyArr;
private Price[] testArr;
public TestBuffer(int size) {
keyArr = new Object[size];
testArr = new Price[size];
}
public Price get(Object key) {
if (key != null) {
for (int i=0; i<keyArr.length; i++) {
if ( key.equals(keyArr[i]) )
return quoteArr[i];
}
}
return null;
}
private void _slideTestQuotes() {
Object prevKey = null;
Price prevQuote = null;
Object tempKey;
Price tempQuote;
for (int i=0; i<keyArr.length; i++) {
// slide key to the next index
tempKey = keyArr[i];
keyArr[i] = prevKey;
prevKey = tempKey;
// tempKey = null; //I am guessing uncommenting this should make a difference
// slide quote to the next index
tempQuote = quoteArr[i];
quoteArr[i] = prevQuote;
prevQuote = tempQuote;
// tempQuote= null; //I am guessing uncommenting this should make a difference
}
}
public void push(Object key, Double quote) {
_slideTestQuotes();
keyArr[0] = key;
quoteArr[0] = new Price(quote); //quote;
}
}
public class Price {
Double price;
Double a1;
Double a2;
Double a3;
Double a4;
Double a5;
Double a6;
Price(Double price) {
this.price = price;
this.a1 = price;
this.a2 = price;;
this.a3 = price;
this.a4 = price;
this.a5 = price;
this.a6 = price;
}
}

You don't need to actually set a reference to null to make it eligible for garbage collection. Consider the following snippet:
Double d = new Double(1.1); // (1)
d = new Double(2.2); // (2)
At line 2 the object handle "d" is assigned a new value. This means the original Double with value 1.1 is now no longer reachable in any way and is eligible for garbage collection. There is no need to specifically write "d = null" first.

The Java garbage collector will collect all objects which are not reachable any longer. You don't have to null out references;
ref = null;
ref = newRef;
and
ref = newRef;
have exactly the same effect and, if there are no other references to the object that ref was pointing to, will both cause the garbage collector to collect that object.
You only need to null out references when you want to throw away a reference to an object but do not assign a new value to the reference. In that case forgetting to null out the reference might indeed cause a memory leak, because there is still a reference to an object that you might not need anymore.

However, when removing the oldest item from the array, the object reference was not nulled out i.e. only the array index was written to with another object. - This is the key point. It doesn't matter whether you are setting the reference to null or setting it to some other object, the "original" object becomes unreachable if there are no more references to it.
Example :
arr[0] = new myObject();
MyObject my = arr[0];
arr[0]=null; // or arr[0] = new myObject(); makes no difference. Since The original MyObject is still reachable, it will not be considered for GC.
my=null; // or my=new MyObject() // now the original MyObject instance will be unreachable and hence ready for GC.

Related

Creating new objects from array to array

I'm trying to generate new unique objects from an array of all possible objects to another array. The idea is that I have 3 classes that implement Region class and they have their own methods. These 3 classes are in my ArrayList<Region> arr. I pick a random class and add it to ArrayList<Region> ALL_REGIONS in a for loop. The problem is that the object that is added from arr is not unique, they are the same. This ca be told by their name. Every Region must have it's unique name and other settings but they don't. So this is the code I have so far:
public void generateRegions(){
ArrayList<Region> arr = Regions.getAllRegions();
Random rnd = new Random();
String ntype;
int regcounter = 5;
int b;
for(int i = 0; i < regcounter; i++){
ALL_REGIONS.add(arr.get(rnd.nextInt(arr.size())));
ntype = "n" + ALL_REGIONS.get(i).getType();
b = rnd.nextInt(Regions.getNtypeSize(ntype));
UI.print("b: " + b);
ALL_REGIONS.get(i).setName(Regions.getArrayName(ntype, b));
}
}
public static ArrayList<Region> getAllRegions(){
ArrayList<Region> arr = new ArrayList<Region>();
arr.add(new Highlands());
arr.add(new Cave());
arr.add(new Oasis());
return arr;
}
getArrayName returns a String name of the Region from an array and getNtypeSize returns an int, size of the array String[] that contatins all names which is not really important just now.
So.. how can I have every Cave, every Oasis unique/as a separate object?
**EDIT: ** Requested getArrayName() and getNtypeSize() methods are below:
public static String getArrayName(String ntype, int t) {
String ans = null;
if(ntype.equals("ncave")){
if(t<=ncaveSize)
ans = ncave[t];
}else if(ntype.equals("noasis")){
if(t<=noasisSize)
ans = noasis[t];
}else if(ntype.equals("nhighlands")){
if(t<=noasisSize)
ans = nhighlands[t];
}
//Can happen when t is bigger then ntype size or
// if ntype string is wrong
if(ans == null){
UI.printerr("getArrayNames: ans is empty/null");
}
UI.printerr(ans);
return ans;
}
public static int getNtypeSize(String ntype){
int ans = 0;
if(ntype.equals("ncave")){
ans = ncaveSize;
}else if(ntype.equals("noasis")){
ans = noasisSize;
}else if(ntype.equals("nhighlands")){
ans = nhighlandsSize;
}else
UI.printerr("getNtypeSize: returned 0 as an error");
return ans;
}
The issue is in this line:
ALL_REGIONS.add(arr.get(rnd.nextInt(arr.size())));
Here, you're not adding a new object to ALL_REGIONS. Rather, each time you're adding a reference to an object in 'arr'.
For example, each time rnd.nextInt(arr.size()) returns 2, you would add a reference to arr[2] to ALL_REGIONS. Thus, effectively, each entry in ALL_REGIONS refers to one of the objects in arr. (In this specific example, one of 3 objects you added in getAllRegions())
Effectively, this means that every Highlands object reference in ALL_REGIONS points to the same object => arr[0]
Similarly, every Cave reference in ALL_REGIONS points to arr[1] and every Oasis reference points to arr[2]
Something along this line should fix the issue:
Region reg = arr.get(rnd.nextInt(arr.size()))
ALL_REGIONS.add(reg.clone()); // this is just meant to be a sort of pseudo-code. Use a clone() method to create a new copy of the object and that copy to ALL_REGIONS.
If I got it right? You want to cast back to the type of the original object. It is plenty easy to do so, you will use some of the Java Polymorphism concepts.
You will use a function called InstanceOf like this
Region ob = arr[0];
if (ob instanceof Highlands)
Highlands newOb = (Highlands) ob;

Java multi-thread scalability issue

more updates
As is explained in the selected answer, the problem is in JVM's garbage collection algorithm.
JVM uses card marking algorithm to keep track of modified references in object fields. For each reference assignment to a field, it marks an associated bit in the card to be true -- this causes a false-sharing hence blocks scaling. The details are well described in this article: https://blogs.oracle.com/dave/entry/false_sharing_induced_by_card
The option -XX:+UseCondCardMark (in Java 1.7u40 and up) mitigates the problem, and makes it scale almost perfectly.
updates
I found out (hinted from Park Eung-ju) that assigning an object into a field variable makes the difference. If I remove the assignment, it scales perfectly.
I think probably it has something to do with Java memory model -- such as, an object reference must point to a valid address before it gets visible, but I am not completely sure. Both double and Object reference (likely) have 8 bytes size on 64 bit machine, so it seems to me that assigning a double value and an Object reference should be the same in terms of synchronization.
Anyone has a reasonable explanation?
Here I have a weird Java multi-threading scalability problem.
My code simply iterates over an array (using the visitor pattern) to compute simple floating-point operations and assign the result to another array. There is no data dependency, nor synchronization, so it should scale linearly (2x faster with 2 threads, 4x faster with 4 threads).
When primitive (double) array is used, it scales very well. When object type (e.g. String) array is used, it doesn't scale at all (even though the value of the String array is not used at all...)
Here's the entire source code:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.CyclicBarrier;
class Table1 {
public static final int SIZE1=200000000;
public static final boolean OBJ_PARAM;
static {
String type=System.getProperty("arg.type");
if ("double".equalsIgnoreCase(type)) {
System.out.println("Using primitive (double) type arg");
OBJ_PARAM = false;
} else {
System.out.println("Using object type arg");
OBJ_PARAM = true;
}
}
byte[] filled;
int[] ivals;
String[] strs;
Table1(int size) {
filled = new byte[size];
ivals = new int[size];
strs = new String[size];
Arrays.fill(filled, (byte)1);
Arrays.fill(ivals, 42);
Arrays.fill(strs, "Strs");
}
public boolean iterate_range(int from, int to, MyVisitor v) {
for (int i=from; i<to; i++) {
if (filled[i]==1) {
// XXX: Here we are passing double or String argument
if (OBJ_PARAM) v.visit_obj(i, strs[i]);
else v.visit(i, ivals[i]);
}
}
return true;
}
}
class HeadTable {
byte[] filled;
double[] dvals;
boolean isEmpty;
HeadTable(int size) {
filled = new byte[size];
dvals = new double[size];
Arrays.fill(filled, (byte)0);
isEmpty = true;
}
public boolean contains(int i, double d) {
if (filled[i]==0) return false;
if (dvals[i]==d) return true;
return false;
}
public boolean contains(int i) {
if (filled[i]==0) return false;
return true;
}
public double groupby(int i) {
assert filled[i]==1;
return dvals[i];
}
public boolean insert(int i, double d) {
if (filled[i]==1 && contains(i,d)) return false;
if (isEmpty) isEmpty=false;
filled[i]=1;
dvals[i] = d;
return true;
}
public boolean update(int i, double d) {
assert filled[i]==1;
dvals[i]=d;
return true;
}
}
class MyVisitor {
public static final int NUM=128;
int[] range = new int[2];
Table1 table1;
HeadTable head;
double diff=0;
int i;
int iv;
String sv;
MyVisitor(Table1 _table1, HeadTable _head, int id) {
table1 = _table1;
head = _head;
int elems=Table1.SIZE1/NUM;
range[0] = elems*id;
range[1] = elems*(id+1);
}
public void run() {
table1.iterate_range(range[0], range[1], this);
}
//YYY 1: with double argument, this function is called
public boolean visit(int _i, int _v) {
i = _i;
iv = _v;
insertDiff();
return true;
}
//YYY 2: with String argument, this function is called
public boolean visit_obj(int _i, Object _v) {
i = _i;
iv = 42;
sv = (String)_v;
insertDiff();
return true;
}
public boolean insertDiff() {
if (!head.contains(i)) {
head.insert(i, diff);
return true;
}
double old = head.groupby(i);
double newval=Math.min(old, diff);
head.update(i, newval);
head.insert(i, diff);
return true;
}
}
public class ParTest1 {
public static int THREAD_NUM=4;
public static void main(String[] args) throws Exception {
if (args.length>0) {
THREAD_NUM = Integer.parseInt(args[0]);
System.out.println("Setting THREAD_NUM:"+THREAD_NUM);
}
Table1 table1 = new Table1(Table1.SIZE1);
HeadTable head = new HeadTable(Table1.SIZE1);
MyVisitor[] visitors = new MyVisitor[MyVisitor.NUM];
for (int i=0; i<visitors.length; i++) {
visitors[i] = new MyVisitor(table1, head, i);
}
int taskPerThread = visitors.length / THREAD_NUM;
MyThread[] threads = new MyThread[THREAD_NUM];
CyclicBarrier barrier = new CyclicBarrier(THREAD_NUM+1);
for (int i=0; i<THREAD_NUM; i++) {
threads[i] = new MyThread(barrier);
for (int j=taskPerThread*i; j<taskPerThread*(i+1); j++) {
if (j>=visitors.length) break;
threads[i].addVisitors(visitors[j]);
}
}
Runtime r=Runtime.getRuntime();
System.out.println("Force running gc");
r.gc(); // running GC here (excluding GC effect)
System.out.println("Running gc done");
// not measuring 1st run (excluding JIT compilation effect)
for (int i=0; i<THREAD_NUM; i++) {
threads[i].start();
}
barrier.await();
for (int i=0; i<10; i++) {
MyThread.start = true;
long s=System.currentTimeMillis();
barrier.await();
long e=System.currentTimeMillis();
System.out.println("Iter "+i+" Exec time:"+(e-s)/1000.0+"s");
}
}
}
class MyThread extends Thread {
static volatile boolean start=true;
static int tid=0;
int id=0;
ArrayList<MyVisitor> tasks;
CyclicBarrier barrier;
public MyThread(CyclicBarrier _barrier) {
super("MyThread"+(tid++));
barrier = _barrier;
id=tid;
tasks = new ArrayList(256);
}
void addVisitors(MyVisitor v) {
tasks.add(v);
}
public void run() {
while (true) {
while (!start) { ; }
for (int i=0; i<tasks.size(); i++) {
MyVisitor v=tasks.get(i);
v.run();
}
start = false;
try { barrier.await();}
catch (InterruptedException e) { break; }
catch (Exception e) { throw new RuntimeException(e); }
}
}
}
The Java code can be compiled with no dependency, and you can run it with the following command:
java -Darg.type=double -server ParTest1 2
You pass the number of worker threads as an argument (the above uses 2 threads).
After setting up the arrays (that is excluded from the measured time), it does a same operation for 10 times, printing out the execution time at each iteration.
With the above option, it uses double array, and it scales very well with 1,2,4 threads (i.e. the execution time reduces to 1/2, and 1/4), but
java -Darg.type=Object -server ParTest1 2
With this option, it uses Object (String) array, and it doesn't scale at all!
I measured the GC time, but it was insignificant (and I also forced running GC before measuring times). I have tested with Java 6 (updates 43) and Java 7 (updates 51), but it's the same.
The code has comments with XXX and YYY describing the difference when arg.type=double or arg.type=Object option is used.
Can you figure out what is going on with the String-type argument passing here?
HotSpot VM generates following assemblies for reference type putfield bytecode.
mov ref, OFFSET_OF_THE_FIELD(this) <- this puts the new value for field.
mov this, REGISTER_A
shr 0x9, REGISTER_A
movabs OFFSET_X, REGISTER_B
mov %r12b, (REGISTER_A, REGISTER_B, 1)
putfield operation is completed in 1 instruction.
but there are more instructions following.
They are "Card Marking" instructions. (http://www.ibm.com/developerworks/library/j-jtp11253/)
Writing reference field to every objects in a card (512 bytes), will store a value in a same memory address.
And I guess, store to same memory address from multiple cores mess up with cache and pipelines.
just add
byte[] garbage = new byte[600];
to MyVisitor definition.
then every MyVisitor instances will be spaced enough not to share card marking bit, you will see the program scales.
This is not a complete answer but may provide a hint for you.
I have changed your code
Table1(int size) {
filled = new byte[size];
ivals = new int[size];
strs = new String[size];
Arrays.fill(filled, (byte)1);
Arrays.fill(ivals, 42);
Arrays.fill(strs, "Strs");
}
to
Table1(int size) {
filled = new byte[size];
ivals = new int[size];
strs = new String[size];
Arrays.fill(filled, (byte)1);
Arrays.fill(ivals, 42);
Arrays.fill(strs, new String("Strs"));
}
after this change, the running time with 4 threads with object type array reduced.
According to http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7
For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.
Writes and reads of volatile long and double values are always atomic.
Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.
Assigning references are always atomic,
and double is not atomic except when it is defined as volatile.
The problem is sv can be seen by other threads and its assignment is atomic.
Therefore, wrapping visitor's member variables (i, iv, sv) using ThreadLocal will solve the problem.
"sv = (String)_v;" makes the difference. I also confirmed that the type casting is not the factor. Just accessing _v can't make the difference. Assigning some value to sv field makes the difference. But I can't explain why.

Why does my Java deep copy code tell me "Assignment requires a deep, not shallow copy"?

I am trying to submit this sound manipulation program, but I keep getting this error:
Assignment requires a deep, not shallow copy. Are you just copying pointers, or copying the contents of the array?...
I think I am doing a shallow copy on my public void set(double[] mySamples), but I am new to Java and really don't know what to do.
public class Sound
{
private double[] temp;
private double[] samples;
public Sound() {
samples = null;
}
public Sound(Sound s) {
int j = 0;
double[] source = s.samples;
double temp[] = new double[source.length];
for(int i = 0; i < source.length; i++) {
temp[j] = source[i];
}
samples = temp;
}
public double[] get() {
return samples;
}
public void set(double[] mySamples) {
if (mySamples == null) {
throw new IllegalArgumentException("samples cannot be null");
} else {
samples = mySamples;
}
}
public void increaseVol(double percent) {
double [] result = new double[samples.length];
for (int i = 0; i < samples.length; i++) {
double reduce = samples[i] * percent;
result[i] = samples[i] + reduce;
}
samples = result;
}
public void wavSave(java.lang.String fileName) {
WavIO.write(fileName, samples);
}
}
I think you need to make two changes -
1 Your constructor is doing something weird with those samples (and might be causing your error by itself) - I'm not sure what you were trying to do, but I think you should just defer to set.
public Sound(Sound s) {
int j = 0;
set(s.samples);
}
2 Just copy the samples array in set, and store it.
public void set(double[] mySamples) {
if (mySamples == null) {
throw new IllegalArgumentException(
"samples cannot be null");
} else {
samples = new double[mySamples.length];
for (int i = 0; i < mySamples.length; i++) {
samples[i] = mySamples[i];
}
}
}
Yes, you're doing "shallow" copies. Arrays in Java are like pointers, in that if you return or set an array, it's just a pointer (really, reference) to that array. With your current code it's pretty easy for different Sound objects to reference the same underlying array, which is probably not what you want.
In the set method, you need to copy the contents of the array instead of just copying the reference to that array.
You do copy the contents of the array properly in the Sound(Sound s) constructor. However, you don't need to manually allocate a new array and write a for-loop to copy the values. Just call java.util.Arrays.copyOf() to copy it. You can use a similar technique in the set method.
The get method also has the issue where it's returning a reference to the array from inside your Sound object. The caller now has a reference to the Sound object's internal state and can manipulate it. That might or might not be what you want.
Primitive arrays and arrays of autoboxed reference types (from primitives) are final and immutable. Therefore, they are ideal candidates for the clone() method.
You can edit this code:
public void set(double[] mySamples) {
if (mySamples == null) {
throw new IllegalArgumentException(
"samples cannot be null");
} else {
samples = new double[mySamples.length];
for (int i = 0; i < mySamples.length; i++) {
samples[i] = mySamples[i];
}
}
}
into this with the same functionality and superior performance:
public void set(double[] mySamples) {
if (mySamples == null || mySamples.length < 1) {
throw new IllegalArgumentException(
"samples cannot be null or empty");
}
samples = mySamples.clone();
}
This works safely because doubles are immutable and primitive and so a shallow copy is the same as a deep copy in this case. It's also safe because double cannot be subclassed and so no mischief is possible with clone().
Moreover, the performance of clone() on arrays is much, much faster than iterating over an array in code and copying element by element.
TL;DR: Making a copy of an array of primitives (or an array of the corresponding autoboxed reference types that correspond to a primitive) is quite possibly the only case in which it is ideal to use the clone() method.

null pointer exception in array

I am getting a null exception error from this segment of code and I am not sure what causing it. The array itemcatalog has being populate for i =0 to 8. I am new to java so any assistance will be greatly appreciated. The error message points to the line of the while statement. Thanks
public class ItemCatalog {
private static ItemCatalog instance = new ItemCatalog();
private Item itemCatalog[] = new Item[9];
private ItemCatalog(){
};
public static synchronized ItemCatalog getInstance() {
return instance;
}
public void populateCatalog()
{
itemCatalog[0] = new Item("bb","Baked Beans",new BigDecimal("0.35"));
itemCatalog[1] = new Item("cf","Cornflakes",new BigDecimal("1.00"));
itemCatalog[2] = new Item("s0","Sugar",new BigDecimal("0.50"));
itemCatalog[3] = new Item("tb","Tea Bags",new BigDecimal("1.15"));
itemCatalog[4] = new Item("ic","Instant Coffee",new BigDecimal("2.50"));
itemCatalog[5] = new Item("b0","Bread",new BigDecimal("0.50"));
itemCatalog[6] = new Item("s0","Sausages",new BigDecimal("1.30"));
itemCatalog[7] = new Item("e0","Eggs",new BigDecimal("0.75"));
itemCatalog[8] = new Item("m0","Milk",new BigDecimal("0.65"));
}
public BigDecimal getPrice(String itemCode)
{
int i = 0;
while (!itemCode.equals(itemCatalog[i].getItemCode()))
{
i++;
}
BigDecimal itemPrice = itemCatalog[i].getItemprice();
return itemPrice;
}
}
I solved the issue. I was populating the catalog in the main class which was giving the null exception error. I instantiate it in the jframe instead and it works. The follow code solved the issue, but is this the best place to populate the catalog?
private void saleButtonActionPerformed(java.awt.event.ActionEvent evt) {
String itemCode = this.itemCodeinput.getText();
int itemQuantity =Integer.parseInt(this.itemQuantityinput.getText());
ItemCatalog catalog = ItemCatalog.getInstance();
catalog.populateCatalog();
BigDecimal price = catalog.getPrice(itemCode);
itemCostoutput.setText(price.toString());
}
If your itemCode doesn't match any entries in your itemCatalog, then eventually
while (!itemCode.equals(itemCatalog[i].getItemCode()))
{
i++;
}
will increment i to 11, in which case itemCatalog[11] is either empty or out of bounds.
If addition, you should use a for loop to iterate through the itemCatalog:
for (int i = 0; i < itemCatalog.length; i++) {
if (itemCode.equals(itemCatalog[i].getItemCode()) {
return (BigDecimal) itemCatalog[i].getItemprice();
}
}
return null // you can change this from null to a flag
// value for not finding the item.
From the comments, it's clear the design isn't sound.
Here's a possible solution :
public BigDecimal getPrice(String itemCode) {
for (int i=0; i<itemCatalog.length; i++) { // not going outside the array
if (itemCatalog[i].getItemCode().equals(itemCode)) { // inversing the test to avoid npe if itemCode is null
return itemCatalog[i].getItemprice();
}
}
return null; // default value
}
This supposes your array is correctly filled with itemCatalogs having an itemCode.
How do you end your loop?
Seems that the loop will keep going until i is 10. Then your will have exceeded the limit.
Unless this is a uni assignment where you have to use arrays, I'd also suggest using a map, rather than an array. This way your lookup will be the same time, whether your collection has 100,000 entries or 10.
You will also reduce risk of NPE or ArrayOutOfBounds exception
See http://docs.oracle.com/javase/1.4.2/docs/api/java/util/HashMap.html
When adding the object use the item code as the key. Then lookup by the key.
The cost of using a map is increased memory usage.

Returning searched results in an array in Java without ArrayList

I started down this path of implementing a simple search in an array for a hw assignment without knowing we could use ArrayList. I realized it had some bugs in it and figured I'd still try to know what my bug is before using ArrayList. I basically have a class where I can add, remove, or search from an array.
public class AcmeLoanManager
{
public void addLoan(Loan h)
{
int loanId = h.getLoanId();
loanArray[loanId - 1] = h;
}
public Loan[] getAllLoans()
{
return loanArray;
}
public Loan[] findLoans(Person p)
{
//Loan[] searchedLoanArray = new Loan[10]; // create new array to hold searched values
searchedLoanArray = this.getAllLoans(); // fill new array with all values
// Looks through only valid array values, and if Person p does not match using Person.equals()
// sets that value to null.
for (int i = 0; i < searchedLoanArray.length; i++) {
if (searchedLoanArray[i] != null) {
if (!(searchedLoanArray[i].getClient().equals(p))) {
searchedLoanArray[i] = null;
}
}
}
return searchedLoanArray;
}
public void removeLoan(int loanId)
{
loanArray[loanId - 1] = null;
}
private Loan[] loanArray = new Loan[10];
private Loan[] searchedLoanArray = new Loan[10]; // separate array to hold values returned from search
}
When testing this, I thought it worked, but I think I am overwriting my member variable after I do a search. I initially thought that I could create a new Loan[] in the method and return that, but that didn't seem to work. Then I thought I could have two arrays. One that would not change, and the other just for the searched values. But I think I am not understanding something, like shallow vs deep copying???....
The return value from getAllLoans is overwriting the searchedLoanArray reference, which means that both loanArray and searchedLoanArray are pointing at the same underlying array. Try making searchedLoanArray a local variable, and then use Arrays.copyOf. If you're trying not to use standard functions for your homework, manually create a new Loan array of the same size as loanArray, and then loop and copy the values over.
your searchloanarray and loanarray point to the same array. doing this
private Loan[] searchedLoanArray = new Loan[10]
does nothing as you never use that new Loan[10]
this is the key to your problem
searchedLoanArray = this.getAllLoans()
that just points searchedLoanArray at loanArray
You could rewrite it like this:
public Loan[] findLoans(Person p)
{
Loan[] allLoans = this.getAllLoans();
System.arraycopy(allLoans, searchedLoanArray, 0, 0, allLoans.length); // fill new array with all values
// remainder of method the same
}
But as it stands, the code still has some problems:
The maximum number of loans is fixed to the size of the array. You will avoid this problem when you switch to List<Loan>.
Using the id as an index means that your ids must be carefully generated. If IDs come from a database, you may find that the list tries to allocate a huge amount of memory to size itself to match the Id. You would be better using a Map, then the size of the map is based on the number of loans, rather than their IDs.
As the number of people and loans increase, the search time will also increase. You can reduce search time to a constant (irrespective of how many People) by using a Map>, which allows quick lookup of the loans associated just with that person.
Here's a version with these changes:
class AcmeLoanManager
{
public void addLoan(Loan l)
{
Person client = l.getClient();
List<Loan> loans = clientLoans.get(l);
if (loans==null)
{
loans = new ArrayList();
clientLoans.put(client, loans);
}
loans.add(l);
allLoans.put(l.getLoanId(), l);
}
public void removeLoan(int loanId)
{
Loan l = loans.remove(loanId);
clientLoans.remove(loan);
}
public Collection<Loan> getAllLoans()
{
return loans.values();
}
public List<Loan> findLoans(Person p)
{
List<Loan> loans = clientLoans.get(p);
if (loans==null)
loans = Collections.emptyList();
return loans;
}
private Map<Integer,Loan> allLoans = new HashMap<Integer,Loan>();
private Map<Person, List<Loan>> clientLoans = new HashMap<Person,List<Loan>>();
}
I hope this helps!
What I would do is loop through the values and reassign each value to the new variable. Alternatively, you could use "deep copy" technique as described here in Javaworld: http://www.javaworld.com/javaworld/javatips/jw-javatip76.html

Categories