EECS1022 Winter 2018 Additional Notes Tracing, Collector, and CollectorTester Chen-Wei Wang Contents 1 Class 1 2 Class Collector 2 Class CollectorTester 7 1 Class 1 class { 2 double ; double ; 4 (double n, double n) { 5 = n; 6 = n; 7 } 8 } Figure 1: Template for Two-Dimensional s Figure 1 (page 1) shows the complete definition of class, which is a template for entities (i.e., ) on a two-dimensional plane. The common attributes (Lines 2 and ) of all are the -coordinate and -coordinate: 2 double ; double ; To create an instance of the above class, ou need to make a call to its (in this case, the onl) constructor (Lines 4 to 7) b giving two argument values whose tpes match the tpes of the constructor s two parameters. 4 (double n, double n) { 5 = n; 6 = n; 7 } For eample, the call (, 4) is valid, because the first argument value matches the tpe of the first parameter, and the second argument value should 4 matches the tpe of the second parameter. 1
2 Class Collector 1 class Collector { 2 [] ; int nop; /* number of */ 4 Collector() { 5 = new [100]; 6 } 7 void add(double, double ) { 8 [nop] = new (, ); 9 nop++; 10 } 11 [] getsinquadranti() { 12 [] ps = new [nop]; 1 int count = 0; /* number of in Quadrant I */ 14 for(int i = 0; i < nop; i ++) { 15 p = [i]; 16 if(p. > 0 && p. > 0) { 17 ps[count] = p; 18 count ++; 19 } 20 } 21 [] q1s = new [count]; 22 /* ps contains if count < nop */ 2 for(int i = 0; i < count; i ++) { 24 q1s[i] = ps[i] 25 } 26 return q1s; 27 } 28 } Figure 2: Template for Entities which Collect s Attributes of Collector : Figure 2 (page 2) shows the complete definition of class Collector, which is a template for entities (i.e., collectors of ), each of which collecting a list of on the two-dimensional plane. The common attributes (Lines 2 and ) of all point collectors are: Line 2: An arra of, which is of tpe []. Declaring the attribute [] ; means that at runtime, each point collector has an arra named, where each slot stores the address (which ma be ) of some object. Line : An integer counter named nop (standing for number of ), which serves two purposes: 1. Records how man have been collected (i.e., added to the arra) so far. Notice that the value of nop should start with 0, and get incremented b one as each new object is collected, until it reaches.length. 2. Indicates where in (i.e., an inde value between 0 and.length - 1). When the value of nop reaches its maimum.length, meaning that all slots of the arra 2
are occupied b addresses of objects, this maimum value also means that storing a net object is not possible (because the value.length is out of the inde bounds for arra ). Constructor of Collector : To create an instance of the above Collector class, ou need to make a call to its (onl) constructor (Lines 4 to 6) b giving zero argument values. For eample, consider the the following line Collector pc = new Collector(); where Step 1: The RHS (i.e., new Collector()) creates a new object of tpe PersonCollector. Step 2: The LHS (i.e., Collector pc) declares a variable pc which, at runtime, stores onl an address of some object. Step : The middle equal sign (=) assigns (i.e., stores) the address of the new object (created in Step 1) to the address placeholder (declared in Step 2). Here is a visual summar of the effect of eecuting the above three steps (where we use an addition dashed arrow to make it eplicit that the value of nop can be seen as pointing to a position in arra pc.): Object Structure After Initializing the Collector pc nop == 0 Collector 0 1 2 0 where The path pc.nop (where pc is the contet object) brings us to the value of attribute nop of the specific Collector instance pc. The path pc.ponts (where pc is the contet object) brings us to the value of attribute. That is, pc. denotes the address of an arra object, whose each slot stores the address of some object. Wh is it the case that pc.nop has the value 0 and pc. has the value of an arra whose each slot stores the (i.e., unknown address) value? Inspect the definition of the constructor in class Collector (Lines 5 to 6 in Figure 2, page 2): 4 Collector() { 5 = new [100]; 6 }
When each instance of tpe Collector is created, it is critical to initialize the arra that it possesses (so that subsequent method calls on each Collector object can access/modif its arra without triggering a NullerEception). That is, here is a wrong implementation of the constructor of Collector: Collector() { /* wrong implementation of the constructor */ /* arra is implicitl initialized to */ } where the arra is not eplicitl initialized to certain length, so it is implicitl assigned to the default value of reference tpes:. Here is a visual summar of the effect of the above wrong implementation of constructor: Collector 0 Wh is it wrong? Tr access contents of the arra, for eample, calling pc.[0], then it will trigger a NullerEception, because pc. is and calling pc.[0] is equivalent to calling.[0], which simpl does not make sense. Line 5 initializes as an arra of size 100 (just for this eample as the assumed maimum number of that each Collectr ma collect). We onl initializes the arra with a fied size, without storing in an of its slots addresses of (simpl because initiall no have been collected et), so all slots in store values onl. Since there is no eplicit assignments that given an initial value to the attribute nop, it is initialized (implicitl) with the default value of integers: 0. Remark: The above digram shows a sensible configuration of objects: 1) pc..length is 100, meaning that we can in the future store at most 100 objects of tpe into it; and 2) pc.nop is 0, meaning that so far we have collected zero, and that the net (i.e., ver first) point object to be stored will be stored at pc.[0]. Mutator Method add of Collector : The mutator method void add(double, double ) (Lines 7 to 10, page 2) defines how a new point with coordinate values and can be added to the point collector. 7 void add(double, double ) { 8 [nop] = new (, ); /* store the new point at inde nop of */ 9 nop++; /* increment # of collected, update inde for storing net point */ 10 } There are two steps involved: Step 1 (Line 8): 8 [nop] = new (, ); Step 1.1: The RHS (i.e., new (, )) creates a new object, b calling the constructor of : the parameter values and of method add are used as argument values for the call to the constructor call (, ). Step 1.2: The LHS (i.e., [nop]) brings us to the specific slot at inde nop in arra. Step 1.: The middle equal sign (=) assigns/stores the address of the new object (created in Step 1.1) at inde nop in arra. Remember that one of the purposes of counter nop is that it indicates where a new to be collected should be stored. 4
Step 2 (Line 9): 9 nop++; Now that from Step 1 we alread store a new object at inde nop of arra, this line updates (b incrementing) the value of nop so that it still serves its purpose well. For eample, if in Step 1 the value of nop is 2, that means so far the slots [0], [1], [2] are all occupied b addresses of some object. So to make sure that value of nop correctl records the number of objects collected so far (which should be rather than 2), and that value of nop indicates the position for storing the net new to be collected (which should be [] rather than [2]), we increment the value of nop b 1. Accessor Method getsinquadranti of Collector : The accessor method [] getsinquadranti() (Lines 11 to 27, page 2) defines, out of the objects collected so far, how to return an arra containing onl those which are located at the first quadrant (i.e., those whose both coordinates are positive). First of all, we need to understand that simpl returning (which is also of tpe []) is not acceptable, because: 1) if nop <.length, there are.length - nop slots storing values; and 2) even if nop ==.length, not necessaril all collected so far are all located in the first quadrant. Therefore, this accessor method getsinquadranti is supposed to return an arra of objects, whose length corresponds to eactl the number of collected that are located in the first quadrant. There are three steps involved: Step 1 (Lines 12 to 20): 12 [] ps = new [nop]; 1 int count = 0; /* number of in Quadrant I (Q1) */ 14 for(int i = 0; i < nop; i ++) { 15 p = [i]; /* store the current point under consideration into p */ 16 if(p. > 0 && p. > 0) { /* the point being considered is in 1st quadrant */ 17 ps[count] = p; /* cop what s stored in p to slot at inde count of ps */ 18 count ++; /* increment # of Q1, update inde for storing net Q1 point */ 19 } 20 } Line 12 initializes an arra of size nop, indicating that might be up to nop that we have collected are located in the first quadrant. Wh? Given that so far we have collected nop (rather than.length) objects in the point collector, the maimum number of that are located in the first quadrant is simpl nop (i.e., when all collected are located in the first quadrant). As we iterate through the arra (slots [0], [1],..., [nop - 1]) some collected might be located in the first quadrant, and some might not be. As a result, we need a separate counter count that records eactl how man are located in the first quadrant. How count is used (Lines 17 and 18, Figure 2, page 2) to keep track of how man collected objects are located in the first quadrant is analogous to how nop is used (Lines 8 and 9, Figure 2, page 2) to keep track of how man objects are collected so far. Step 2 (Lines 21 to 25): 21 [] q1s = new [count]; 22 /* ps contains if count < nop */ 2 for(int i = 0; i < count; i ++) { 24 q1s[i] = ps[i] 25 } 5
From Step 1, we have updated the integer variable count so that it records the number of collected that are located in the first quadrant, and that the count slots ps[0], ps[1],..., ps[count - 1] store addresses of such first-quadrant. Notice that it is the case that count <= nop: Case 1) if count == nop, then all slots of arra ps (whose length is nop as can be seen in Line 12 in Figure 2, page 2) are occupied; Case 2) if count < nop, then at at least one slots of arra ps (i.e., ps[count], ps[count + 1],..., ps[nop]) store the (simpl because not all collected happen to be in the first quadrant). If the above Case 2 is true, meaning that there are values in arra ps, then we cannot simpl return ps as the return value for method getsinquadranti. Therefore: In Line 21 we initialize an arra q1s whose length corresponds the eact number of first-quadrant (i.e., the value of count). In Lines 2 to 25, we cop addresses of all such first-quadrant (stored in ps[0], ps[1],..., ps[count - 1]) to their corresponding positions in arra q1s to be returned: q1s[0], q1s[1],..., q1s[count - 1]. Step (Line 26): 26 return q1s; Now that the previous two steps store addresses of all first-quadrant (no more, and no less) into the arra q1s, we return it as the return value of method getsinquadranti (whose return tpe is []). 6
Class CollectorTester After understanding the descriptions of the attributes and methods of the classes (Section 1) and Collector (Section 2) from the previous two sections, let us learn how objects of these two classes ma interact. 1 class CollectorTester { 2 public static void main(string[] args) { Collector pc = new Collector(); 4 Sstem.out.println(pc.nop); /* 0 */ 5 pc.add(, 4); 6 Sstem.out.println(pc.nop); /* 1 */ 7 pc.add(-, 4); 8 Sstem.out.println(pc.nop); /* 2 */ 9 pc.add(-, -4); 10 Sstem.out.println(pc.nop); /* */ 11 pc.add(, -4); 12 Sstem.out.println(pc.nop); /* 4 */ 1 [] ps = pc.getsinquadranti(); 14 Sstem.out.println(ps.length); /* 1 */ 15 Sstem.out.println("(" + ps[0]. + ", " + ps[0]. + ")"); /* (, 4) */ 16 } 17 } Figure : Tester for Instances of and Tester Line of Tester : Collector pc = new Collector(); As eplained in the constructor of Collector (Section 2, page 2), the above line results in the following object structure: Object Structure After Initializing the Collector pc nop == 0 Collector 0 1 2 0 Therefore, eecuting the print statement Sstem.out.println(pc.nop) in Line 4 of Collector outputs 0 to the console. 7
Line 5 of Tester : pc.add(, 4); This method call has the contet object pc, declared as an address holder of some Collector object, and the mutator method void add(double, double ) (defined in Lines 7 to 10 in Figure 2, Section 2, page 2) is instantiated (b substituting b and b 4) as: void add(, 4) { /* current value of nop is 0 */ [nop] = new (, 4); nop++; /* current value of nop is 1 */ } Eecuting the above method call results in the following object structure: Object Structure After Adding the 1st to the Collector pc nop == 1 Collector 0 1 2 1.0 Therefore, eecuting the print statement Sstem.out.println(pc.nop) in Line 6 of Collector outputs 1 to the console. Line 7 of Tester : pc.add(-, 4); This method call has the contet object pc, declared as an address holder of some Collector object, and the mutator method void add(double, double ) (defined in Lines 7 to 10 in Figure 2, Section 2, page 2) is instantiated (b substituting b - and b 4) as: void add(, 4) { /* current value of nop is 1 */ [nop] = new (, 4); nop++; /* current value of nop is 2 */ } Eecuting the above method call results in the following object structure: 8
Object Structure After Adding the 2nd to the Collector pc nop == 2 Collector 0 1 2 2.0 -.0 Therefore, eecuting the print statement Sstem.out.println(pc.nop) in Line 8 of Collector outputs 2 to the console. Line 9 of Tester : pc.add(-, -4); This method call has the contet object pc, declared as an address holder of some Collector object, and the mutator method void add(double, double ) (defined in Lines 7 to 10 in Figure 2, Section 2, page 2) is instantiated (b substituting b - and b -4) as: void add(, 4) { /* current value of nop is 2 */ [nop] = new (, 4); nop++; /* current value of nop is */ } Eecuting the above method call results in the following object structure: Object Structure After Adding the rd to the Collector pc nop == Collector 0 1 2.0 -.0 -.0 - Therefore, eecuting the print statement Sstem.out.println(pc.nop) in Line 10 of Collector outputs to the console. 9
Line 11 of Tester : pc.add(, -4); This method call has the contet object pc, declared as an address holder of some Collector object, and the mutator method void add(double, double ) (defined in Lines 7 to 10 in Figure 2, Section 2, page 2) is instantiated (b substituting b and b -4) as: void add(, 4) { /* current value of nop is */ [nop] = new (, 4); nop++; /* current value of nop is 4 */ } Eecuting the above method call results in the following object structure: Object Structure After Adding the 4th to the Collector pc nop == 4 Collector 0 1 2 4.0 -.0 -.0.0 - - Therefore, eecuting the print statement Sstem.out.println(pc.nop) in Line 12 of Collector outputs 4 to the console. Pause and think: Up to this point, nop == 4 indicates that there have been four collected in the arra of the point collector object pc. We observe that out of these four collected, onl the stored at pc.[0] is located in the first quadrant. Therefore, we would epect the return value from a subsequent (accessor) method call pc.getsinquadranti() to be an arra of length 1. Line 1 of Tester : [] ps = pc.getsinquadranti(); This method call has the contet object pc, declared as an address holder of some Collector object, and the accessor method [] getsinquadranti() (defined in Lines 11 to 27 in Figure 2, Section 2, page 2) is eecuted (b substituting nop b its current value 4 and b the arra above where the first four slots are occupied) as: 10
Lines 12 to 20 of Collector : 12 [] ps = new [nop]; /* current value of nop is 4 */ 1 int count = 0; /* number of in Quadrant I */ 14 for(int i = 0; i < nop; i ++) { 15 p = [i]; 16 if(p. > 0 && p. > 0) { 17 ps[count] = p; 18 count ++; 19 } 20 } Right after Line 1, we initialize an arra ps of size nop (whose current value is 4, meaning that at most the four collected are all located in the first quadrant) and a counter count (whose job is to keep track of out of those collected so far, how man of them are located in the first quadrant). Here is a visual summar of the object structure after right after eecuting Line 1: nop == 4 Collector 0 1 2 4.0 -.0 -.0.0 - - ps 0 1 2 count == 0 Then, the for loop in Lines 14 to 20 is eecuted to iterate through pc.[0], pc.[1], pc.[2], and pc.[], and store whichever that is located in the first quadrant into slots in ps. Onl the first iteration (when i == 0) which inspects pc.[0] would pass the condition in Line 16, store the address of the point object (, 4) into ps, and update the count value accordingl; all later three iterations (when i is 1, 2, and ), the condition in Line 6 evaluates to false, so none of those three would be stored in ps and the value of count would not be incremented an further. Here is a visual summar of the object structure after right after eecuting Line 20: 11
nop == 4 Collector 0 1 2 4.0 -.0 -.0.0 - - ps 0 1 2 count == 1 It is important to notice that in Line 14, the value of nop (which is currentl 4), rather than the value of pc..length (which has remained 100 since it was first created), is used as the upper bound of the loop counter. What if i < pc..length was used instead as the sta condition of the for loop? Unfortunatel, when the value of loop counter i reaches 4 (which corresponds to the first, left-most inde at which pc. stores a value), we would still pass this (wrong) sta condition i < pc..length and enter the bod of the for loop. Consequentl, eecuting Line 15 assigns to variable p, and when the eecution reaches Line 16, eecuting p. and p. is equivalent to eecuting. and., which would trigger a NullerEception. Lines 21 to 25 of Collector : 21 [] q1s = new [count]; 22 /* ps contains if count < nop */ 2 for(int i = 0; i < count; i ++) { 24 q1s[i] = ps[i] 25 } From Lines 12 to 20, we have updated the values of count (which is now 1) and ps, so that arra ps now has 1 non- slot and (calculated through ps.length - count, where ps.length == nop && nop == 4) slots. We cannot simpl return arra ps as the return value for method getsinquadranti, as it contains slots. As a result, we eecute Lines 21 to 25 to initialize an arra q1s whose length corresponds to eactl those (indicated b the value of count) collected that are located in the first quadrant, and we simpl cop over the addresses of the first-quadrant to slots in arra q1s. 12
Figure 4 summarizes the object structure after right after eecuting Line 25: nop == 4 Collector 0 1 2 4.0 -.0 -.0.0 - - q1s ps 0 0 1 2 count == 1 Figure 4: Final Object Structure from Eecuting CollectorTester Again, it is important to notice that in Line 2, the value of count (which is currentl 1), rather than the value of nop (which is 4), is used as the upper bound of the loop counter. What if i < nop was used instead as the sta condition of the for loop? Unfortunatel, when the value of loop counter i reaches 1, we would still pass this (wrong) sta condition i < nop and enter the bod of the for loop. Consequentl, eecuting the epression q1s[i] in Line 24 would trigger an ArraIndeOutOfBoundEception because the arra q1s was created in Line 21 to have length count (which is 1). Line 26 of Collector : 26 return q1s; Up to now, there are three candidate objects (whose tpes all match the return tpe [] of the accessor method getsinquadranti) that can be the return values: 1. pc. This should not be returned, since it contains pc..length - nop slots that store values, and even for those slots that store addresses of objects, some of them are actuall not located in the first quadrant. 2. ps This should not be returned, since it contains nop - count slots that store value.. q1s This should be returned, since it contains eactl those collected that are located in the first quadrant, and there are no -slots in this arra. 1
Therefore, we return q1s (which stores the address of the arra of size 1) as the return value of accessor method getsinquadranti. This return value is stored, from Line 1 of CollectorTester (Figure, page 7), into variable ps. Consequentl: In Line 14 of CollectorTester, the print statement Sstem.out.println(ps.length) outputs 1 to the console. In Line 15 of CollectorTester, the print statement Sstem.out.println("(" + ps[0]. + ", " + ps[0]. + ")") outputs (, 4) to the console. 14