Detailed Table of Contents
Guidance for the item(s) below:
As you know, OOP is a core part of this course. Let's start learning the OOP paradigm this week.
Can describe OOP at a higher level
Object-Oriented Programming (OOP) is a programming paradigm. A programming paradigm guides programmers to analyze programming problems, and structure programming solutions, in a specific way.
Programming languages have traditionally divided the world into two parts—data and operations on data. Data is static and immutable, except as the operations may change it. The procedures and functions that operate on data have no lasting state of their own; they’re useful only in their ability to affect data.
This division is, of course, grounded in the way computers work, so it’s not one that you can easily ignore or push aside. Like the equally pervasive distinctions between matter and energy and between nouns and verbs, it forms the background against which you work. At some point, all programmers—even object-oriented programmers—must lay out the data structures that their programs will use and define the functions that will act on the data.
With a procedural programming language like C, that’s about all there is to it. The language may offer various kinds of support for organizing data and functions, but it won’t divide the world any differently. Functions and data structures are the basic elements of design.
Object-oriented programming doesn’t so much dispute this view of the world as restructure it at a higher level. It groups operations and data into modular units called objects and lets you combine objects into structured networks to form a complete program. In an object-oriented programming language, objects and object interactions are the basic elements of design.
Some other examples of programming paradigms are:
Paradigm | Programming Languages |
---|---|
Procedural Programming paradigm | C |
Functional Programming paradigm | F#, Haskell, Scala |
Logic Programming paradigm | Prolog |
Some programming languages support multiple paradigms.
Java is primarily an OOP language but it supports limited forms of functional programming and it can be used to (although not recommended to) write procedural code. e.g. se-edu/addressbook-level1
JavaScript and Python support functional, procedural, and OOP programming.
Can describe how OOP relates to the real world
An object in Object-Oriented Programming (OOP) has state and behavior, similar to objects in the real world.
Every object has both state (data) and behavior (operations on data). In that, they’re not much different from ordinary physical objects. It’s easy to see how a mechanical device, such as a pocket watch or a piano, embodies both state and behavior. But almost anything that’s designed to do a job does, too. Even simple things with no moving parts such as an ordinary bottle combine state (how full the bottle is, whether or not it’s open, how warm its contents are) with behavior (the ability to dispense its contents at various flow rates, to be opened or closed, to withstand high or low temperatures).
It’s this resemblance to real things that gives objects much of their power and appeal. They can not only model components of real systems, but equally as well fulfill assigned roles as components in software systems.
OOP views the world as a network of interacting objects.
A real world scenario viewed as a network of interacting objects:
You are asked to find out the average age of a group of people Adam, Beth, Charlie, and Daisy. You take a piece of paper and pen, go to each person, ask for their age, and note it down. After collecting the age of all four, you enter it into a calculator to find the total. And then, use the same calculator to divide the total by four, to get the average age. This can be viewed as the objects You
, Pen
, Paper
, Calculator
, Adam
, Beth
, Charlie
, and Daisy
interacting to accomplish the end result of calculating the average age of the four persons. These objects can be considered as connected in a certain network of certain structure that dictates how these objects can interact. For example, You
object is connected to the Pen
object, and hence You
can use the Pen
object to write.
OOP solutions try to create a similar object network inside the computer’s memory – a sort of virtual simulation of the corresponding real world scenario – so that a similar result can be achieved programmatically.
OOP does not demand that the virtual world object network follow the real world exactly.
Our previous example can be tweaked a bit as follows:
Main
to represent your role in the scenario.Pen
and Paper
with an object called AgeList
that is able to keep a list of ages.Every object has both state (data) and behavior (operations on data).
The state and behavior of our running example are as follows:
Object | Real World? | Virtual World? | Example of State (i.e. Data) | Examples of Behavior (i.e. Operations) |
---|---|---|---|---|
Adam | Name, Date of Birth | Calculate age based on birthday | ||
Pen | - | Ink color, Amount of ink remaining | Write | |
AgeList | - | Recorded ages | Give the number of entries, Accept an entry to record | |
Calculator | Numbers already entered | Calculate the sum, divide | ||
You/Main | Average age, Sum of ages | Use other objects to calculate |
Every object has an interface and an implementation.
Every real world object has,
The interface and implementation of some real-world objects in our example:
Similarly, every object in the virtual world has an interface and an implementation.
The interface and implementation of some virtual-world objects in our example:
Adam
: the interface might have a method getAge(Date asAt)
; the implementation of that method is not visible to other objects.Objects interact by sending messages. Both real world and virtual world object interactions can be viewed as objects sending messages to each other. The message can result in the sender object receiving a response and/or the receiver object’s state being changed. Furthermore, the result can vary based on which object received the message, even if the message is identical (see rows 1 and 2 in the example below).
Same messages and responses from our running example:
World | Sender | Receiver | Message | Response | State Change |
---|---|---|---|---|---|
Real | You | Adam | "What is your name?" | "Adam" | - |
Real | as above | Beth | as above | "Beth" | - |
Real | You | Pen | Put nib on paper and apply pressure | Makes a mark on your paper | Ink level goes down |
Virtual | Main | Calculator (current total is 50) | add(int i): int i = 23 | 73 | total = total + 23 |
Guidance for the item(s) below:
OOP is built upon four core concepts. The firs two are:
They are explained in the sections below.
Can explain the abstraction aspect of OOP
The concept of Objects in OOP is an abstraction mechanism because it allows us to abstract away the lower level details and work with bigger granularity entities i.e. ignore details of data formats and the method implementation details and work at the level of objects.
You can deal with a Person
object that represents the person Adam and query the object for Adam's age instead of dealing with details such as Adam’s date of birth (DoB), in what format the DoB is stored, the algorithm used to calculate the age from the DoB, etc.
Can explain the encapsulation aspect of OOP
Encapsulation protects an implementation from unintended actions and from inadvertent access.
-- Object-Oriented Programming with Objective-C, Apple
An object is an encapsulation of some data and related behavior in terms of two aspects:
1. The packaging aspect: An object packages data and related behavior together into one self-contained unit.
2. The information hiding aspect: The data in an object is hidden from the outside world and are only accessible using the object's interface.
Can explain the relationship between classes and objects
Writing an OOP program is essentially writing instructions that the computer will use to,
A class contains instructions for creating a specific kind of objects. It turns out sometimes multiple objects keep the same type of data and have the same behavior because they are of the same kind. Instructions for creating a 'kind' (or ‘class’) of objects can be done once and those same instructions can be used to objects of that kind. We call such instructions a Class.
Classes and objects in an example scenario
Consider the example of writing an OOP program to calculate the average age of Adam, Beth, Charlie, and Daisy.
Instructions for creating objects Adam
, Beth
, Charlie
, and Daisy
will be very similar because they are all of the same kind: they all represent ‘persons’ with the same interface, the same kind of data (i.e. name
, dateOfBirth
, etc.), and the same kind of behavior (i.e. getAge(Date)
, getName()
, etc.). Therefore, you can have a class called Person
containing instructions on how to create Person
objects and use that class to instantiate objects Adam
, Beth
, Charlie
, and Daisy
.
Similarly, you need classes AgeList
, Calculator
, and Main
classes to instantiate one each of AgeList
, Calculator
, and Main
objects.
Class | Objects |
---|---|
Person | objects representing Adam, Beth, Charlie, Daisy |
AgeList | an object to represent the age list |
Calculator | an object to do the calculations |
Main | an object to represent you (i.e., the one who manages the whole operation) |
Guidance for the item(s) below:
Now that you know what objects are, let's see how they are used in Java, which happens to be an OOP language.
Can use in-built Java objects
Java is an "object-oriented" language, which means that it uses objects to represent data and provide methods related to them. Object types are called classes e.g., you can use String
objects in Java and those objects belong to the String
class.
Java comes with many inbuilt classes which are organized into packages. Here are some examples:
package | Some example classes in the package |
---|---|
java.lang | String , Math , System |
Before using a class in your code, you need to import
the class. import
statements appear at the top of the code.
This example imports the java.awt.Point
class (i.e., the Point
class in the java.awt
package) -- which can be used to represent the coordinates of a location in a Cartesian plane -- and use it in the main
method.
import java.awt.Point;
public class Main{
public static void main(String[] args) {
Point spot = new Point(3, 4);
int x = spot.x;
System.out.println(x);
}
}
You might wonder why we can use the System
class without importing it. System
belongs to the java.lang
package, which is imported automatically.
new
operatorTo create a new object, you have to use the new
operator
This line shows how to create a new Point
object using the new
operator:
Point spot = new Point(3, 4);
Update the code below to create a new Rectangle
object as described in the code comments, to produce the given output.
Rectangle
class is found in the java.awt
package.Rectangle
objects are (int x, int y, int width, int height)
.public class Main {
public static void main(String[] args) {
Rectangle r;
// TODO: create a Rectangle object that has the
// properties x=0, y=0, width=5, height=10
// and assign it to r
System.out.println(r);
}
}
java.awt.Rectangle[x=0,y=0,width=5,height=10]
Hint
Can use instance members of objects
Variables that belong to an object are called attributes (or fields).
To access an attribute of an object, Java uses dot notation.
The code below uses spot.x
which means "go to the object spot
refers to, and get the value of the attribute x
."
Point spot = new Point(3, 4);
int sum = spot.x * spot.x + spot.y * spot.y;
System.out.println(spot.x + ", " + spot.y + ", " + sum);
3, 4, 25
You can an object by assigning a different value to its attributes.
This example changes the x value of the Point
object to 5
.
Point spot = new Point(3, 4);
spot.x = 5;
System.out.println(spot.x + ", " + spot.y);
5, 4
Java uses the dot notation to invoke methods on an object too.
This example invokes the translate
method on a Point
object so that it moves to a different location.
Point spot = new Point(3, 4);
System.out.println(spot.x + ", " + spot.y);
spot.translate(5,5);
System.out.println(spot.x + ", " + spot.y);
3, 4
8, 9
Update the code below as described in code comments, to produce the given output.
import java.awt.Rectangle;
public class Main {
public static void main(String[] args) {
Rectangle r = new Rectangle(0, 0, 4, 6);
System.out.println(r);
int area;
//TODO: add a line below to calculate the area using
// width and height properties of r
// and assign it to the variable area
System.out.println("Area: " + area);
//TODO: add a line here to set the size of r to
// 8x10 (width x height)
//Recommended: use the setSize(int width, int height)
// method of the Rectangle object
System.out.println(r);
}
}
java.awt.Rectangle[x=0,y=0,width=4,height=6]
Area: 24
java.awt.Rectangle[x=0,y=0,width=8,height=10]
Hint
Can pass objects between methods
You can pass objects as parameters to a method in the usual way.
The printPoint
method below takes a Point
object as an argument and displays its attributes in (x,y)
format.
public static void printPoint(Point p) {
System.out.println("(" + p.x + ", " + p.y + ")");
}
public static void main(String[] args) {
Point spot = new Point(3, 4);
printPoint(spot);
}
(3, 4)
You can return an object from a method too.
The java.awt
package also provides a class called Rectangle
. Rectangle
objects are similar to points, but they have four attributes: x
, y
, width
, and height
. The findCenter
method below takes a Rectangle
as an argument and returns a Point
that corresponds to the center of the rectangle:
public static Point findCenter(Rectangle box) {
int x = box.x + box.width / 2;
int y = box.y + box.height / 2;
return new Point(x, y);
}
The return type of this method is Point
. The last line creates a new Point
object and returns a reference to it.
null
and NullPointerException
null
is a special value that means "no object". You can assign null to a variable to indicate that the variable is 'empty' at the moment. However, if you try to use a null value, either by accessing an attribute or invoking a method, Java throws a NullPointerException
.
In this example, the variable spot
is assigned a null
value. As a result, trying to access spot.x
attribute or invoking the spot.translate
method results in a NullPointerException
.
Point spot = null;
int x = spot.x; // NullPointerException
spot.translate(50, 50); // NullPointerException
On the other hand, it is legal to return null from a method or to pass a null
reference as an argument to a method.
Returning null
from a method.
public static Point createCopy(Point p) {
if (p == null) {
return null; // return null if p is null
}
// create a new object with same x,y values
return new Point(p.x, p.y);
}
Passing null
as the argument.
Point result = createCopy(null);
System.out.println(result);
null
It is possible to have multiple variables that refer to the same object.
Notice how p1
and p2
are aliases for the same object. When the object is changed using the variable p1
, the changes are visible via p2
as well (and vice versa), because they both point to the same Point
object.
Point p1 = new Point(0,0);
Point p2 = p1;
System.out.println("p1: " + p1.x + ", " + p1.y);
System.out.println("p2: " + p2.x + ", " + p2.y);
p1.x = 1;
p2.y = 2;
System.out.println("p1: " + p1.x + ", " + p1.y);
System.out.println("p2: " + p2.x + ", " + p2.y);
p1: 0, 0
p2: 0, 0
p1: 1, 2
p2: 1, 2
Java does not have explicit pointers (and other related things such as pointer de-referencing and pointer arithmetic). When an object is passed into a method as an argument, the method gains access to the original object. If the method changes the object it received, the changes are retained in the object even after the method has completed.
Note how p3
retains changes done to it by the method swapCoordinates
even after the method has completed executing.
public static void swapCoordinates(Point p){
int temp = p.x;
p.x = p.y;
p.y = temp;
}
public static void main(String[] args) {
Point p3 = new Point(2,3);
System.out.println("p3: " + p3.x + ", " + p3.y);
swapCoordinates(p3);
System.out.println("p3: " + p3.x + ", " + p3.y);
}
p3: 2, 3
p3: 3, 2
Add a method move(Point p, Rectangle r)
to the code below, to produce the given output. The behavior of the method is as follows:
null
and does nothing if either p
or r
is nullPoint
object that has attributes x
and y
that match those of r
p
r
so that its attributes x
and y
match those of p
import java.awt.Point;
import java.awt.Rectangle;
public class Main {
//TODO add your method here
public static void main(String[] args) {
Point p1 = new Point(0, 0);
Rectangle r1 = new Rectangle(2, 3, 5, 6);
System.out.println("arguments: " + p1 + ", " + r1);
Point p2 = move(p1, r1);
System.out.println(
"argument point after method call: " + p1);
System.out.println(
"argument rectangle after method call: " + r1);
System.out.println(
"returned point: " + p2);
System.out.println(move(null, null));
}
}
arguments: java.awt.Point[x=0,y=0], java.awt.Rectangle[x=2,y=3,width=5,height=6]
argument point after method call: java.awt.Point[x=0,y=0]
argument rectangle after method call: java.awt.Rectangle[x=0,y=0,width=5,height=6]
returned point: java.awt.Point[x=2,y=3]
null
Hint
Can explain Java garbage collection
What happens when no variables refer to an object?
Point spot = new Point(3, 4);
spot = null;
The first line creates a new Point
object and makes spot refer to it. The second line changes spot
so that instead of referring to the object, it refers to nothing. If there are no references to an object, there is no way to access its attributes or invoke a method on it. From the programmer’s view, it ceases to exist. However, it’s still present in the computer’s memory, taking up space.
In Java, you don’t have to delete objects you create when they are no longer needed. As your program runs, the system automatically looks for stranded objects and reclaims them; then the space can be reused for new objects. This process is called garbage collection. You don’t have to do anything to make garbage collection happen, and in general don’t have to be aware of it. But in high-performance applications, you may notice a slight delay every now and then when Java reclaims space from discarded objects.
Guidance for the item(s) below:
Having seen how to use objects in Java, the next step is learn how to define new kinds of objects (aka classes) in Java.
Can define Java classes
As you know,
new
operator instantiates objects, that is, it creates new instances of a class. Here's a class called Time
, intended to represent a moment in time. It has three attributes and no methods.
public class Time {
private int hour;
private int minute;
private int second;
}
You can give a class any name you like. The Java convention is to use format for class names.
The code is placed in a file whose name matches the class e.g., the Time
class should be in a file named Time.java
.
When a class is public
(e.g., the Time
class in the above example) it can be used in other classes. But the that are private
(e.g., the hour
, minute
and second
attributes of the Time
class) can only be accessed from inside the Time
class.
The syntax for is similar to that of other methods, except:
static
is omitted.When you invoke new
, Java creates the object and calls your constructor to initialize the instance variables. When the constructor is done, it returns a reference to the new object.
Here is an example constructor for the Time
class:
public Time() {
hour = 0;
minute = 0;
second = 0;
}
This constructor does not take any arguments. Each line initializes an instance variable to 0
(which in this example means midnight).
Now you can create Time
objects.
Time time = new Time();
Like other methods, constructors can be .
You can add another constructor to the Time
class to allow creating Time
objects that are initialized to a specific time:
public Time(int h, int m, int s) {
hour = h;
minute = m;
second = s;
}
Here's how you can invoke the new constructor:
Time justBeforeMidnight = new Time(11, 59, 59);
this
keywordThe this
keyword is a reference variable in Java that refers to the . You can use this
the same way you use the name of any other object. For example, you can read and write the instance variables of this
, and you can pass this
as an argument to other methods. But you do not declare this
, and you can’t make an assignment to it.
In the following version of the constructor, the names and types of the parameters are the same as the instance variables (parameters don’t have to use the same names, but that’s a common style). As a result, the parameters shadow (or hide) the instance variables, so the keyword this
is necessary to tell them apart.
public Time(int hour, int minute, int second) {
this.hour = hour;
this.minute = minute;
this.second = second;
}
this
can be used to refer to a constructor of a class within the same class too.
In this example the constructor Time()
uses the this
keyword to call its own constructor Time(int, int, int)
public Time() {
this(0, 0, 0); // call the overloaded constructor
}
public Time(int hour, int minute, int second) {
// ...
}
You can add methods to a class which can then be used from the objects of that class. These instance methods do not have the static
keyword in the method signature. Instance methods can access attributes of the class.
Here's how you can add a method to the Time
class to get the number of seconds passed till midnight.
public int secondsSinceMidnight() {
return hour*60*60 + minute*60 + second;
}
Here's how you can use that method.
Time t = new Time(0, 2, 5);
System.out.println(t.secondsSinceMidnight() + " seconds since midnight!");
Define a Circle
class so that the code given below produces the given output. The nature of the class is as follows:
private
):
int x
, int y
: represents the location of the circledouble radius
: the radius of the circleCircle()
: initializes x
, y
, radius
to 0Circle(int x, int y, double radius)
: initializes the attributes to the given valuesgetArea()
: int
int
value (not double
). Calculated as Pi * (radius)2double
to an int
using (int)
e.g., x = (int)2.25
gives x
the value 2
.Math.PI
to get the value of PiMath.pow()
to raise a number to a specific power e.g., Math.pow(3, 2)
calculates 3
2
public class Main {
public static void main(String[] args) {
Circle c = new Circle();
System.out.println(c.getArea());
c = new Circle(1, 2, 5);
System.out.println(c.getArea());
}
}
0
78
Hint
Can define getters and setters
As the instance variables of Time
are private, you can access them from within the Time
class only. To compensate, you can provide methods to access attributes:
public int getHour() {
return hour;
}
public int getMinute() {
return minute;
}
public int getSecond() {
return second;
}
Methods like these are formally called “accessors”, but more commonly referred to as getters. By convention, the method that gets a variable named something
is called getSomething
.
Similarly, you can provide setter methods to modify attributes of a Time
object:
public void setHour(int hour) {
this.hour = hour;
}
public void setMinute(int minute) {
this.minute = minute;
}
public void setSecond(int second) {
this.second = second;
}
Consider the Circle
class below:
public class Circle {
private int x;
private int y;
private double radius;
public Circle(){
this(0, 0, 0);
}
public Circle(int x, int y, double radius){
this.x = x;
this.y = y;
this.radius = radius;
}
public int getArea(){
double area = Math.PI * Math.pow(radius, 2);
return (int)area;
}
}
Update it as follows so that code given below produces the given output.
public class Main {
public static void main(String[] args) {
Circle c = new Circle(1,2, 5);
c.setX(4);
c.setY(5);
c.setRadius(6);
System.out.println("x : " + c.getX());
System.out.println("y : " + c.getY());
System.out.println("radius : " + c.getRadius());
System.out.println("area : " + c.getArea());
c.setRadius(-5);
System.out.println("radius : " + c.getRadius());
c = new Circle(1, 1, -4);
System.out.println("radius : " + c.getRadius());
}
}
x : 4
y : 5
radius : 6.0
area : 113
radius : 0.0
radius : 0.0
Hint
Guidance for the item(s) below:
Now that we know about classes, let's learn what class-level members are and how to use them in Java.
Can explain class-level members
While all objects of a class have the same attributes, each object has its own copy of the attribute value.
All Person
objects have the name
attribute but the value of that attribute varies between Person
objects.
However, some attributes are not suitable to be maintained by individual objects. Instead, they should be maintained centrally, shared by all objects of the class. They are like ‘global variables’ but attached to a specific class. Such variables whose value is shared by all instances of a class are called class-level attributes.
The attribute totalPersons
should be maintained centrally and shared by all Person
objects rather than copied at each Person
object.
Similarly, when a normal method is being called, a message is being sent to the receiving object and the result may depend on the receiving object.
Sending the getName()
message to the Adam
object results in the response "Adam"
while sending the same message to the Beth
object results in the response "Beth"
.
However, there can be methods related to a specific class but not suitable for sending messages to a specific object of that class. Such methods that are called using the class instead of a specific instance are called class-level methods.
The method getTotalPersons()
is not suitable to send to a specific Person
object because a specific object of the Person
class should not have to know about the total number of Person
objects.
Class-level attributes and methods are collectively called class-level members (also called static members sometimes because some programming languages use the keyword static
to identify class-level members). They are to be accessed using the class name rather than an instance of the class.
Can use class-level members
The content below is an extract from -- Java Tutorial, with slight adaptations.
When a number of objects are created from the same class blueprint, they each have their own distinct copies of instance variables. In the case of a Bicycle
class, the instance variables are gear, and speed. Each Bicycle object has its own values for these variables, stored in different memory locations.
Sometimes, you want to have variables that are common to all objects. This is accomplished with the static
modifier. Fields that have the static
modifier in their declaration are called static fields or class variables. They are associated with the class, rather than with any object. Every instance of the class shares a class variable, which is in one fixed location in memory. Any object can change the value of a class variable, but class variables can also be manipulated without creating an instance of the class.
Suppose you want to create a number of Bicycle objects and assign each a serial number, beginning with 1 for the first object. This ID number is unique to each object and is therefore an instance variable. At the same time, you need a field to keep track of how many Bicycle
objects have been created so that you know what ID to assign to the next one. Such a field is not related to any individual object, but to the class as a whole. For this you need a class variable, numberOfBicycles
, as follows:
public class Bicycle {
private int gear;
private int speed;
// an instance variable for the object ID
private int id;
// a class variable for the number of Bicycle
// objects instantiated
private static int numberOfBicycles = 0;
...
}
Class variables are referenced by the class name itself, as in Bicycle.numberOfBicycles
This makes it clear that they are class variables.
The Java programming language supports static methods as well as static variables. Static methods, which have the static
modifier in their declarations, should be invoked with the class name, without the need for creating an instance of the class, as in ClassName.methodName(args)
The static
modifier, in combination with the final
modifier, is also used to define constants. The final modifier indicates that the value of this field cannot change. For example, the following variable declaration defines a constant named PI
, whose value is an approximation of pi (the ratio of the circumference of a circle to its diameter):
static final double PI = 3.141592653589793;
Here is an example with class-level variables and class-level methods:
public class Bicycle {
private int gear;
private int speed;
private int id;
private static int numberOfBicycles = 0;
public Bicycle(int startSpeed, int startGear) {
gear = startGear;
speed = startSpeed;
numberOfBicycles++;
id = numberOfBicycles;
}
public int getID() {
return id;
}
public static int getNumberOfBicycles() {
return numberOfBicycles;
}
public int getGear(){
return gear;
}
public void setGear(int newValue) {
gear = newValue;
}
public int getSpeed() {
return speed;
}
// ...
}
Explanation of System.out.println(...)
:
out
is a class-level public attribute of the System
class.println
is an instance level method of the out
object.Consider the Circle
class below:
public class Circle {
private int x;
private int y;
private double radius;
public Circle(){
this(0, 0, 0);
}
public Circle(int x, int y, double radius){
setX(x);
setY(y);
setRadius(radius);
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = Math.max(radius, 0);
}
//TODO: Add your getMaxRadius() method here
}
Update it as follows so that code given below produces the given output.
maxRadius
variable to store the maximum radius value of the Circle
objects in existence thus far.getMaxRadius()
method that uses the above-mentioned variable to return the maximum radius value of the Circle
objects in existence thus far.setRadius
method to ensure maxRadius
value is updated (if needed) when the radius of an existing Circle
object is changed.public class Main {
public static void main(String[] args) {
Circle c = new Circle();
System.out.println("max radius used so far : " + Circle.getMaxRadius());
c = new Circle(0, 0, 10);
System.out.println("max radius used so far : " + Circle.getMaxRadius());
c = new Circle(0, 0, -15);
System.out.println("max radius used so far : " + Circle.getMaxRadius());
c.setRadius(12);
System.out.println("max radius used so far : " + Circle.getMaxRadius());
}
}
max radius used so far : 0.0
max radius used so far : 10.0
max radius used so far : 10.0
max radius used so far : 12.0
Hint
Guidance for the item(s) below:
You are going to start your project pretty soon. As you are adding feature increments to your project, you'll need a way to keep track of all those changes too. The tool we are going to use for that is called Git.
Let's jump in and learn how to get started using Git in your own computer.
Guidance for the item(s) below:
First, let's learn a bit about tracking the change history of a project in general, at a higher level.
Can explain revision control
Revision control is the process of managing multiple versions of a piece of information. In its simplest form, this is something that many people do by hand: every time you modify a file, save it under a new name that contains a number, each one higher than the number of the preceding version.
Manually managing multiple versions of even a single file is an error-prone task, though, so software tools to help automate this process have long been available. The earliest automated revision control tools were intended to help a single user to manage revisions of a single file. Over the past few decades, the scope of revision control tools has expanded greatly; they now manage multiple files, and help multiple people to work together. The best modern revision control tools have no problem coping with thousands of people working together on projects that consist of hundreds of thousands of files.
Revision control software will track the history and evolution of your project, so you don't have to. For every change, you'll have a log of who made it; why they made it; when they made it; and what the change was.
Revision control software makes it easier for you to collaborate when you're working with other people. For example, when people more or less simultaneously make potentially incompatible changes, the software will help you to identify and resolve those conflicts.
It can help you to recover from mistakes. If you make a change that later turns out to be an error, you can revert to an earlier version of one or more files. In fact, a really good revision control tool will even help you to efficiently figure out exactly when a problem was introduced.
It will help you to work simultaneously on, and manage the drift between, multiple versions of your project. Most of these reasons are equally valid, at least in theory, whether you're working on a project by yourself, or with a hundred other people.
-- [adapted from bryan-mercurial-guide]
Revision: A revision (some seem to use it interchangeably with version while others seem to distinguish the two -- here, let us treat them as the same, for simplicity) is a state of a piece of information at a specific time that is a result of some changes to it e.g., if you modify the code and save the file, you have a new revision (or a new version) of that file.
RCS: Revision control software are the software tools that automate the process of Revision Control i.e. managing revisions of software artifacts.
Revision control software are also known as Version Control Software (VCS), and by a few other names.
Git is the most widely used RCS today. Other RCS tools include Mercurial, Subversion (SVN), Perforce, CVS (Concurrent Versions System), Bazaar, TFS (Team Foundation Server), and Clearcase.
Github is a web-based project hosting platform for projects using Git for revision control. Other similar services include GitLab, BitBucket, and SourceForge.
Can explain repositories
The repository is the database that stores the revision history. Suppose you want to apply revision control on files in a directory called ProjectFoo
. In that case, you need to set up a repo (short for repository) in the ProjectFoo
directory, which is referred to as the working directory of the repo. For example, Git uses a hidden folder named .git
inside the working directory, to store the database of the working directory's revision history.
Repository (repo for short): The database of the history of a directory being tracked by an RCS software (e.g. Git).
Working directory: the root directory revision-controlled by Git (e.g., the directory in which the repo was initialized).
You can have multiple repos in your computer, each repo revision-controlling files of a different working directory, for examples, files of different projects.
Guidance for the item(s) below:
Now that we know what RCS is in general, we can try to practice it ourselves using a specific tool i.e., Git.
The following section gives a specific scenario that includes the steps of initializing a Git repository.
If you are new to Git, you are highly recommended to follow those steps in your own computer to get some hands-on practice as you learn Git usage.
Can create a local Git repo
Let's take your first few steps in your Git journey.
0. Take a peek at the full picture(?). Optionally, if you are the sort who prefers to have some sense of the full picture before you get into the nitty-gritty details, watch the video in the panel below:
Git Overview
1. First, install Sourcetree (installation instructions), which is Git + a GUI for Git. If you prefer to use Git via the command line (i.e., without a GUI), you can install Git instead.
2. Next, create a directory for the repo (e.g., a directory named things
).
3. Then, initialize a repository in that directory.
Windows: Click File
→ Clone/New…
. Click on Create
button.
Mac: New...
→ Create New Repository
.
Enter the location of the directory (Windows version shown below) and click Create
.
Go to the things
folder and observe how a hidden folder .git
has been created.
Windows: you might have to configure Windows Explorer to show hidden files.
Open a Git Bash Terminal.
If you installed Sourcetree, you can click the Terminal
button to open a GitBash terminal (on a Linux/Mac environment, even a regular terminal should do).
Navigate to the things
directory.
Use the command git init
which should initialize the repo.
$ cd /c/repos/things
$ git init
Initialized empty Git repository in c:/repos/things/.git/
You can use the list all command ls -a
to view all files, which should show the .git
directory that was created by the previous command.
$ ls -a
. .. .git
You can also use the git status
command to check the status of the newly-created repo. It should respond with something like the following:
$ git status
# On branch master
#
# No commits yet
#
nothing to commit (create/copy files and use "git add" to track)
As you see above, this textbook explains how to use Git via Sourcetree (a GUI client) as well as via the Git CLI. If you are new to Git, we recommend you learn both the GUI method and the CLI method -- The GUI method will help you visualize the result better while the CLI method is more universal (i.e., you will not be tied to any GUI) and more flexible/powerful.
It is fine to learn the CLI way only (using Sourcetree is optional), especially if you normally prefer to work with CLI user GUI.
If you are new to Git, we caution you against using Git or GitHub features that come with the IDE as it is better to learn Git independent of any other tool. Similarly, using clients provided by GitHub (e.g., GitHub Desktop GUI client) will make it harder for you to separate Git features from GitHub features.
Guidance for the item(s) below:
For the next few sections, the drill is the same: first learn the high-level explanation of a revision control concept, and then follow the given scenarios yourself to learn how to apply that concept using Git.
Can explain saving history
In a repo, you can specify which files to track and which files to ignore. Some files such as temporary log files created during the build/test process should not be revision-controlled.
Committing saves a snapshot of the current state of the tracked files in the revision control history. Such a snapshot is also called a commit (i.e. the noun).
Commit (noun): a change (aka a revision) saved in the Git revision history.
(verb): the act of creating a commit i.e., saving a change in the working directory into the Git revision history.
When ready to commit, you first add the specific changes you want to commit to a staging area. This intermediate step allows you to commit only some changes while saving other changes for a later commit.
Stage (verb): Instructing Git to prepare a file for committing.
Can commit using Git
After initializing a repository, Git can help you with revision controlling files inside the working directory. However, it is not automatic. You need to tell Git which of your changes (aka revisions) should be committed to its memory for later use. Saving changes into Git's memory in that way is called committing and a change saved to the revision history is called a commit.
Here are the steps you can follow to learn how to create Git commits:
1. Do some changes to the content inside the working directory e.g., create a file named fruits.txt
in the things
directory and add some dummy text to it.
2. Observe how the file is detected by Git.
The file is shown as ‘unstaged’.
You can use the git status
command to check the status of the working directory.
$ git status
# On branch master
#
# No commits yet
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# a.txt
nothing added to commit but untracked files present (use "git add" to track)
3. Stage the changes to commit: Although Git has detected the file in the working directory, it will not do anything with the file unless you tell it to. Suppose you want to commit the current changes to the file. First, you should stage the file, which is how you tell Git which changes you want to include in the next commit.
Select the fruits.txt
and click on the Stage Selected
button.
fruits.txt
should appear in the Staged files
panel now.
If Sourcetree shows a \ No newline at the end of the file
message below the staged lines (i.e., below the cherries
line in the above screenshot), that is because you did not hit enter after entering the last line of the file (hence, Git is not sure if that line is complete). To rectify, move the cursor to end of the last line in that file and hit enter (like you are adding a blank line below it). This new change will now appear as an 'unstaged' change. Stage it as well.
You can use the stage
or the add
command (they are synonyms, add
is the more popular choice) to stage files.
$ git add fruits.txt
$ git status
# On branch master
#
# No commits yet
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
# new file: fruits.txt
#
4. Commit the staged version of fruits.txt
.
Click the Commit
button, enter a commit message e.g. add fruits.txt
into the text box, and click Commit
.
Use the commit
command to commit. The -m
switch is used to specify the commit message.
$ git commit -m "Add fruits.txt"
You can use the log
command to see the commit history.
$ git log
commit 8fd30a6910efb28bb258cd01be93e481caeab846
Author: … < … @... >
Date: Wed Jul 5 16:06:28 2017 +0800
Add fruits.txt
Note the existence of something called the master
branch. Git uses a mechanism called branches to facilitate evolving file content in parallel (we'll learn git branching in a later topic). Furthermore, Git auto-creates a branch named master
on which the commits go on by default.
Expand the BRANCHES
menu and click on the master
to view the history graph, which contains only one node at the moment, representing the commit you just added. Also note a label master
attached to the commit.
This label points to the latest commit on the master
branch.
Run the git status
command and note how the output contains the phrase on branch master
.
5. Do a few more commits.
Make some changes to fruits.txt
(e.g. add some text and delete some text). Stage the changes, and commit the changes using the same steps you followed before. You should end up with something like this.
Next, add two more files colors.txt
and shapes.txt
to the same working directory. Add a third commit to record the current state of the working directory.
You can decide what to stage and what to leave unstaged. When staging changes to commit, you can leave some files unstaged, if you wish to not include them in the next commit. In fact, Git even allows some changes in a file to be staged, while others changes in the same file to be unstaged. This flexibility is particularly useful when you want to put all related changes into a commit while leaving out unrelated changes.
6. See the revision graph: Note how commits form a path-like structure aka the revision tree/graph. In the revision graph, each commit is shown as linked to its 'parent' commit (i.e., the commit before it).
To see the revision graph, click on the History
item (listed under the WORKSPACE
section) on the menu on the right edge of Sourcetree.
The gitk
command opens a rudimentary graphical view of the revision graph.
How do undo/delete a commit?
To undo the last commit, right-click on the commit just before it, and choose Reset current branch to this commit
.
In the next dialog, choose the mode Mixed - keep working copy but reset index
option. This will make the offending commit disappear but will keep the changes that you included in that commit intact.
If you use the Soft - ...
mode instead, the last commit will be undone as before, but the changes included in that commit will stay in the staging area.
To delete the last commit entirely (i.e., undo the commit and also discard the changes included in that commit), do as above but choose the Hard - ...
mode instead.
To undo/delete last n commits, right-click on the commit just before the last n commits, and do as above.
To undo the last commit, but keep the changes in the staging area, use the following command.
$ git reset --soft HEAD~1
To undo the last commit, and remove the changes from the staging area (but not discard the changes), used --mixed
instead of --soft
.
$ git reset --mixed HEAD~1
To delete the last commit entirely (i.e., undo the commit and also discard the changes included in that commit), do as above but use the --hard
flag instead (i.e., do a hard reset).
$ git reset --hard HEAD~1
To undo/delete last n commits: HEAD~1
is used to tell get you are targeting the commit one position before the latest commit -- in this case the target commit is the one we want to reset to, not the one we want to undo (as the command used is reset
). To undo/delete two last commits, you can use HEAD~2
, and so on.
Can set Git to ignore files
Often, there are files inside the Git working folder that you don't want to revision-control e.g., temporary log files. Follow the steps below to learn how to configure Git to ignore such files.
1. Add a file into your repo's working folder that you supposedly don't want to revision-control e.g., a file named temp.txt
. Observe how Git has detected the new file.
2. Configure Git to ignore that file:
The file should be currently listed under Unstaged files
. Right-click it and choose Ignore…
. Choose Ignore exact filename(s)
and click OK
.
Observe that a file named .gitignore
has been created in the working directory root and has the following line in it.
temp.txt
Create a file named .gitignore
in the working directory root and add the following line in it.
temp.txt
The .gitignore
file
The .gitignore
file tells Git which files to ignore when tracking revision history. That file itself can be either revision controlled or ignored.
To version control it (the more common choice – which allows you to track how the .gitignore
file changes over time), simply commit it as you would commit any other file.
To ignore it, follow the same steps you followed above when you set Git to ignore the temp.txt
file.
It supports file patterns e.g., adding temp/*.tmp
to the .gitignore
file prevents Git from tracking any .tmp
files in the temp
directory.
More information about the .gitignore
file: git-scm.com/docs/gitignore
Files recommended to be omitted from version control
*.class
, *.jar
, *.exe
(reasons: 1. no need to version control these files as they can be generated again from the source code 2. Revision control systems are optimized for tracking text-based files, not binary files. Guidance for the item(s) below:
Previously, you learned how to save revision history in your local repository, in the form of commits. Next, let us use how to make use of that history.
Can explain basic concepts of how RCS history is used
RCS tools store the history of the working directory as a series of commits. This means you should commit after each change that you want the RCS to 'remember'.
Each commit in a repo is a recorded point in the history of the project that is uniquely identified by an auto-generated hash e.g. a16043703f28e5b3dab95915f5c5e5bf4fdc5fc1
.
You can tag a specific commit with a more easily identifiable name e.g. v1.0.2
.
To see what changed between two points of the history, you can ask the RCS tool to diff the two commits in concern.
To restore the state of the working directory at a point in the past, you can checkout the commit in concern. i.e., you can traverse the history of the working directory simply by checking out the commits you are interested in.
Can tag commits using Git
Each Git commit is uniquely identified by a hash e.g., d670460b4b4aece5915caf5c68d12f560a9fe3e4
. As you can imagine, using such an identifier is not very convenient for our day-to-day use. As a solution, Git allows adding a more human-readable tag to a commit e.g., v1.0-beta
.
Here's how you can tag a commit in a local repo:
Right-click on the commit (in the graphical revision graph) you want to tag and choose Tag…
.
Specify the tag name e.g. v1.0
and click Add Tag
.
The added tag will appear in the revision graph view.
To add a tag to the current commit as v1.0
:
$ git tag –a v1.0
To view tags:
$ git tag
To learn how to add a tag to a past commit, go to the ‘Git Basics – Tagging’ page of the git-scm book and refer the ‘Tagging Later’ section.
After adding a tag to a commit, you can use the tag to refer to that commit, as an alternative to using the hash.
Tags are different from commit messages, in purpose and in form. A commit message is a description of the commit that is part of the commit itself. A tags is a short name for a commit, which exists as a separate entity that points to a commit.
Can compare git revisions
Git can show you what changed in each commit.
To see which files changed in a commit, click on the commit. To see what changed in a specific file in that commit, click on the file name.
$ git show < part-of-commit-hash >
Example:
$ git show 5bc0e306
commit 5bc0e30635a754908dbdd3d2d833756cc4b52ef3
Author: … < … >
Date: Sat Jul 8 16:50:27 2017 +0800
fruits.txt: replace banana with berries
diff --git a/fruits.txt b/fruits.txt
index 15b57f7..17f4528 100644
--- a/fruits.txt
+++ b/fruits.txt
@@ -1,3 +1,3 @@
apples
-bananas
+berries
cherries
Git can also show you the difference between two points in the history of the repo.
Select the two points you want to compare using Ctrl+Click
. The differences between the two selected versions will show up in the bottom half of Sourcetree, as shown in the screenshot below.
The same method can be used to compare the current state of the working directory (which might have uncommitted changes) to a point in the history.
The diff
command can be used to view the differences between two points of the history.
git diff
: shows the changes (uncommitted) since the last commit.git diff 0023cdd..fcd6199
: shows the changes between the points indicated by commit hashes.git diff v1.0..HEAD
: shows changes that happened from the commit tagged as v1.0
to the most recent commit.Can load a specific version of a Git repo
Git can load a specific version of the history to the working directory. Note that if you have uncommitted changes in the working directory, you need to stash them first to prevent them from being overwritten.
Double-click the commit you want to load to the working directory, or right-click on that commit and choose Checkout...
.
Click OK
to the warning about ‘detached HEAD’ (similar to below).
The specified version is now loaded to the working folder, as indicated by the HEAD
label. HEAD
is a reference to the currently checked out commit.
If you checkout a commit that comes before the commit in which you added the .gitignore
file, Git will now show ignored files as ‘unstaged modifications’ because at that stage Git hasn’t been told to ignore those files.
To go back to the latest commit, double-click it.
Use the checkout <commit-identifier>
command to change the working directory to the state it was in at a specific past commit.
git checkout v1.0
: loads the state as at commit tagged v1.0
git checkout 0023cdd
: loads the state as at commit with the hash 0023cdd
git checkout HEAD~2
: loads the state that is 2 commits behind the most recent commitFor now, you can ignore the warning about ‘detached HEAD’.