Java Module 1: Objects and Classes
Create your repository: https://classroom.github.com/a/W1JDwUv9
- Then clone the repository into your CodeAnywhere container using
git clone YOUR_URL
- (Click the green “Clone or Download” button to find your repository’s URL).
- For this assignment you do NOT need to submit your source code files, just your README file.
- Be sure to fill in your name in your readme file or you may lose points!
Due: Thursday 10/18 at 11:59PM.
Updates:
- 10/13: Clarified requirements for 1.5
Objectives
- To learn how Java uses objects to encapsulate data and methods
- To understand how Java objects are stored in memory
- To appreciate the differences between C and Java
Java Basics
Sick of “Hello World” yet?
public class Hello {
public static void main (String[] argv)
{
String s = "Hello World!";
System.out.println(s);
}
}
- Must be put in a file called “Hello.java”
- Unlike C, file name is important and must match class name
- Program start point:
public static void main(...)
Compiling and Running Java
In Java, javac
is used as the compiler. There are a few notable differences from C:
- Standardized names:
- The input code file must be named based on whatever
public class
is defined in it. A java code file can only define onepublic class
(but it can have several non-public ones) - The output file always becomes
<ClassName>.class
- The input code file must be named based on whatever
- Each class gets its own compiled file (not just one executable like
a.out
) - A
.class
file is java bytecode—must be interpreted by the JVM- It is not platform specific assembly instructions/byte code like in C
To run a program we use the java
command, followed by the name of the class we want to run.
cabox@codeanywhere$ ls
Hello.java
cabox@codeanywhere$ javac Hello.java
cabox@codeanywhere$ ls
Hello.class Hello.java
cabox@codeanywhere$ java Hello
Hello World!
Compiling and Running in Repl.it
For this module you can do your java development on the Repl.it site instead of CodeAnywhere.
Here is a basic Java program editor that you can extend for each exercise in this module
A few notes:
- Repl.it has a restriction on how files (and thus classes) are named. The site requires that your primary code file be named
Main.java
, which in turn means that your public class in that file must be namedMain
- Remember that Repl.it will lose your work if you leave the page, so you will need to copy your code into your GitHub readme to save it.
Data Types
We can define and work with variables in the same way as in C.
Type | Domain |
---|---|
byte |
8-bit integers: -128 to 127 |
short |
16-bit integers: -32768 to 32767 |
int |
32-bit integers: -2147483648 to 2147483647 |
long |
64-bit integers: -9223372036854775808 to 9223372036854775807 |
float |
32-bit floating point numbers: ± 1.4x10-45 to ± 3.4028235x10^38 |
double |
64-bit floating point numbers: ± 4.39x10-322 to ± 1.7976931348623157x10308 |
boolean |
true or false |
char |
16-bit characters encoded using Unicode |
- Java has the same basic data types as C
short
,int
,long
,float
,double
,char
- No unsigned types
- Plus two more
byte
(8 byte number) andboolean
(T or F)
- All data types (except Boolean) have a precisely defined size on all platforms
- Different from C where size depends on architecture: 16 vs 32 vs 64bit ints
- All elements in an array are initialized to zero, false, or null. The compiler will produce an error for any other uninitialized variables.
- Note that Java has a
String
type, but this is actually a class rather than a primitive data type as we will see later. The capital “S” in its name is a clue to that!- Java Strings are easier to use than C! You can easily do things like combine two strings together with the
+
operator.
- Java Strings are easier to use than C! You can easily do things like combine two strings together with the
Arrays can be created using []
notation. Unlike in C, an array must be instantiated using the new
keyword, like:
int a[] = new int[size];
.
- Arrays can be dynamically sized (i.e.,
size
does not need to be a constant like in C). This should give you a hint about where they will be located in memory.
Note, at this point we will assume that you have basic Java knowledge, such as working with variables, for/while loops, etc.
If you need a refresher, check Prof. Simha’s 1111 and 1112 modules.
Classes and Objects
In Java:
- A class determines the type of an object
- All objects have a class
- Primitive data types (int, float, etc) and functions are not objects
Let’s use an example to think about why we want to use classes.
Consider the representation of points in the XY plane:
We could represent these points with a series of variables:
double x1 = 2;
double y1 = 3;
double x2 = 5;
double y2 = 3;
double x3 = 5;
double y3 = 7;
double d1 = Math.sqrt(x3*x3 + y3*y3);
System.out.println("d1 = " + d1);
but this is pretty awkward – whenever we add new points to our graph we would need to define new variables that would be added to the stack.
Alternatively we could store everything in a pair of arrays, one for x values and one for the Y values. Since arrays in Java can be dynamically sized, they must not be based on the stack. Instead, arrays are always stored on the Heap:
- Note that the arrary variables
x
andy
are like pointers in C, referring to memory allocated on the heap with thenew
command d0
is a local int on the stack which could be used to calculate distances for the different points
This approach would let us grow our number of XY points, but it is still a pretty messy solution, and it wouldn’t help if we decide to later add more data such as a Z coordinate to each point.
Exercise 1.1: Write a java program that stores the XY points into arrays that will result in the same memory layout as shown above. Write a for loop that calculates the distance from the origin to each point. Paste your program into a code block in your README file AND paste its output.
Class Data
For a better solution, we want to encapsulate the data that forms a “point” into a class and then instantiate objects to represent the specific points in our program. This keeps the data together in one place so we can easily work with it.
class XYPoint {
double x;
double y;
}
public class Main {
public static void main (String[] argv) {
// Make a new instance of an XYPoint object
XYPoint p = new XYPoint();
// Assign values to its variables:
p.x = 2;
p.y = 3;
// Access its variables:
double d = Math.sqrt(p.x*p.x + p.y*p.y);
System.out.println("Distance = " + d);
}
}
Here:
- We are defining two classes:
XYPoint
andMain
. What must be the filename to store these classes? - Importantly, the
XYPoint
class example above does not use thestatic
keyword, whereasMain
contains only a single method and that method is markedstatic
- We call
XYPoint
a dynamic class… and soon we will see why. - The
main
method must appear in a public class. - Whatever class is marked as
public
must match the overall filename, since this is the class that will be visible to the outside. - This means any given
.java
file can have severalclass
declarations, but only onepublic class
and only onepublic static void main
method.
- We call
Let’s see how the two classes interact:
- The entry point to any Java program will always be a
static void main
method. - A dynamic class is not usable until an instance is created with the
new
operator:XYPoint p = new XYPoint();
- Once it is declared, we create a chunk of memory on the heap for it
- This is analogous to what we’ve done with arrays in Java and with using
malloc
in C
Then, once this variable p
points to the space in memory to hold the object instance, we can access parts of the object using the dot operator:
p.x = 2;
Think of it this way:
- The variable
p
points to a chunk of memory that is constructed in the template of the objectXYPoint
(just like astruct
in C) - The object
XYPoint
has two double’s called x and y. - Think of x and y as components of the object pointed to by p.
- To access these components, we use
p.x
andp.y
- Recall that in C we had to distinguish between structs that were accessed with as a local variable (meaning they were on the stack) versus a pointer (which could have been to the stack or heap). Why doesn’t Java require two operators for accessing object fields like in C?
In memory, our current program looks like:
From this we see that Java has pointers! It isn’t just something to torture you in C!
- BUT, the main difficulties in using pointers in C arise from the complete freedom it gives you in how you use them (casting them, doing pointer arithmetic, treating arrays as pointers, the differences between pointer and non-pointer structs, etc).
- In Java, all objects are stored on the heap and accessed via pointers, but because the programming model is restricted, it is hard to make the same mistakes as you had in C. You’ll still get plenty of Null pointer exceptions though!
- In Java we generally call these references instead of pointers.
- The chunk of memory on the heap is a bit larger than the data in the class because, underneath the hood, there is bookkeeping info for a class, and other items not necessarily visible in the code.
Class Methods
Our solution thus far can be further improved by making the XYPoint
class contain not only data, but also the methods that operate on that data. This is something we couldn’t have done with structs
in C.
Suppose we want to add a function to compute the distance from the origin to a point. Where should we put this function?
- We could put it into
Main
and have it take anXYPoint
as an argument - We could put it inside
XYPoint
and have it operate on the x and y fields of the object itself
In this case, it is fairly clear that the second solution is better – we generally want to keep functions close to the data that they operate on, and here it makes sense that the XYPoint
object itself should be responsible for calculating its own distance. What are some reasons why we want to keep the method close to the data?
Exercise 1.2: Add a
distance
function to the XYPoint class that returns a double with the geometric distance from the origin to the point. Paste your code and output in your README.
Next let’s add a function to calculate the distance between two XYPoint
s. Where should we put this?
- We could put it in
Main
since it is aware of all the different points. - We could put it in
XYPoint
since it could compare itself against a point passed as an argument.
Once again, the second solution is better since we think of distance as being a characteristic of the XYPoint
itself, rather than being something controlled by the overall program.
- Nevertheless, there may be cases where placing it in
Main
is better or it may be required if you don’t have access to edit theXYPoint
source code.
Note: If we place a function in the Main
class, it will need to be a static
method because we never instantiate that class.
- A static method like
main
can only call functions in a dynamic class that has been instantiated into an object, or other static methods. - The
static
keyword is similar in C and in Java: having a static variable in a C function meant that there was only a single version of that variable – unlike a normal local variable, we would not (dynamically) create a new version of that variable on the stack every time the function was called. - Static variables in Java act the same way – we will only have one version of them, rather than one version per object that gets created.
Exercise 1.3: Add
distanceTo
functions to bothMain
andXYPoint
– you will need different arguments for each one. Instantiate twoXYPoint
variables in yourmain
function and call both versions of your distance functions to compute the distance between them. Paste your code and output into your README.
Exercise 1.4: Consider a square with the lower left corner at the origin, and with length
s
. A point is considered within the square if it’s inside or on the one of the edges, as in this diagram:Write a method inside the
XYPoint
class so that the following code inmain()
works:public static void main (String[] argv) { XYPoint p = new XYPoint (); p.x = 2; p.y = 3; boolean yes = p.isWithin (5); System.out.println (yes); // should be true XYPoint q = new XYPoint (); q.x = 5; q.y = 7; yes = q.isWithin (5); System.out.println (yes); // should be false XYPoint z = new XYPoint (); z.x = 8; z.y = 9; yes = z.isWithin (5); System.out.println (yes); // should be false }
The method
isWithin()
takes in the length of the square’s side.
Arrays of Objects
One of the conveniences of using an array is the ability to write code that marches through the array performing the same computation. For example, we might want to walk through an array of our point objects and print their distance values.
Let’s now see how this can work with a group of objects in an array:
class XYPoint {
double x;
double y;
public void printDistance () {
double d = Math.sqrt (x*x + y*y);
System.out.println (d);
}
}
public class Main {
public static void main (String[] argv) {
// Make an instance of the object with the new operator:
XYPoint[] p = new XYPoint [3];
p[0] = new XYPoint (); // First point.
p[0].x = 2; p[0].y = 3;
p[1] = new XYPoint (); // Second one.
p[1].x = 5; p[1].y = 7;
p[2] = new XYPoint (); // Third.
p[2].x = 5; p[2].y = 3;
for (int i=0; < p.length; i++) {
p[i].printDistance ();
}
}
}
There are a few key points we need to undestand this. Let’s draw some memory pictures to illustrate.
First, the memory picture just after executing
XYPoint[] p = new XYPoint [3];
- The statement declares an array variable
p
. - Then, the
new
operator assigns a size-3 array of variables that will eventually point to chunks ofXYPoint
objects. At this point all of the entries in the array itself arenull
references.
Next, consider the execution up to:
XYPoint[] p = new XYPoint [3];
p[0] = new XYPoint ();
p[0].x = 2; p[0].y = 3;
- The second line makes the first location of the array point to an
XYPoint
object. - The third line assigns values using the “dot” operator.
- Thus, in memory it looks like:
Finally, after the next two objects have been created
XYPoint[] p = new XYPoint [3];
p[0] = new XYPoint ();
p[0].x = 2; p[0].y = 3;
p[1] = new XYPoint (); // Second one.
p[1].x = 5; p[1].y = 7;
p[2] = new XYPoint (); // Third.
p[2].x = 5; p[2].y = 3;
we will have:
A common error in Java programming is to only use new
to allocate the array itself and to forget to also use new
for each object in the array! What type of error will you get if you make this mistake?
Side Note: Java provides the Math.random()
function which returns a random double
value between 0 and 1 (specifically, the range includes 0 but will be strictly less than 1). You can scale this up to get larger random numbers:
int d6 = Math.Random() * 6; // random int between 0 and 5
Exercise 1.5: Create an array of 10
XYPoint
s in yourmain
function with random X,Y coordinates between 0 and 20. Add adistanceToAllPoints
function that calculates the distance between each pair of points in the array and prints it to the console. You do not need to include the output of your program in your README file. Instead, explain why you created the function in the place that you did and why you chose those arguments.
Objects in Objects
One intuitive way to think about the variable x
in XYPoint
is that the variable x
is inside the object and is accessed via the “dot” operator: p.x = 2;
- One can draw an analogy thinking of the object as a box with compartments, one each for instance variable defined within it.
It is possible to put an object (which itself has components) inside another, as in this example:
Suppose we wish to represent a line segment between two points.
- A line segment is therefore defined by its end points.
- We’ll call one of them
start
and the otherend
.
class XYPoint {
double x;
double y;
}
class XYLine {
XYPoint start; // An object variable.
XYPoint end; // Another one.
public void printSlope () {
double s = (end.y - start.y) / (end.x - start.x);
System.out.println ("Slope = " + s);
}
}
public class Main {
public static void main (String[] argv) {
XYPoint p = new XYPoint ();
p.x = 2; p.y = 3;
XYPoint q = new XYPoint ();
q.x = 5; q.y = 7;
XYLine L = new XYLine ();
L.start = p; // Point to the same object as p, not a full copy!
L.end = q;
L.printSlope ();
}
}
Let’s understand this from a memory point of view:
- After the first two points are created in
main()
we have:
- Then, right after the
XYLine
object is created withXYLine L = new XYLine ();
- Next, consider what happens after the assignments
L.start = p; // Point to the same object as p, not a full copy! L.end = q;
- Here we have entered some addresses to emphasize the copying of (pointer or address) values from variables
p
andq
intoL.start
andL.end
- Now let’s draw the same thing without addresses but with our conceptual arrows:
Class Constructors
Often we want to immediately fill in some instance variables when we create a new object. A class constructor allows us to easily do that:
class XYPoint {
double x;
double y;
public XYPoint(double x, double y) {
this.x = x; // differentiate between parameter x and instance variable
this.y = y;
}
public XYPoint() {
x = Math.random() * 10;
y = Math.random() * 10;
}
}
public class Main {
public static void main (String[] argv) {
// Make a new instance of an XYPoint object
XYPoint p = new XYPoint();
XYPoint p2 = new XYPoint(5, 5);
// Access its variables:
double d = Math.sqrt(p.x*p.x + p.y*p.y);
System.out.println(d);
d = Math.sqrt(p2.x*p2.x + p2.y*p2.y);
System.out.println(d);
}
}
Note:
- A class can have several different constructors with different parameters.
- Here we are using the
this
keyword to distinguish between thex
variable that arrives as a parameter to the constructor and the instance variablex
which is part of “this” new object being created.
What are the benefits of using a constructor? What could go wrong if we didn’t have one?
Exercise 1.6: Create at least two different constructors for the XYPoint and XYLine classes. Add a function that calculates the length of a line. Create an array with 10 random lines and print the length of each one.
From C to Java
Consider this C code for a manually defined Linked List from C Module 4:
struct LNode {
int data;
LNode* next;
};
struct LList {
LNode* head;
};
int main() {
struct LList* list;
struct LNode * a, * b, * c;
list = NULL;
a = NULL; b = NULL; c = NULL;
list = malloc(sizeof(struct LList));
a = malloc(sizeof(struct LNode));
b = malloc(sizeof(struct LNode));
c = malloc(sizeof(struct LNode));
list->head = a;
a->data = 45;
a->next = b;
b->data = 89;
b->next = c;
c->data = 52;
c->next = NULL;
}
How do we convert this C code into Java code?
- no structs!
- Define a class for each data type
- no malloc!
- instantiate object with
new
- instantiate object with
- define constructor to initialize objects
- don’t need to initialize references to null
- don’t need to differentiate between local structs and struct pointers
- in Java, everything is a pointer!
- use
.
instead of->
Exercise 1.7: Convert the above C linked list code into Java. Make the program print out the data for each node.