Download presentation
Presentation is loading. Please wait.
Published byWhitney Wilcox Modified over 8 years ago
1
Five-Minute Review 1.What are local/instance/class variables? What about constants? 2.What is an array? 3.How do we locally declare an array of 5 integers? 4.How is the above array stored? 5.What are multi-dimensional arrays? 1
2
Programming – Lecture 8 Objects and Memory (Chapter 7) Memory structure Allocation of memory to variables – Heap, Stack Recursion (for this only: Chapter 14) Primitive types vs. objects, wrapper classes, boxing/unboxing Linking objects together 2
3
Chapter 7—Objects and Memory The Art and Science of An Introduction to Computer Science ERIC S. ROBERTS Java Objects and Memory C H A P T E R 7 Yea, from the table of my memory I’ll wipe away all trivial fond records. —William Shakespeare, Hamlet, c. 1600 7.1 The structure of memory 7.2 The allocation of memory to variables 7.3 Primitive types vs. objects 7.4 Linking objects together
4
Numbers Decimal, binary, octal, hexadecimal notation 42 10 = 00101010 2 = 52 8 = 2A 16 4
5
The Structure of Memory The fundamental unit of memory inside a computer is called a bit, which is a contraction of the words binary digit. A bit can be in either of two states, usually denoted as 0 and 1. Numbers are stored in still larger units that consist of multiple bytes. The unit that represents the most common integer size on a particular hardware is called a word. Because machines have different architectures, the number of bytes in a word may vary from machine to machine. 00101010 The hardware structure of a computer combines individual bits into larger units. In most modern architectures, the smallest unit on which the hardware operates is a sequence of eight consecutive bits called a byte. The following diagram shows a byte containing a combination of 0s and 1s:
6
Binary Notation The rightmost digit is the units place. The next digit gives the number of 2s. The next digit gives the number of 4s. And so on... Bytes and words can be used to represent integers of different sizes by interpreting the bits as a number in binary notation. 0 x = 0 1 1 x = 2 2 0 x = 0 4 42 1 x = 8 8 0 x = 0 16 1 x = 32 0 x = 0 64 0 x = 0 128 Binary notation is similar to decimal notation but uses a different base. Decimal numbers use 10 as their base, which means that each digit counts for ten times as much as the digit to its right. Binary notation uses base 2, which means that each position counts for twice as much, as follows: 00101010
7
Numbers and Bases The calculation at the end of the preceding slide makes it clear that the binary representation 00101010 is equivalent to the number 42. When it is important to distinguish the base, the text uses a small subscript, like this: 00101010 2 = 42 10 Although it is useful to be able to convert a number from one base to another, it is important to remember that the number remains the same. What changes is how you write it down. The number 42 is what you get if you count how many stars are in the pattern at the right. The number is the same whether you write it in English as forty-two, in decimal as 42, or in binary as 00101010. Numbers do not have bases; representations do.
8
Octal and Hexadecimal Notation Because binary notation tends to get rather long, computer scientists often prefer octal (base 8) or hexadecimal (base 16) notation instead. Octal notation uses eight digits: 0 to 7. Hexadecimal notation uses sixteen digits: 0 to 9, followed by the letters A through F to indicate the values 10 to 15. The following diagrams show how the number forty-two appears in both octal and hexadecimal notation: 2 x = 2 1 5 x = 40 8 52 42 10 x = 10 1 02 x = 32 16 2A 42 octalhexadecimal The advantage of using either octal or hexadecimal notation is that doing so makes it easy to translate the number back to individual bits because you can convert each digit separately.
9
Exercises: Number Bases What is the decimal value for each of the following numbers? 10001 2 177 8 AD 16 1 x = 1 1 0 x = 0 2 = 0 4 = 0 8 1 x = 1 16 10001 17 7 x = 7 1 = 56 8 77 127 1 1 x = 64 13 x = 13 1 10 x = 160 16 AD 173 17127173 As part of a code to identify the file type, every Java class file begins with the following sixteen bits: 1100101011111110 How would you express that number in hexadecimal notation? 1100101011111110 AFE CAFE 16 1100101011111110
10
Memory and Addresses Every byte inside the primary memory of a machine is identified by a numeric address. The addresses begin at 0 and extend up to the number of bytes in the machine, as shown in the diagram on the right. In these slides as well as in the diagrams in the text, memory addresses appear as four-digit hexadecimal numbers, which makes addresses easy to recognize. In Java, it is impossible to determine the address of an object. Memory addresses used in the examples are therefore chosen completely arbitrarily. Memory diagrams that show individual bytes are not as useful as those that are organized into words. The revised diagram on the right now includes four bytes in each of the memory cells, which means that the address numbers increase by four each time. 0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B FFF4 FFF5 FFF6 FFF7 FFF8 FFF9 FFFA FFFB FFFC FFFD FFFE FFFF 0000 0004 0008 000C 0010 0014 0018 001C 0020 0024 0028 002C FFD0 FFD4 FFD8 FFDC FFE0 FFE4 FFE8 FFEC FFF0 FFF4 FFF8 FFFC............
11
The Allocation of Memory to Variables When you declare a variable in a program, Java allocates space for that variable from one of several memory regions. One region of memory is reserved for variables that are never created or destroyed as the program runs, such as named constants and other class variables. This information is called static data. static data 0000 stack FFFF Each time you call a method, Java allocates a new block of memory called a stack frame to hold its local variables. These stack frames come from a region of memory called the stack. Whenever you create a new object, Java allocates space from a pool of memory called the heap. heap In classical architectures, the stack and heap grow toward each other to maximize the available space.
12
0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A 000B FFF4 FFF5 FFF6 FFF7 FFF8 FFF9 FFFA FFFB FFFC FFFD FFFE FFFF 0000 0004 0008 000C 0010 0014 0018 001C 0020 0024 0028 002C FFD0 FFD4 FFD8 FFDC FFE0 FFE4 FFE8 FFEC FFF0 FFF4 FFF8 FFFC............ Memory Bits, bytes, words K, M, G, T (binary/decimal) Initialization to default value for Class var’s Instance var’s Array components Not for local var’s! static data 0000 stack FFFF heap 12
13
Heap-Stack Diagrams It is easier to understand how Java works if you have a good mental model of its use of memory. The text illustrates this model using heap-stack diagrams, which show the heap on the left and the stack on the right, separated by a dotted line. Whenever your program creates a new object, you need to add a block of memory to the heap side of the diagram. That block must be large enough to store the instance variables for the object, along with some extra space, called overhead, that is required for any object. Overhead space is indicated in heap-stack diagrams as a crosshatched box. Whenever your program calls a method, you need to create a new stack frame by adding a block of memory to the stack side. For method calls, you need to add enough space to store the local variables for the method, again with some overhead information that tracks what the program is doing. When a method returns, Java reclaims the memory in its frame.
14
Object References Internally, Java identifies an object by its address in memory. That address is called a reference. stack 1000 r1 FFFC The local variable r1 is allocated in the current stack frame and is assigned the value 1000, which identifies the object. The next slide traces the execution of the TestRational program from Chapter 6 using heap-stack model. Rational r1 = new Rational(1, 2); heap 2 den 1 num 1008 1004 1000 it allocates heap space for the new Rational object. For this example, imagine that the object is created at address 1000. As an example, when Java evaluates the declaration
15
A Complete Heap-Stack Trace skip simulation a b c sum FFFC FFF8 FFF4 FFF0 FFEC 1000 100C 1018 1030 TestRational stack This object is a temporary value used only during the calculation. 1/2 + 1/3 + 1/6 = 1 public void run() { Rational a = new Rational(1, 2); Rational b = new Rational(1, 3); Rational c = new Rational(1, 6); Rational sum = a.add(b).add(c); println(a + " + " + b + " + " + c + " = " + sum); } 1 den 1 num 6 den 5 num 6 den 1 num 3 den 1 num 2 den 1 num public Rational add(Rational r) { return new Rational( this.num * r.den + r.num * this.den, this.den * r.den ); } 6 5 1 3 1 2 public Rational add(Rational r) { return new Rational( this.num * r.den + r.num * this.den, this.den * r.den ); } 36 heap 1038 1034 1030 102C 1028 1024 1020 101C 1018 1014 1010 100C 1008 1004 1000 100C this r FFE8 FFE4 FFE0 1000 100C 1024 1018 This stack frame is created for the run method. This stack frame is created for the add method. All objects are created in the heap. TestRational 1/2 + 1/3 + 1/6 = 1 public void run() { Rational a = new Rational(1, 2); Rational b = new Rational(1, 3); Rational c = new Rational(1, 6); Rational sum = a.add(b).add(c); println(a + " + " + b + " + " + c + " = " + sum); } 1 den 1 num 6 den 5 num 6 den 1 num 3 den 1 num 2 den 1 num heap stack a b c sum FFFC FFF8 FFF4 FFF0 FFEC 1000 100C 1018 1030 1038 1034 1030 102C 1028 1024 1020 101C 1018 1014 1010 100C 1008 1004 1000 15
16
Address Model vs. Pointer Model 1 den 1 num 6 den 5 num 6 den 1 num 3 den 1 num 2 den 1 num heapstack a b c sum FFFC FFF8 FFF4 FFF0 FFEC 1000 100C 1018 1030 1038 1034 1030 102C 1028 1024 1020 101C 1018 1014 1010 100C 1008 1004 1000 1 den 1 num 6 den 5 num 6 den 1 num 3 den 1 num 2 den 1 num a b c sum heapstack 16
17
Addresses vs. Pointers 1 den 1 num 6 den 5 num 6 den 1 num 3 den 1 num 2 den 1 num heapstack a b c sum FFFC FFF8 FFF4 FFF0 FFEC 1000 100C 1018 1030 1038 1034 1030 102C 1028 1024 1020 101C 1018 1014 1010 100C 1008 1004 1000 The two heap-stack diagram formats—the address model and the pointer model—describe exactly the same memory state. The models, however, emphasize different things: –The address model makes it clear that references have numeric values. 1 den 1 num 6 den 5 num 6 den 1 num 3 den 1 num 2 den 1 num a b c sum heapstack –The pointer model emphasizes the relationship between the reference and the object and makes the diagram easier to follow.
18
Garbage Collection 1 den 1 num 6 den 5 num 6 den 1 num 3 den 1 num 2 den 1 num a b c sum heapstack This object was used to hold a temporary result and is no longer accessible Mark-and-sweep collection, in-use flags 18
19
Exercise: Stack-Heap Diagrams public void run() { Point p1 = new Point(0, 0); Point p2 = new Point(200, 200); Line line = new Line(p1, p2); } public class Point { public Point(int x, int y) { cx = x; cy = y; }... other methods appear here... private int cx; private int cy; } public class Line { public Line(Point p1, Point p2) { start = p1; finish = p2; }... other methods appear here... private Point start; private Point finish; } 19
20
100C finish 1000 start 200 cy 200 cx 0 cy 0 cx heap stack p1 p2 line FFFC FFF8 FFF4 FFF0 1000 100C 1018 1020 101C 1018 1014 1010 100C 1008 1004 1000 finish start 200 cy 200 cx 0 cy 0 cx p1 p2 line heapstack Address ModelPointer Model 20
21
Recursion Note: the slides on recursion are for Chapter 14 of the book (not contained in the on-line draft-book) One programming pattern made possible by the use of a dynamic stack for data associated with a method is recursion, which may involve multiple instances of the same method. Recursion is the process of solving a problem by dividing it into smaller subproblems of the same form. The italicized phrase is the essential characteristic of recursion; without it, all you have is a description of stepwise refinement as discussed in Chapter 5. The fact that recursive decomposition generates subproblems that have the same form as the original problem means that recursive programs will use the same method to solve subproblems at different levels of the solution. In terms of the structure of the code, the defining characteristic of recursion is having methods that call themselves, directly or indirectly, as the decomposition process proceeds.
22
A Simple Illustration of Recursion Suppose that you are the national fundraising director for a charitable organization and need to raise $1,000,000. One possible approach is to find a wealthy donor and ask for a single $1,000,000 contribution. The problem with that strategy is that individuals with the necessary combination of means and generosity are difficult to find. Donors are much more likely to make contributions in the $10 range. Another strategy would be to ask 100,000 friends for $10 each. Unfortunately, most of us don’t have 100,000 friends. There are, however, more promising strategies. You could, for example, find ten regional coordinators and charge each one with raising $100,000. Those regional coordinators could in turn delegate the task to local coordinators, each with a goal of $10,000, continuing the process reached a manageable contribution level.
23
A Simple Illustration of Recursion The following diagram illustrates the recursive strategy for raising $1,000,000 described on the previous slide: Goal: $1,000,000 Goal: $100,000 Goal: $100,000 Goal: $100,000 Goal: $100,000 Goal: $100,000 Goal: $100,000 Goal: $100,000 Goal: $100,000 Goal: $100,000 Goal: $100,000 Goal: $10,000 Goal: $10,000 Goal: $10,000 Goal: $10,000 Goal: $10,000 Goal: $10,000 Goal: $10,000 Goal: $10,000 Goal: $10,000 Goal: $10,000 Goal: $1000 Goal: $1000 Goal: $1000 Goal: $1000 Goal: $1000 Goal: $1000 Goal: $1000 Goal: $1000 Goal: $1000 Goal: $1000 Goal: $100 Goal: $100 Goal: $100 Goal: $100 Goal: $100 Goal: $100 Goal: $100 Goal: $100 Goal: $100 Goal: $100
24
A Pseudocode Fundraising Strategy If you were to implement the fundraising strategy in the form of a Java method, it would look something like this: private void collectContributions(int n) { if (n <= 100) { Collect the money from a single donor. } else { Find 10 volunteers. Get each volunteer to collect n/10 dollars. Combine the money raised by the volunteers. } What makes this strategy recursive is that the line Get each volunteer to collect n/10 dollars. will be implemented using the following recursive call: collectContributions(n / 10);
25
Recursive Functions The easiest examples of recursion to understand are functions in which the recursion is clear from the definition. As an example, consider the factorial function from Chapter 5, which can be defined in either of the following ways: n! = n x (n - 1) x (n - 2) x... x 3 x 2 x 1 n! = n x (n - 1)! 1 if n is 0 otherwise The second definition leads directly to the following code, which is shown in simulated execution on the next slide: private int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); }
26
Simulating the factorial Method skip simulation Factorial Enter n: 5 5! = 120 public void run() { int n = readInt("Enter n: "); println(n + "! = " + factorial(n) ); } n 5 120 private int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } n 5 24 private int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } n 4 6 private int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } n 3 2 private int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } n 2 1 private int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } n 1 1 private int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } n 0 private int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } n 0
27
The Recursive “Leap of Faith” The purpose of going through the complete decomposition of the calculation of factorial(5) is to convince you that the process works and that recursive calls are in fact no different from other method calls, at least in their internal operation. The danger with going through these details is that it might encourage you to do the same when you write your own recursive programs. As it happens, tracing through the details of a recursive program almost always makes such programs harder to write. Writing recursive programs becomes natural only after you have enough confidence in the process that you don’t need to trace them fully. As you write a recursive program, it is important to believe that any recursive call will return the correct answer as long as the arguments define a simpler subproblem. Believing that to be true—even before you have completed the code—is called the recursive leap of faith.
28
The Recursive Paradigm Most recursive methods you encounter in an introductory course have bodies that fit the following general pattern: if ( test for a simple case ) { Compute and return the simple solution without using recursion. } else { Divide the problem into one or more subproblems that have the same form. Solve each of the problems by calling this method recursively. Return the solution from the results of the various subproblems. } Finding a recursive solution is mostly a matter of figuring out how to break it down so that it fits the paradigm. When you do so, you must do two things: Identify simple cases that can be solved without recursion.1. Find a recursive decomposition that breaks each instance of the problem into simpler subproblems of the same type, which you can then solve by applying the method recursively. 2.
29
Solution: A Recursive gcd Function Exercise: A Recursive gcd Function public int gcd(int x, int y) { if (y == 0) { return x; } else { return gcd(y, x % y); } public int gcd(int x, int y) { int r = x % y; while (r != 0) { x = y; y = r; r = x % y; } return y; } In the discussion of algorithmic methods in Chapter 5, one of the primary examples was Euclid’s algorithm for computing the greatest common divisor of two integers, x and y. Euclid’s algorithm can be implemented using the following code: As always, the key to solving this problem lies in identifying the recursive decomposition and defining appropriate simple cases. Rewrite this method so that it uses recursion instead of iteration, taking advantage of Euclid’s insight that the greatest common divisor of x and y is also the greatest common divisor of the y and the remainder of x divided by y.
30
Graphical Recursion Recursion comes up in certain graphical applications, most notably in the creation of fractals, which are mathematical structures that consist of similar figures repeated at various different scales. Fractals were popularized in a 1982 book by Benoit Mandelbrot entitled The Fractal Geometry of Nature. One of the simplest fractal patterns to draw is the Koch fractal, named after its inventor, the Swedish mathematician Helge von Koch (1870-1924). The Koch fractal is sometimes called a snowflake fractal because of the beautiful, six-sided symmetries it displays as the figure becomes more detailed. as illustrated in the following diagram:
31
Drawing Koch Fractals order 0 The process of drawing a Koch fractal begins with an equilateral triangle, as shown in the diagram on the lower left. order 1 From the initial position (which is called a fractal of order 0), each higher fractal order is created by replacing each line segment in the figure by four segments that connect the same endpoints but include an equilateral wedge in the middle. order 2 The figure on the previous slide is the Koch fractal of order 4.
32
Simulating the Snowflake Program skip simulation public void run() { SnowflakeFractal fractal = new SnowflakeFractal(100, 2) ; fractal.setFilled(true); fractal.setFillColor(Color.MAGENTA); add(fractal, getWidth() / 2, getHeight() / 2); } fractal Snowflake public SnowflakeFractal(double edge, int order) { addVertex(-edge / 2, -edge / (2 * Math.sqrt(3))); addFractalLine(edge, 0, order); addFractalLine(edge, -120, order); addFractalLine(edge, +120, order); } this edgeorder 100.02 private void addFractalLine(double r, int theta, int order) { if (order == 0) { addPolarEdge(r, theta); } else { addFractalLine(r / 3, theta, order - 1); addFractalLine(r / 3, theta + 60, order - 1); addFractalLine(r / 3, theta - 60, order - 1); addFractalLine(r / 3, theta, order - 1); } this thetaorder 02 r 100.0 private void addFractalLine(double r, int theta, int order) { if (order == 0) { addPolarEdge(r, theta); } else { addFractalLine(r / 3, theta, order - 1); addFractalLine(r / 3, theta + 60, order - 1); addFractalLine(r / 3, theta - 60, order - 1); addFractalLine(r / 3, theta, order - 1); } this thetaorder 01 r 33.33 private void addFractalLine(double r, int theta, int order) { if (order == 0) { addPolarEdge(r, theta); } else { addFractalLine(r / 3, theta, order - 1); addFractalLine(r / 3, theta + 60, order - 1); addFractalLine(r / 3, theta - 60, order - 1); addFractalLine(r / 3, theta, order - 1); } this thetaorder 00 r 11.11 private void addFractalLine(double r, int theta, int order) { if (order == 0) { addPolarEdge(r, theta); } else { addFractalLine(r / 3, theta, order - 1); addFractalLine(r / 3, theta + 60, order - 1); addFractalLine(r / 3, theta - 60, order - 1); addFractalLine(r / 3, theta, order - 1); } this thetaorder 600 r 11.11 private void addFractalLine(double r, int theta, int order) { if (order == 0) { addPolarEdge(r, theta); } else { addFractalLine(r / 3, theta, order - 1); addFractalLine(r / 3, theta + 60, order - 1); addFractalLine(r / 3, theta - 60, order - 1); addFractalLine(r / 3, theta, order - 1); } this thetaorder –600 r 11.11 private void addFractalLine(double r, int theta, int order) { if (order == 0) { addPolarEdge(r, theta); } else { addFractalLine(r / 3, theta, order - 1); addFractalLine(r / 3, theta + 60, order - 1); addFractalLine(r / 3, theta - 60, order - 1); addFractalLine(r / 3, theta, order - 1); } this thetaorder 00 r 11.11 public void run() { SnowflakeFractal fractal = new SnowflakeFractal(100, 2) ; fractal.setFilled(true); fractal.setFillColor(Color.MAGENTA); add(fractal, getWidth() / 2, getHeight() / 2); } public SnowflakeFractal(double edge, int order) { addVertex(-edge / 2, -edge / (2 * Math.sqrt(3))); addFractalLine(edge, 0, order); addFractalLine(edge, -120, order); addFractalLine(edge, +120, order); } this edgeorder 100.02 private void addFractalLine(double r, int theta, int order) { if (order == 0) { addPolarEdge(r, theta); } else { addFractalLine(r / 3, theta, order - 1); addFractalLine(r / 3, theta + 60, order - 1); addFractalLine(r / 3, theta - 60, order - 1); addFractalLine(r / 3, theta, order - 1); }
33
Recursion 33
34
Carl Burch, Programming with Java (Online book) http://www.toves.org/books/java/ch18-recurex/ import java.awt.*; import acm.program.*; import acm.graphics.*; public class Tree extends GraphicsProgram { public void run() { drawTree(250, 350, 100, 90); } public void drawTree(...) {... } } 34
35
public void drawTree(double x0, double y0, double len, double angle) { if (len > 2) { double x1 = x0 + len * GMath.cosDegrees(angle); double y1 = y0 – len * GMath.sinDegrees(angle); add(new GLine(x0, y0, x1, y1)); drawTree(x1, y1, len * 0.75, angle + 30); drawTree(x1, y1, len * 0.66, angle - 50); } 35
36
Primitive Types vs. Objects At first glance, Java’s rules for passing objects as arguments seem to differ from the rules Java uses with arguments that are primitive types. When you pass an argument of a primitive type to a method, Java copies the value of the argument into the parameter variable. As a result, changes to the parameter variable have no effect on the argument. When you pass an object as an argument, there seems to be some form of sharing going on. Although changing the parameter variable itself has no effect, any changes that you make to the instance variables inside an object—usually by calling setters—have a permanent effect on the object. Stack-heap diagrams make the reason for this seeming asymmetry clear. When you pass an object to a method, Java copies the reference but not the object itself.
37
Wrapper Classes The designers of Java chose to separate the primitive types from the standard class hierarchy mostly for efficiency. Primitive values take less space and allow Java to use more of the capabilities provided by the hardware. Even so, there are times in which the fact that primitive types are not objects gets in the way. There are many tools in the Java libraries—several of which you will encounter later in the book—that work only with objects. To get around this problem, Java includes a wrapper class to correspond to each of the primitive types: booleanBoolean byteByte charCharacter doubleDouble floatFloat intInteger longLong shortShort
38
Using Wrapper Classes You can create an instance of a wrapper class by calling its constructor with the primitive value. For example, the line Integer five = new Integer(5); heap 5 1004 1000 creates a new Integer object containing the value 5: stack 1000 five FFFC The value stored in the variable five is a real object, and you can use it in any contexts that require objects. For each of the wrapper classes, Java defines a method to retrieve the primitive value, as illustrated below: int underlyingValue = five.intValue();
39
Wrapper Classes booleanBoolean byteByte charCharacter doubleDouble floatFloat intInteger longLong shortShort Integer five = new Integer(5); int six = five.intValue() + 1; Are immutable heap 5 1004 1000 stack 1000 five FFFC 39
40
Boxing and Unboxing As of Java Standard Edition 5.0, Java automatically converts values back and forth between a primitive type and the corresponding wrapper class. For example, if you write Integer five = 5; Java will automatically call the Integer constructor. These operations are called boxing and unboxing. Similarly, if you then write int six = five + 1; Java will automatically call intValue before the addition. Although boxing and unboxing can be quite convenient, this feature can generate confusion and should be used with care.
41
(Auto) Boxing and Unboxing Integer five = new Integer(5); int six = five.intValue() + 1; vs. Integer five = 5; // Boxing 5 int six = five + 1; // Unboxing five 41
42
Linking Objects Together Although most examples of this technique are beyond the scope of a first course, references are particularly important in computer science because they make it possible to represent the relationship among objects by linking them together in various ways. link data One common example (which you will encounter again in Chapter 13) is called a linked list, in which each object in a sequence contains a reference to the one that follows it: link data link data null link data Java marks the end of linked list using the constant null, which signifies a reference that does not actually point to an actual object. The value null has several other uses, as you will discover in the chapters that follow.
43
The Beacons of Gondor Minas TirithAmon DînEilenachNardolErelasMin-RimmonCalenhadHalifirienRohan For answer Gandalf cried aloud to his horse. “On, Shadowfax! We must hasten. Time is short. See! The beacons of Gondor are alight, calling for aid. War is kindled. See, there is the fire on Amon Dîn, and flame on Eilenach; and there they go speeding west: Nardol, Erelas, Min-Rimmon, Calenhad, and the Halifirien on the borders of Rohan.” —J. R. R. Tolkien, The Return of the King, 1955 In a scene that was brilliantly captured in Peter Jackson’s film adaptation of The Return of the King, Rohan is alerted to the danger to Gondor by a succession of signal fires moving from mountain top to mountain top. This scene is a perfect illustration of the idea of message passing in a linked list.
44
Linking Objects Together link data link data link data null link data Linked list: Minas Tirith Amon DînEilenachNardolErelasMin- Rimmon CalenhadHalifirienRohan 44
45
public class SignalTower { /* Constructs a new signal tower */ public SignalTower(String name, SignalTower link) { towerName = name; nextTower = link; } /* Signals this tower and passes the * message along to the next one. */ public void signal() { lightCurrentTower(); if (nextTower != null) { nextTower.signal(); } /* Marks this tower as lit */ public void lightCurrentTower() {... code to draw a fire on this tower... } /* Private instance variables */ private String towerName; private SignalTower nextTower; } Minas Tirith Amon Dîn Eilenach Nardol Erelas Min-Rimmon Calenhad Halifirien Rohan null 45
46
Summary I Computer memory is a sequence of addressable bytes Hexadecimal notation often convenient for memory addresses and contents char / int / double require 2 / 4 / 8 bytes Memory is organized in three regions: 1.Static data: program code, static variables 2.Heap: objects, instance variables ( new ) 3.Stack: local variables, references to objects (pointers) Stacks are dynamic last-in, first-out (LIFO) data structures (push + pop) 46
47
Summary II Using a stack for method data facilitates recursion Stack frame of a method call includes this pointer Garbage collection reclaims unused memory in heap (mark-and-sweep) In method calls, primitive type are passed by value, objects are passed by reference; thus objects are shared between caller and callee Automatic boxing/unboxing transforms between primitive types and their corresponding wrapper classes Objects can contain references to other objects – use this e.g. for linked lists 47
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.