This book chapter assumes you are familiar with basic C++ programming. It provides a crash course to help you migrate from C++ to Java.
This chapter borrows heavily from the excellent book ThinkJava by Allen Downey and Chris Mayfield. As required by the terms of reuse of that book, this chapter is released under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License and not under the MIT license as the rest of this book.
Some conventions used in this chapter:
icon marks the description of an aspect of Java that works mostly similar to C++
icon marks the description of an aspect of Java that is distinctly different from C++
Other resources used:
Java was conceived by James Gosling and his team at Sun Microsystems in 1991.
Java is directly related to both C and C++. Java inherits its syntax from C. Its object model is adapted from C++. --Java: A Beginner’s Guide, by Oracle
Fun fact: The language was initially called Oak after an oak tree that stood outside Gosling's office. Later the project went by the name Green and was finally renamed Java, from Java coffee. --Wikipedia
Oracle became the owner of Java in 2010, when it acquired Sun Microsystems.
Java has remained the most popular language in the world for several years now (as at July 2018), according to the TIOBE index.
Java is both and . Instead of translating programs directly into machine language, the Java compiler generates byte code. Byte code is portable, so it is possible to compile a Java program on one machine, transfer the byte code to another machine, and run the byte code on the other machine. That’s why Java is considered a platform independent technology, aka WORA (Write Once Run Anywhere). The interpreter that runs byte code is called a “Java Virtual Machine” (JVM).
Java technology is both a programming language and a platform. The Java programming language is a high-level object-oriented language that has a particular syntax and style. A Java platform is a particular environment in which Java programming language applications run. --Oracle
According to the Official Java documentation, there are four platforms of the Java programming language:
Java Platform, Standard Edition (Java SE): Contains the core functionality of the Java programming language.
Java Platform, Enterprise Edition (Java EE): For developing and running large-scale enterprise applications. Built on top of Java SE.
Java Platform, Micro Edition (Java ME): For Java programming language applications meant for small devices, like mobile phones. A subset of Java SE.
JavaFX: For creating applications with graphical user interfaces. Can work with the other three above.
This book chapter uses the Java SE edition unless stated otherwise.
To run Java programs, you only need to have a recent version of the Java Runtime Environment (JRE) installed in your device.
If you want to develop applications for Java, download and install a recent version of the Java Development Kit (JDK), which includes the JRE as well as additional resources needed to develop Java applications.
In Java, the HelloWorld program looks like this:
public class HelloWorld {
public static void main(String[] args) {
// generate some simple output
System.out.println("Hello, World!");
}
}
For reference, the equivalent C++ code is given below:
#include <iostream>
using namespace std;
int main() {
// generate some simple output
cout << "Hello, World!";
return 0;
}
This HelloWorld Java program defines one method named main: public static void main(String[] args)
System.out.println()
displays a given text on the screen.
Some similarities:
SYSTEM
is different from System
.public
is an access modifier that indicates the method is accessible from outside this class. Similarly, private
access modifier indicates that a method/attribute is not accessible outside the class.static
indicates this method is defined as a class-level member. Do not worry if you don’t know what that means. It will be explained later.void
indicates that the method does not return anything.main
method is special as it is the method that Java executes when you run a Java program.HelloWorld
.{
and }
) to group things together.//
is a comment. You can use //
for single line comments and /* ... */
for multi-line comments in Java code.Some differences:
main
method will not work unless it is inside the HelloWorld
class.HelloWorld.java
.#include <iostream>
. The library files needed by the HelloWorld code is available by default without having to "include" them explicitly.return 0
at the end of the main method to indicate the execution was successful. It is considered as a successful execution unless an error is signalled specifically.To compile the HelloWorld program, open a command console, navigate to the folder containing the file, and run the following command.
>_ javac HelloWorld.java
If the compilation is successful, you should see a file HelloWorld.class
. That file contains the byte code for your program. If the compilation is unsuccessful, you will be notified of the compile-time errors.
Notes:
javac
is the java compiler that you get when you install the JDK.javac.exe
should be in the PATH
system variable).To run the HelloWorld program, in a command console, run the following command from the folder containing HelloWorld.class
file.
>_ java HelloWorld
Notes:
java
in the command above refers to the Java interpreter installed in your computer.javac
, your console should be able to find the java executable.When you run a Java program, you can encounter a . These errors are also called "exceptions" because they usually indicate that something exceptional (and bad) has happened. When a run-time error occurs, the interpreter displays an error message that explains what happened and where.
For example, modify the HelloWorld code to include the following line, compile it again, and run it.
System.out.println(5/0);
You should get a message like this:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Hello.main(Hello.java:5)
Integrated Development Environments (IDEs) can automate the intermediate step of compiling. They usually have a Run button which compiles the code first and then runs it.
Example IDEs:
A. Install Java in your computer, if you haven't done so already. Java 17 is recommended.
Mac users can follow this se-edu/guide to install Java.
Ensure the installation is correct, as follows.
java -version
. Confirm that the output indicates that you have Java 17. Sample output:C:\Users\john>java -version
java version "17._._" ...
...
...
javac
and ensure it results in a help message.
If it outputs an error message such as javac is not recognized as internal or external command
, you need to configure the PATH
variable of your OS so that the OS know where your javac
executable is.B. Write, compile and run a small Java program (e.g., a HelloWorld program) in your computer. You can use any code editor to write the program but use the command prompt to compile and run the program. Here are suggested steps:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
HelloWorld.java
(ensure there the case match exactly and there is no .txt
at the end).c:> cd my-java-code
c:\my-java-code>
javac HelloWorld.java
. Ensure there are no compile errors.
Note how a file called HelloWorld.class
has been created in that folder.c:\my-java-code>javac HelloWorld.java
java HelloWorld
(no .java
at the end).
The output should be Hello, World!
.c:\my-java-code>java HelloWorld
Hello, World!
C. Modify the code to print something else, save, compile, and run the program again.
Java has a number of primitive data types, as given below:
byte
: an integer in the range -128 to 127 (inclusive).short
: an integer in the range -32,768 to 32,767 (inclusive).int
: an integer in the range -231 to 231-1.long
: An integer in the range -263 to 263-1.float
: a single-precision 32-bit IEEE 754 floating point. This data type should never be used for precise values, such as currency. For that, you will need to use the java.math.BigDecimal class instead.double
: a double-precision 64-bit IEEE 754 floating point. For decimal values, this data type is generally the default choice. This data type should never be used for precise values, such as currency.boolean
: has only two possible values: true
and false
.char
: The char data type is a single 16-bit Unicode character. It has a minimum value of '\u0000'
(or 0
) and a maximum value of '\uffff'
(or 65,535
inclusive).String
type (a peek) Java has a built-in type called String
to represent strings. While String
is not a primitive type, strings are used often. String
values are demarcated by enclosing in a pair of double quotes (e.g., "Hello"
). You can use the +
operator to concatenate strings (e.g., "Hello " + "!"
).
You’ll learn more about strings in a later section.
Java is a statically-typed language in that variables have a fixed type. Here are some examples of declaring variables and assigning values to them.
int x;
x = 5;
int hour = 11;
boolean isCorrect = true;
char capitalC = 'C';
byte b = 100;
short s = 10000;
int i = 100000;
You can use any name starting with a letter, underscore, or $ as a variable name but you cannot use Java keywords as variables names.
You can display the value of a variable using System.out.print
or System.out.println
(the latter goes to the next line after printing). To output multiple values on the same line, it’s common to use several print
statements followed by println
at the end.
int hour = 11;
int minute = 59;
System.out.print("The current time is ");
System.out.print(hour);
System.out.print(":");
System.out.print(minute);
System.out.println("."); //use println here to complete the line
System.out.println("done");
The current time is 11:59.
done
Use the keyword final
to indicate that the variable value, once assigned, should not be allowed to change later i.e., act like a ‘constant’. By convention, names for constants are all uppercase, with the underscore character (_
) between words.
final double CM_PER_INCH = 2.54;
Java supports the usual arithmetic operators, given below.
Operator | Description | Examples |
---|---|---|
+ | Additive operator | 2 + 3 5 |
- | Subtraction operator | 4 - 1 3 |
* | Multiplication operator | 2 * 3 6 |
/ | Division operator | 5 / 2 2 but 5.0 / 2 2.5 |
% | Remainder operator | 5 % 2 1 |
The following program uses some operators as part of an expression hour * 60 + minute
:
int hour = 11;
int minute = 59;
System.out.print("Number of minutes since midnight: ");
System.out.println(hour * 60 + minute);
Number of minutes since midnight: 719
When an expression has multiple operators, normal operator precedence rules apply. Furthermore, you can use parentheses to specify a precise precedence.
Examples:
4 * 5 - 1
19
(*
has higher precedence than -
)4 * (5 - 1)
16
(parentheses (
)
have higher precedence than *
)Java does not allow .
The unary operators require only one operand; they perform various operations such as incrementing/decrementing a value by one, negating an expression, or inverting the value of a boolean.-- Java Tutorial
Operator | Description -- Java Tutorial | example |
---|---|---|
+ | Unary plus operator; indicates positive value (numbers are positive without this, however) | x = 5; y = +x y is 5 |
- | Unary minus operator; negates an expression | x = 5; y = -x y is -5 |
++ | Increment operator; increments a value by 1 | i = 5; i++ i is 6 |
-- | Decrement operator; decrements a value by 1 | i = 5; i-- i is 4 |
! | Logical complement operator; inverts the value of a boolean | foo = true; bar = !foo bar is false |
Relational operators are used to check conditions like whether two values are equal, or whether one is greater than the other. The following expressions show how they are used:
Operator | Description | example true | example false |
---|---|---|---|
x == y | x is equal to y | 5 == 5 | 5 == 6 |
x != y | x is not equal to y | 5 != 6 | 5 != 5 |
x > y | x is greater than y | 7 > 6 | 5 > 6 |
x < y | x is less than y | 5 < 6 | 7 < 6 |
x >= y | x is greater than or equal to y | 5 >= 5 | 4 >= 5 |
x <= y | x is less than or equal to y | 4 <= 5 | 6 <= 5 |
The result of a relational operator is a boolean value.
Java has three conditional operators that are used to operate on boolean values.
Operator | Description | example true | example false |
---|---|---|---|
&& | and | true && true true | true && false false |
|| | or | true || false true | false || false false |
! | not | not false | not true |
Arrays are indicated using square brackets ([]
). To create the array itself, you have to use the new
operator. Here are some example array declarations:
int[] counts;
counts = new int[4]; // create an int array of size 4
int size = 5;
double[] values;
values = new double[size]; //use a variable for the size
double[] prices = new double[size]; // declare and create at the same time
Alternatively, you can use the shortcut syntax to create and initialize an array:
int[] values = {1, 2, 3, 4, 5, 6}; int[] anArray = { 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 };
-- Java Tutorial
The []
operator selects elements from an array. Array elements .
int[] counts = new int[4];
System.out.println("The first element is " + counts[0]);
counts[0] = 7; // set the element at index 0 to be 7
counts[1] = counts[0] * 2;
counts[2]++; // increment value at index 2
A Java array is aware of its size. A Java array prevents a programmer from indexing the array out of bounds. If the index is negative or not present in the array, the result is an error named ArrayIndexOutOfBoundsException
.
int[] scores = new int[4];
System.out.println(scores.length) // prints 4
scores[5] = 0; // causes an exception
4
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at Main.main(Main.java:6)
It is also possible to create arrays of more than one dimension:
String[][] names = { {"Mr. ", "Mrs. ", "Ms. "}, {"Smith", "Jones"} }; System.out.println(names[0][0] + names[1][0]); // Mr. Smith System.out.println(names[0][2] + names[1][1]); // Ms. Jones
-- Java Tutorial
The args
parameter of the main
method is an array of Strings containing command line arguments supplied (if any) when running the program.
public class Foo{
public static void main(String[] args) {
System.out.println(args[0]);
}
}
You can run this program (after compiling it first) from the command line by typing:
>_ java Foo abc
abc
Write a Java program that takes two command line arguments and prints true
or false
to indicate if the two arguments have the same value. Follow the sample output given below.
class Main {
public static void main(String[] args) {
// add your code here
}
}
>_ java Main adam eve
Words given: adam, eve
They are the same: false
>_ java Main eve eve
Words given: eve, eve
They are the same: true
Use the following technique to compare two Strings
(i.e., don't use ==
). Reason: to be covered in a later topic.
String x = "foo";
boolean isSame = x.equals("bar") // false
isSame = x.equals("foo") // true
Hint
if-else
statements Java supports the usual forms of if
statements:
if (x > 0) {
System.out.println("x is positive");
}
if (x % 2 == 0) {
System.out.println("x is even");
} else {
System.out.println("x is odd");
}
if (x > 0) {
System.out.println("x is positive");
} else if (x < 0) {
System.out.println("x is negative");
} else {
System.out.println("x is zero");
}
if (x == 0) {
System.out.println("x is zero");
} else {
if (x > 0) {
System.out.println("x is positive");
} else {
System.out.println("x is negative");
}
}
The braces are optional (but recommended) for branches that have only one statement. So we could have written the previous example this way ( Bad):
if (x % 2 == 0)
System.out.println("x is even");
else
System.out.println("x is odd");
switch
statementsThe switch
statement can have a number of possible execution paths. A switch
works with the byte
, short
, char
, and int
primitive data types. It also works with enums, String
.
Here is an example (adapted from -- Java Tutorial):
public class SwitchDemo {
public static void main(String[] args) {
int month = 8;
String monthString;
switch (month) {
case 1: monthString = "January";
break;
case 2: monthString = "February";
break;
case 3: monthString = "March";
break;
case 4: monthString = "April";
break;
case 5: monthString = "May";
break;
case 6: monthString = "June";
break;
case 7: monthString = "July";
break;
case 8: monthString = "August";
break;
case 9: monthString = "September";
break;
case 10: monthString = "October";
break;
case 11: monthString = "November";
break;
case 12: monthString = "December";
break;
default: monthString = "Invalid month";
break;
}
System.out.println(monthString);
}
}
August
Greeter
Write a Java program that takes a letter grade e.g., A+
as a command line argument and prints the CAP (aka GPA) value for that grade.
Use a switch
statement in your code.
A+ | A | A- | B+ | B | B- | C | Else |
---|---|---|---|---|---|---|---|
5.0 | 5.0 | 4.5 | 4.0 | 3.5 | 3.0 | 2.5 | 0 |
Follow the sample output given below.
>_ java GradeHelper B
CAP for grade B is 3.5
You can assume that the input is always in the correct format i.e., no need to handle invalid input cases.
Hint
Here’s an example of adding more methods to a class:
public class PrintTwice {
public static void printTwice(String s) {
System.out.println(s);
System.out.println(s);
}
public static void main(String[] args) {
String sentence = “Polly likes crackers”
printTwice(sentence);
}
}
Polly likes crackers
Polly likes crackers
By convention, method names should be named in the camelCase format.
Similar to the main
method, the printTwice
method is public
(i.e., it can be invoked from other classes) static
and void
.
A method can specify parameters. The printTwice
method above specifies a parameter of String
type. The main
method passes the argument "Polly likes crackers"
to that parameter.
The value provided as an argument must have the same type as the parameter. Sometimes Java can convert an argument from one type to another automatically. For example, if the method requires a double
, you can invoke it with an int
argument 5
and Java will automatically convert the argument to the equivalent value of type double 5.0
.
Because a variable declared inside a method only exists inside that method, such variables are called local variables. That applies to parameters of a method too. For example, In the code above, s
cannot be used inside main
because it is a parameter of the printTwice
method and can only be used inside that method. If you try to use s
inside main
, you’ll get a compiler error. Similarly, inside printTwice
there is no such thing as sentence
. That variable belongs to main
.
return
statements The return
statement allows you to terminate a method before you reach the end of it:
public static void printLogarithm(double x) {
if (x <= 0.0) {
System.out.println("Error: x must be positive.");
return;
}
double result = Math.log(x);
System.out.println("The log of x is " + result);
}
It can be used to return a value from a method too:
public class AreaCalculator{
public static double calculateArea(double radius) {
double result = 3.14 * radius * radius;
return result;
}
public static void main(String[] args) {
double area = calculateArea(12.5);
System.out.println(area);
}
}
Java methods can be overloaded. If two methods do the same thing, it is natural to give them the same name. Having more than one method with the same name is called overloading, and it is legal in Java as long as each version has a different method signature (the signature of the method is the method name and ordered list of parameter types) . For example, the following overloading of the method calculateArea
is allowed because the method signatures are different (i.e., calculateArea(double)
vs calculateArea(double, double)
).
public static double calculateArea(double radius) {
//...
}
public static double calculateArea(double height, double width) {
//...
}
Methods can be recursive. Here is an example in which the nLines
method calls itself recursively:
public static void nLines(int n) {
if (n > 0) {
System.out.println();
nLines(n - 1);
}
}
Add the following method to the class given below.
public static double getGradeCap(String grade)
: Returns the CAP (aka GPA) value of the given grade
. The mapping from grades to CAP is given below.A+ | A | A- | B+ | B | B- | C | Else |
---|---|---|---|---|---|---|---|
5.0 | 5.0 | 4.5 | 4.0 | 3.5 | 3.0 | 2.5 | 0 |
Do not change the code of the main
method!
public class Main {
// ADD YOUR CODE HERE
public static void main(String[] args) {
System.out.println("A+: " + getGradeCap("A+"));
System.out.println("B : " + getGradeCap("B"));
}
}
A+: 5.0
B : 3.5
Hint
Java has while
and for
constructs for looping.
while
loops Here is an example while
loop:
public static void countdown(int n) {
while (n > 0) {
System.out.println(n);
n = n - 1;
}
System.out.println("Blastoff!");
}
for
loops for
loops have the form:
for (initializer; condition; update) {
statement(s);
}
Here is an example:
public static void printTable(int rows) {
for (int i = 1; i <= rows; i = i + 1) {
printRow(i, rows);
}
}
do-while
loopsThe while
and for
statements are pretest loops; that is, they test the condition first and at the beginning of each pass through the loop. Java also provides a posttest loop: the do-while
statement. This type of loop is useful when you need to run the body of the loop at least once.
Here is an example (from -- Java Tutorial):
class DoWhileDemo {
public static void main(String[] args){
int count = 1;
do {
System.out.println("Count is: " + count);
count++;
} while (count < 11);
}
}
break
and continue
A break
statement exits the current loop.
Here is an example (from -- Java Tutorial):
class Main {
public static void main(String[] args) {
int[] numbers = new int[] { 1, 2, 3, 0, 4, 5, 0 };
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] == 0) {
break;
}
System.out.print(numbers[i]);
}
}
}
123
[Try the above code on Repl.it]
A continue
statement skips the remainder of the current iteration and moves to the next iteration of the loop.
Here is an example (from -- Java Tutorial):
public static void main(String[] args) {
int[] numbers = new int[] { 1, 2, 3, 0, 4, 5, 0 };
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] == 0) {
continue;
}
System.out.print(numbers[i]);
}
}
12345
for
loopsSince traversing arrays is so common, Java provides an alternative for-loop syntax that makes the code more compact. For example, consider a for loop that displays the elements of an array on separate lines:
for (int i = 0; i < values.length; i++) {
int value = values[i];
System.out.println(value);
}
We could rewrite the loop like this:
for (int value : values) {
System.out.println(value);
}
This statement is called an enhanced for
loop. You can read it as, “for each value in values”.
Notice how the single line for (int value : values)
replaces the first two lines of the standard for
loop.
Add the following method to the class given below.
public static double[] getMultipleGradeCaps(String[] grades)
: Returns the CAP (aka GPA) values of the given grades
. e.g., if the input was the array ["A+", "B"]
, the method returns [5.0, 3.5]
. The mapping from grades to CAP is given below.A+ | A | A- | B+ | B | B- | C | Else |
---|---|---|---|---|---|---|---|
5.0 | 5.0 | 4.5 | 4.0 | 3.5 | 3.0 | 2.5 | 0 |
Do not change the code of the main
method!
public class Main {
// ADD YOUR CODE HERE
public static double getGradeCap(String grade) {
double cap = 0;
switch (grade) {
case "A+":
case "A":
cap = 5.0;
break;
case "A-":
cap = 4.5;
break;
case "B+":
cap = 4.0;
break;
case "B":
cap = 3.5;
break;
case "B-":
cap = 3.0;
break;
case "C":
cap = 2.5;
break;
default:
}
return cap;
}
public static void main(String[] args) {
String[] grades = new String[]{"A+", "A", "A-"};
double[] caps = getMultipleGradeCaps(grades);
for (int i = 0; i < grades.length; i++) {
System.out.println(grades[i] + ":" + caps[i]);
}
}
}
A+:5.0
A:5.0
A-:4.5
Hint
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
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
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
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.
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 their 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 since 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
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
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
Java comes with a rich collection of classes that you can use. They form what is known as the Java API (Application Programming Interface). Each class in the API comes with documentation in a standard format.
String is a built-in Java class that you can use without importing. Given below are some useful String methods:
Strings provide a method named charAt
, which extracts a character. It returns a char
, a primitive type that stores an individual character (as opposed to strings of them).
String fruit = "banana";
char letter = fruit.charAt(0);
The argument 0 means that we want the letter at position 0. Like array indexes, string indexes start at 0, so the character assigned to letter
is 'b'
.
You can convert a string to an array of characters using the toCharArray
method.
char[] fruitChars = fruit.toCharArray()
Strings provide methods, toUpperCase
and toLowerCase
, that convert from uppercase to lowercase and back.
After these statements run, upperName
refers to the string "ALAN TURING"
but name
still refers to "Alan Turing"
.
String name = "Alan Turing";
String upperName = name.toUpperCase();
System.out.println(name);
System.out.println(upperName);
Alan Turing
ALAN TURING
Note that a string method cannot change the string object on which the method is invoked, because strings are . For example, when you invoke toUpperCase
on a string "abc"
, you get a new string object "ABC"
as the return value rather than the string "abc"
being changed to "ABC"
. As a result, for such string methods that seemingly modify the string but actually return a new string instead e.g., toLowerCase
, invoking the method has no effect if you don’t assign the return value to a variable.
String s = "Ada";
s.toUpperCase(); // no effect
s = s.toUpperCase(); // the correct way
Another useful method is replace
, which finds and replaces instances of one string within another.
This example replaces "Computer Science"
with "CS"
.
String text = "Computer Science is fun!";
text = text.replace("Computer Science", "CS");
System.out.println(text);
CS is fun!
The substring
method returns a new string that copies letters from an existing string, starting at the given index.
"banana".substring(0)
"banana"
"banana".substring(2)
"nana"
"banana".substring(6)
""
If it’s invoked with two arguments, they are treated as a start and end index:
"banana".substring(0, 3)
"ban"
"banana".substring(2, 5)
"nan"
"banana".substring(6, 6)
""
The indexOf
method searches for a single character (or a substring) in a string and returns the index of the first occurrence. The method returns -1
if there are no occurrences.
"banana".indexOf('a')
1
"banana".indexOf('a', 2)
3
searches for 'a'
, starting from position 2"banana".indexOf('x')
-1
"banana".indexOf("nan")
2
searches for the substring "nan"
To compare two strings, it is tempting to use the ==
and !=
operators.
String name1 = "Alan Turing";
String name2 = "Alan Turing";
System.out.println(name1 == name2);
This code compiles and runs, and most of the time it shows true
. But it is not correct. The problem is, , the ==
operator checks whether the two variables refer to the same object (by comparing the references). If you give it two different string objects that contain the same letters, it is supposed to yield false
because they are two distinct objects even if they contain the same text. However, because Java strings are immutable, in some cases (but not always) Java reuses existing string objects instead of creating multiple objects, which can cause the above code to yield true
. Therefore, it is not safe to use ==
to compare strings if your intention is to check if they contain the same text.
The right way to compare strings is with the equals
method.
This example invokes equals
on name1
and passes name2
as an argument. The equals
method returns true
if the strings contain the same characters; otherwise it returns false
.
if (name1.equals(name2)) {
System.out.println("The names are the same.");
}
If the strings differ, you can use compareTo
to see which comes first in alphabetical order. The return value from compareTo
is the difference between the first characters in the strings that differ. If the strings are equal, their difference is zero. If the first string (the one on which the method is invoked) comes first in the alphabet, the difference is negative. Otherwise, the difference is positive.
In this example, compareTo
returns positive 8, because the second letter of "Alan" comes 8 letters after the second letter of "Ada".
String name1 = "Alan";
String name2 = "Ada";
int diff = name1.compareTo(name2);
if (diff == 0) {
System.out.println("The names are the same.");
} else if (diff < 0) {
System.out.println("name1 comes before name2.");
} else if (diff > 0) {
System.out.println("name2 comes before name1.");
}
Both equals
and compareTo
are case-sensitive. The uppercase letters come before the lowercase letters, so "Ada"
comes before "ada"
. To check if two strings are similar irrespective of the differences in case, you can use the equalsIgnoreCase
method.
String s1 = "Apple";
String s2 = "apple";
System.out.println(s1.equals(s2)); //false
System.out.println(s1.equalsIgnoreCase(s2)); //true
Some more comparison-related String
methods:
contains
: checks if one string is a sub-string of the other e.g., Snapple
and app
startsWith
: checks if one string has the other as a substring at the beginning e.g., Apple
and App
endsWith
: checks if one string has the other as a substring at the end e.g., Crab
and ab
You can embed a special character e.g., line break, tab, backspace, etc. in a string using an escape sequence.
Escape sequence | meaning |
---|---|
\n | newline character |
\t | tab character |
\b | backspace character |
\f | form feed character |
\r | carriage return character |
\" | " (double quote) character |
\' | ' (single quote) character |
\\ | \ (back slash) character |
\uDDDD | character from the Unicode character set, by specifying the Unicode as four hex digits in the place of DDDD |
An example of using escape sequences to print some special characters.
System.out.println("First line\nSecond \"line\"");
First line
Second "line"
As the behavior of the \n
, the recommended way to print a line break is using the System.lineSeparator()
as it works the same in all platforms.
Using System.lineSeparator()
to print a line break.
System.out.println("First" + System.lineSeparator() + "Second");
First
Second
Sometimes programs need to create strings that are formatted in a certain way. String.format
takes a format specifier followed by a sequence of values and returns a new string formatted as specified.
The following method returns a time string in 12-hour format. The format specifier \%02d
means “two digit integer padded with zeros”, so timeString(19, 5)
returns the string "07:05 PM"
.
public static String timeString(int hour, int minute) {
String ampm;
if (hour < 12) {
ampm = "AM";
if (hour == 0) {
hour = 12; // midnight
}
} else {
ampm = "PM";
hour = hour - 12;
}
// returns "07:05 PM"
return String.format("%02d:%02d %s", hour, minute, ampm);
}
Implement the printPrice
method in the code below to produce the given output. Its behavior:
item
is a string in the format name--$price
i.e., a name and a price of an item separated using a --
e.g., banana--$3/50
NAME: price
where the name is in upper case. The price does not have a $
sign and has .
in place of the /
banana--$3/50
BANANA: 3.50
name
part of the input can have trailing/leading spaces which should be omitted from the output.
banana --$3/50
BANANA: 3.50
Do a Web search to find how to remove leading/trailing spaces. Suggested search terms java string remove leading trailing spaces
public class Main {
public static void printPrice(String item) {
// TODO: add your code here
}
public static void main(String[] args) {
printPrice("sandwich --$4/50");
printPrice(" soda --$10/00");
printPrice(" fries --$0/50");
}
}
SANDWICH: 4.50
SODA: 10.00
FRIES: 0.50
Hint
Primitive values (like int
, double
, and char
) do not provide methods.
For example, you can’t call equals
on an int
:
int i = 5;
System.out.println(i.equals(5)); // compiler error
But for each primitive type, there is a corresponding class in the Java library, called a wrapper class, as given in the table below. They are in the java.lang
package i.e., no need to import.
Primitive type | Wrapper class |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
Double d = new Double(2.5);
int i = d.intValue();
System.out.println(d);
System.out.println(i);
2.5
2
Each wrapper class defines constants MIN_VALUE
and MAX_VALUE
.
Accessing max and min values for integers:
System.out.println(Integer.MIN_VALUE + " : " + Integer.MAX_VALUE);
-2147483648 : 2147483647
Wrapper classes provide methods for strings to other types e.g., Integer.parseInt
converts a string to (you guessed it) an integer. The other wrapper classes provide similar methods, like Double.parseDouble
and Boolean.parseBoolean
.
Integer.parseInt("1234")
1234
Wrapper classes also provide toString
, which returns a string representation of a value.
Integer.toString(1234)
"1234"
Implement the printTotalScore
method in the code below to produce the given output. Its behavior:
values
is an array of strings, each string representing an integer e.g., ["5", "-1"]
["5", "-1"]
4
public class Main {
public static void printTotalScore(String[] values){
// TODO: add your code here
}
public static void main(String[] args) {
printTotalScore(new String[]{});
printTotalScore(new String[]{"0", "124", "-15"});
}
}
0
109
java.util.Arrays
provides methods for working with arrays. One of them, toString
, returns a string representation of an array. It also provides a copyOf
that copies an array.
Using Arrays.copyOf
and Arrays.toString
:
int[] a = new int[]{1,2,3,4};
int[] b = Arrays.copyOf(a, 3); // copy first three elements
System.out.println(Arrays.toString(b));
int[] c = Arrays.copyOf(a, a.length); // copy all elements
System.out.println(Arrays.toString(c));
[1, 2, 3]
[1, 2, 3, 4]
Implement the following two methods in the code below to produce the given output.
filterEmails(String[] items): String[]
items
is an array of strings each of which may be an email address or some other random stringString[]
containing email addresses that were in items
. Any string containing @
is considered as an email.["aaa@bbb", "xyz"]
["aaa@bbb"]
printItems(String[] items)
items
in the standard array format. e.g., ["aaa", "bbb"]
[aaa, bbb]
import java.util.Arrays;
public class Main {
public static String[] filterEmails(String[] items){
// TODO: add your code here
}
public static void printItems(String[] items){
// TODO: add your code here
}
public static void main(String[] args) {
printItems(filterEmails(new String[]{}));
printItems(filterEmails(new String[]{"abc"}));
printItems(filterEmails(new String[]{"adam@example.com", "aab", "john@example.com", "some@"}));
printItems(filterEmails(new String[]{"xyz", "@bee.com", "aab"}));
}
}
[]
[]
[adam@example.com, john@example.com, some@]
[@bee.com]
Hint
Scanner
is a class that provides methods for inputting words, numbers, and other data. Scanner
provides a method called nextLine
that reads a line of input from the keyboard and returns a String. The following example reads two lines and repeats them back to the user:
import java.util.Scanner;
public class Echo {
public static void main(String[] args) {
String line;
Scanner in = new Scanner(System.in);
System.out.print("Type something: ");
line = in.nextLine();
System.out.println("You said: " + line);
System.out.print("Type something else: ");
line = in.nextLine();
System.out.println("You also said: " + line);
}
}
Scanner
class normally reads inputs as strings but it can read in a specific type of input too.
The code below uses the nextInt
method of the Scanner
class to read an input as an integer.
Scanner in = new Scanner(System.in);
System.out.print("What is your age? ");
int age = in.nextInt();
in.nextLine(); // read the new-line character that follows the integer
System.out.print("What is your name? ");
String name = in.nextLine();
System.out.printf("Hello %s, age %d\n", name, age);
Write a program to ask the user for a description of overseas expenses (presumably, the user has just returned from an overseas trip) and calculate the total in local currency.
$1.0
= local $1.70
$amount
e.g., $1.50
Here is one example output:
Your expenses while overseas?beer $4.50 books $3.00 $5.00 for food, that's all
Expenses in overseas currency:[$4.50, $3.00, $5.00]
Total in local currency: $21.25
Here is another:
Your expenses while overseas?nothing. I lived off my friends all the time.
Expenses in overseas currency:[]
Total in local currency: $0.00
One more:
Your expenses while overseas? Just $10
Expenses in overseas currency:[$10]
Total in local currency: $17.00
Here's the skeleton code to use as the starting point:
public class Main {
// You can add more methods here
public static void main(String[] args) {
String line;
Scanner in = new Scanner(System.in);
System.out.print("Your expenses while overseas?");
// TODO: add your code here
}
}
You can use the split
method of the String
class to convert a sentence into an array of words. e.g.,
String sentence = "hello my dear";
// split using the space as the delimiter
String[] words = sentence.split(" ");
System.out.println(Arrays.toString(words));
[hello, my, dear]
Hint
Given below is an extract from the -- Java Tutorial, with slight adaptations.
A class that is derived from another class is called a subclass (also a derived class, extended class, or child class). The class from which the subclass is derived is called a superclass (also a base class or a parent class).
A subclass inherits all the members (fields, methods, and nested classes) from its superclass. Constructors are not members, so they are not inherited by subclasses, but the constructor of the superclass can be invoked from the subclass.
Every class has one and only one direct superclass (single inheritance), except the Object
class, which has no superclass, . In the absence of any other explicit superclass, every class is implicitly a subclass of Object
. Classes can be derived from classes that are derived from classes that are derived from classes, and so on, and ultimately derived from the topmost class, Object
. Such a class is said to be descended from all the classes in the inheritance chain stretching back to Object
. Java does not support multiple inheritance among classes.
The java.lang.Object
class defines and implements behavior common to all classes—including the ones that you write. In the Java platform, many classes derive directly from Object
, other classes derive from some of those classes, and so on, forming a single hierarchy of classes.
The keyword extends
indicates one class inheriting from another.
Here is the sample code for a possible implementation of a Bicycle
class and a MountainBike
class that is a subclass of the Bicycle
:
public class Bicycle {
public int gear;
public int speed;
public Bicycle(int startSpeed, int startGear) {
gear = startGear;
speed = startSpeed;
}
public void setGear(int newValue) {
gear = newValue;
}
public void applyBrake(int decrement) {
speed -= decrement;
}
public void speedUp(int increment) {
speed += increment;
}
}
public class MountainBike extends Bicycle {
// the MountainBike subclass adds one field
public int seatHeight;
// the MountainBike subclass has one constructor
public MountainBike(int startHeight, int startSpeed, int startGear) {
super(startSpeed, startGear);
seatHeight = startHeight;
}
// the MountainBike subclass adds one method
public void setHeight(int newValue) {
seatHeight = newValue;
}
}
A subclass inherits all the fields and methods of the superclass. In the example above, MountainBike
inherits all the fields and methods of Bicycle
and adds the field seatHeight
and a method to set it.
If your method overrides one of its superclass's methods, you can invoke the overridden method through the use of the keyword super
. You can also use super
to refer to a (although hiding fields is discouraged).
Consider this class, Superclass
and a subclass, called Subclass
, that overrides printMethod()
:
public class Superclass {
public void printMethod() {
System.out.println("Printed in Superclass.");
}
}
public class Subclass extends Superclass {
// overrides printMethod in Superclass
public void printMethod() {
super.printMethod();
System.out.println("Printed in Subclass");
}
public static void main(String[] args) {
Subclass s = new Subclass();
s.printMethod();
}
}
Printed in Superclass.
Printed in Subclass
Within Subclass
, the simple name printMethod()
refers to the one declared in Subclass
, which overrides the one in Superclass
. So, to refer to printMethod()
inherited from Superclass
, Subclass
must use a qualified name, using super
as shown.
A subclass constructor can invoke the superclass constructor. Invocation of a superclass constructor must be the first line in the subclass constructor.
The syntax for calling a superclass constructor is super()
(which invokes the no-argument constructor of the superclass) or super(parameters)
(to invoke the superclass constructor with a matching parameter list).
The following example illustrates how to use the super
keyword to invoke a superclass's constructor. Recall from the Bicycle
example that MountainBike
is a subclass of Bicycle
. Here is the MountainBike
(subclass) constructor that calls the superclass constructor and then adds some initialization code of its own (i.e., seatHeight = startHeight;
):
public MountainBike(
int startHeight, int startSpeed, int startGear) {
super(startSpeed, startGear);
seatHeight = startHeight;
}
Note: If a constructor does not explicitly invoke a superclass constructor, the Java compiler automatically inserts a call to the no-argument constructor of the superclass. If the superclass does not have a no-argument constructor, you will get a compile-time error. Object
does have such a constructor, so if Object
is the only superclass, there is no problem.
Access level modifiers determine whether other classes can use a particular field or invoke a particular method. Given below is a simplified version of Java access modifiers, assuming you have not yet started placing your classes in different packages i.e., all classes are placed in the root level. A full explanation of access modifiers is given in a later topic.
There are two levels of access control:
At the class level:
public
: the class is visible to all other classespublic
At the member level:
public
: the member is visible to all other classesprotected
: same as public
public
private
: the member is not visible to other classes (but can be accessed in its own class)A very beginner-friendly video about implementing Java inheritance.
Background: Suppose we are creating a software to manage various tasks a person has to do. Two types of such tasks are,
The Task
class is given below:
public class Task {
protected String description;
public Task(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
Todo
class that inherits from the Task
class.
boolean
field isDone
to indicate whether the todo is done or not done.isDone()
method to access the isDone
field and a setDone(boolean)
method to set the isDone
field.Deadline
class that inherits from the Todo
class that you implemented in the previous step. It should have,
String
field by
to store the details of when the task to be done e.g., Jan 25th 5pm
getBy()
method to access the value of the by
field, and a corresponding setBy(String)
method.Deadline(String description, String by)
The expected behavior of the two classes is as follows:
public class Main {
public static void main(String[] args) {
// create a todo task and print details
Todo t = new Todo("Read a good book");
System.out.println(t.getDescription());
System.out.println(t.isDone());
// change todo fields and print again
t.setDone(true);
System.out.println(t.isDone());
// create a deadline task and print details
Deadline d = new Deadline("Read textbook", "Nov 16");
System.out.println(d.getDescription());
System.out.println(d.isDone());
System.out.println(d.getBy());
// change deadline details and print again
d.setDone(true);
d.setBy("Postponed to Nov 18th");
System.out.println(d.isDone());
System.out.println(d.getBy());
}
}
Read a good book
false
true
Read textbook
false
Nov 16
true
Postponed to Nov 18th
Hint
As you know, all Java objects inherit from the Object
class. Let us look at some of the useful methods in the Object
class that can be used by other classes.
toString
methodEvery class inherits a toString
method from the Object
class that is used by Java to get a string representation of the object e.g., for printing. By default, it simply returns the type of the object and its address (in hexadecimal).
Suppose you defined a class called Time
, to represent a moment in time. If you create a Time
object and display it with println:
class Time {
int hours;
int minutes;
int seconds;
Time(int hours, int minutes, int seconds) {
this.hours = hours;
this.minutes = minutes;
this.seconds = seconds;
}
}
Time t = new Time(5, 20, 13);
System.out.println(t);
Time@80cc7c0
(the address part can vary)
You can override the toString
method in your classes to provide a more meaningful string representation of the objects of that class.
Here's an example of overriding the toString
method of the Time
class:
class Time{
//...
@Override
public String toString() {
return String.format("%02d:%02d:%02d\n",
this.hours, this.minutes, this.seconds);
}
}
Time t = new Time(5, 20, 13);
System.out.println(t);
05:20:13
@Override
is an optional annotation you can use to indicate that the method is overriding a method from the parent class.
equals
methodThere are two ways to check whether values are equal: the ==
operator and the equals
method. With objects you can use either one, but they are not the same.
==
operator checks whether objects are identical; that is, whether they are the same object.equals
method checks whether they are equivalent; that is, whether they have the same value.The definition of identity is always the same, so the ==
operator always does the same thing.
Consider the following variables:
Time time1 = new Time(9, 30, 0);
Time time2 = time1;
Time time3 = new Time(9, 30, 0);
time1
and time2
refer to the same object. Because they are identical, time1 == time2
is true
.time1
and time3
refer to different objects. Because they are not identical, time1 == time3
is false
.By default, the equals
method inherited from the Object
class does the same thing as ==
. As the definition of equivalence is different for different classes, you can override the equals
method to define your own criteria for equivalence of objects of your class.
Here's how you can override the equals
method of the Time
class to provide an equals
method that considers two Time
objects equivalent as long as they represent the same time of the day:
public class Time {
int hours;
int minutes;
int seconds;
// ...
@Override
public boolean equals(Object o) {
Time other = (Time) o;
return this.hours == other.hours
&& this.minutes == other.minutes
&& this.seconds == other.seconds;
}
}
Time t1 = new Time(5, 20, 13);
Time t2 = new Time(5, 20, 13);
System.out.println(t1 == t2);
System.out.println(t1.equals(t2));
false
true
Note that a proper equals
method implementation is more complex than the example above. See the article How to Implement Java’s equals Method Correctly by Nicolai Parlog for a detailed explanation before you implement your own equals
method.
Suppose you have the following classes Task
, Todo
, Deadline
:
public class Task {
protected String description;
public Task(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
public class Todo extends Task {
protected boolean isDone;
public Todo(String description) {
super(description);
isDone = false;
}
public void setDone(boolean done) {
isDone = done;
}
public boolean isDone() {
return isDone;
}
}
public class Deadline extends Todo {
protected String by;
public Deadline(String description, String by) {
super(description);
this.by = by;
}
public void setBy(String by) {
this.by = by;
}
public String getBy() {
return by;
}
}
Override the toString
method of the three classes to produce the following behavior.
public class Main {
public static void main(String[] args) {
// create a todo task and print it
Todo t = new Todo("Read a good book");
System.out.println(t);
// change todo fields and print again
t.setDone(true);
System.out.println(t);
// create a deadline task and print it
Deadline d = new Deadline("Read textbook", "Nov 16");
System.out.println(d);
// change deadline details and print again
d.setDone(true);
d.setBy("Postponed to Nov 18th");
System.out.println(d);
}
}
description: Read a good book
is done? No
description: Read a good book
is done? Yes
description: Read textbook
is done? No
do by: Nov 16
description: Read textbook
is done? Yes
do by: Postponed to Nov 18th
You can use the super.toString
from the subclass to invoke the behavior of the method you are overriding. This is useful here because the overriding method is simply adding onto the behavior of the overridden method.
Hint
The text given in this section borrows some explanations and code examples from the -- Java Tutorial.
In Java, an interface is a reference type, similar to a class, mainly containing method signatures. Defining an interface is similar to creating a new class except it uses the keyword interface
in place of class
.
Here is an interface named DrivableVehicle
that defines methods needed to drive a vehicle.
public interface DrivableVehicle {
void turn(Direction direction);
void changeLanes(Direction direction);
void signalTurn(Direction direction, boolean signalOn);
// more method signatures
}
Note that the method signatures have no braces ({ }
) and are terminated with a semicolon.
Interfaces cannot be instantiated—they can only be implemented by classes. When an instantiable class implements an interface, indicated by the keyword implements
, it provides a method body for each of the methods declared in the interface.
Here is how a class CarModelX
can implement the DrivableVehicle
interface.
public class CarModelX implements DrivableVehicle {
@Override
public void turn(Direction direction) {
// implementation
}
// implementation of other methods
}
An interface can be used as a type e.g., DrivableVehicle dv = new CarModelX();
.
Interfaces can inherit from other interfaces using the extends
keyword, similar to a class inheriting another.
Here is an interface named SelfDrivableVehicle
that inherits the DrivableVehicle
interface.
public interface SelfDrivableVehicle extends DrivableVehicle {
void goToAutoPilotMode();
}
Note that the method signatures have no braces and are terminated with a semicolon.
Furthermore, Java allows multiple inheritance among interfaces. A Java interface can inherit multiple other interfaces. A Java class can implement multiple interfaces (and inherit from one class).
The design below is allowed by Java. In case you are not familiar with UML notation used: solid lines indicate normal inheritance; dashed lines indicate interface inheritance; the triangle points to the parent.
Staff
interface inherits (note the solid lines) the interfaces TaxPayer
and Citizen
.TA
class implements both Student
interface and the Staff
interface.TA
class has to implement all methods in the interfaces TaxPayer
and Citizen
.TA
is a Staff
, is a TaxPayer
and is a Citizen
.Interfaces can also contain constants and static methods.
This example adds a constant MAX_SPEED
and a static method isSpeedAllowed
to the interface DrivableVehicle
.
public interface DrivableVehicle {
int MAX_SPEED = 150;
static boolean isSpeedAllowed(int speed){
return speed <= MAX_SPEED;
}
void turn(Direction direction);
void changeLanes(Direction direction);
void signalTurn(Direction direction, boolean signalOn);
// more method signatures
}
Interfaces can contain default method implementations and nested types. They are not covered here.
The Main
class below passes a list of Printable
objects (i.e., objects that implement the Printable
interface) for another method to be printed.
public class Main {
public static void printObjects(Printable[] items) {
for (Printable p : items) {
p.print();
}
}
public static void main(String[] args) {
Printable[] printableItems = new Printable[]{
new Circle(5),
new Rectangle(3, 4),
new Person("James Cook")};
printObjects(printableItems);
}
}
Circle of area 78
Rectangle of area 12
Person of name James Cook
Classes Shape
, Circle
, and Rectangle
are given below:
public abstract class Shape {
public abstract int area();
}
public class Circle extends Shape implements Printable {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
@Override
public int area() {
return (int)(Math.PI * radius * radius);
}
@Override
public void print() {
System.out.println("Circle of area " + area());
}
}
public class Rectangle extends Shape implements Printable {
private int height;
private int width;
public Rectangle(int height, int width){
this.height = height;
this.width = width;
}
@Override
public int area() {
return height * width;
}
@Override
public void print() {
System.out.println("Rectangle of area " + area());
}
}
Add the missing Printable
interface. Add the missing methods of the Person
class given below.
public class Person implements Printable {
private String name;
// todo: add missing methods
}
Partial solution
Java is a strongly-typed language which means the code works with only the object types that it targets.
The following code PetShelter
keeps a list of Cat
objects and make them speak
. The code will not work with any other type, for example, Dog
objects.
public class PetShelter {
private static Cat[] cats = new Cat[]{
new Cat("Mittens"),
new Cat("Snowball")};
public static void main(String[] args) {
for (Cat c: cats){
System.out.println(c.speak());
}
}
}
Mittens: Meow
Snowball: Meow
The Cat
class
This strong-typing can lead to unnecessary verbosity caused by repetitive similar code that do similar things with different object types.
If the PetShelter
is to keep both cats and dogs, you'll need two arrays and two loops:
public class PetShelter {
private static Cat[] cats = new Cat[]{
new Cat("Mittens"),
new Cat("Snowball")};
private static Dog[] dogs = new Dog[]{
new Dog("Spot")};
public static void main(String[] args) {
for (Cat c: cats){
System.out.println(c.speak());
}
for(Dog d: dogs){
System.out.println(d.speak());
}
}
}
Mittens: Meow
Snowball: Meow
Spot: Woof
The Dog
class
A better way is to take advantage of polymorphism to write code that targets a superclass so that it works with any subclass objects.
The PetShelter2
uses one data structure to keep both types of animals and one loop to make them speak. The code targets the Animal
superclass (assuming Cat
and Dog
inherits from the Animal
class) instead of repeating the code for each animal type.
public class PetShelter2 {
private static Animal[] animals = new Animal[]{
new Cat("Mittens"),
new Cat("Snowball"),
new Dog("Spot")};
public static void main(String[] args) {
for (Animal a: animals){
System.out.println(a.speak());
}
}
}
Mittens: Meow
Snowball: Meow
Spot: Woof
The Animal
, Cat
, and Dog
classes
Explanation: Because Java supports polymorphism, you can store both Cat
and Dog
objects in an array of Animal
objects. Similarly, you can call the speak
method on any Animal
object (as done in the loop) and yet get different behavior from Cat
objects and Dog
objects.
Suggestion: try to add an Animal
object (e.g., new Animal("Unnamed")
) to the animals
array and see what happens.
Polymorphic code is better in several ways:
main
method will work even if we add more animal types).The Main
class below keeps a list of Circle
and Rectangle
objects and prints the area (as an int
value) of each shape when requested.
Add the missing variables/methods to the code below so that it produces the output given.
public class Main {
//TODO add your methods here
public static void main(String[] args) {
addShape(new Circle(5));
addShape(new Rectangle(3, 4));
addShape(new Circle(10));
printAreas();
addShape(new Rectangle(4, 4));
printAreas();
}
}
78
12
314
78
12
314
16
Circle
class and Rectangle
class is given below but you'll need to add a parent class Shape
.
public class Circle extends Shape {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
@Override
public int area() {
return (int)(Math.PI * radius * radius);
}
}
public class Rectangle extends Shape{
private int height;
private int width;
public Rectangle(int height, int width){
this.height = height;
this.width = width;
}
@Override
public int area() {
return height * width;
}
}
You may use an array of size 100 to store the shapes.
Partial solution
Hint
In Java, an abstract method is declared with the keyword abstract
and given without an implementation. If a class includes abstract methods, then the class itself must be declared abstract.
The speak
method in this Animal
class is abstract
. Note how the method signature ends with a semicolon and there is no method body. This makes sense as the implementation of the speak
method depends on the type of the animal and it is meaningless to provide a common implementation for all animal types.
public abstract class Animal {
protected String name;
public Animal(String name){
this.name = name;
}
public abstract String speak();
}
As one method of the class is abstract
, the class itself is abstract
.
An abstract class is declared with the keyword abstract
. Abstract classes can be used as reference type but cannot be instantiated.
This Account
class has been declared as abstract although it does not have any abstract methods. Attempting to instantiate Account
objects will result in a compile error.
public abstract class Account {
int number;
void close(){
//...
}
}
Account a;
OK to use as a type
a = new Account();
Compile error!
In Java, even a class that does not have any abstract methods can be declared as an abstract class.
When an abstract class is subclassed, the subclass should provide implementations for all of the abstract methods in its superclass or else the subclass must also be declared abstract.
The Feline
class below inherits from the abstract class Animal
but it does not provide an implementation for the abstract method speak
. As a result, the Feline
class needs to be abstract too.
public abstract class Feline extends Animal {
public Feline(String name) {
super(name);
}
}
The DomesticCat
class inherits the abstract Feline
class and provides the implementation for the abstract method speak
. As a result, it need not be (but can be) declared as abstract.
public class DomesticCat extends Feline {
public DomesticCat(String name) {
super(name);
}
@Override
public String speak() {
return "Meow";
}
}
Animal a = new Feline("Mittens");
Feline
is abstract.Animal a = new DomesticCat("Mittens");
DomesticCat
can be instantiated and assigned to a variable of Animal
type (the assignment is allowed by polymorphism).The Main
class below keeps a list of Circle
and Rectangle
objects and prints the area (as an int
value) of each shape when requested.
public class Main {
private static Shape[] shapes = new Shape[100];
private static int shapeCount = 0;
public static void addShape(Shape s){
shapes[shapeCount] = s;
shapeCount++;
}
public static void printAreas(){
for (int i = 0; i < shapeCount; i++){
shapes[i].print();
}
}
public static void main(String[] args) {
addShape(new Circle(5));
addShape(new Rectangle(3, 4));
addShape(new Circle(10));
addShape(new Rectangle(4, 4));
printAreas();
}
}
Circle of area 78
Rectangle of area 12
Circle of area 314
Rectangle of area 16
Circle
class and Rectangle
class is given below:
public class Circle extends Shape {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
@Override
public int area() {
return (int)(Math.PI * radius * radius);
}
@Override
public void print() {
System.out.println("Circle of area " + area());
}
}
public class Rectangle extends Shape {
private int height;
private int width;
public Rectangle(int height, int width){
this.height = height;
this.width = width;
}
@Override
public int area() {
return height * width;
}
@Override
public void print() {
System.out.println("Rectangle of area " + area());
}
}
Add the missing Shape
class as an abstract class with two abstract methods.
Partial solution
Statements about abstract classes
Given below is an extract from the -- Java Tutorial, with some adaptations.
There are three basic categories of exceptions In Java:
Error
, RuntimeException
, and their subclasses. Suppose an application prompts a user for an input file name, then opens the file by passing the name to the constructor for java.io.FileReader. Normally, the user provides the name of an existing, readable file, so the construction of the FileReader
object succeeds, and the execution of the application proceeds normally. But sometimes the user supplies the name of a nonexistent file, and the constructor throws java.io.FileNotFoundException
. A well-written program will catch this exception and notify the user of the mistake, possibly prompting for a corrected file name.
Error
and its subclasses. Suppose that an application successfully opens a file for input, but is unable to read the file because of a hardware or system malfunction. The unsuccessful read will throw java.io.IOError
. An application might choose to catch this exception, in order to notify the user of the problem — but it also might make sense for the program to print a stack trace and exit.
RuntimeException
and its subclasses. These usually indicate programming bugs, such as logic errors or improper use of an API. Consider the application described previously that passes a file name to the constructor for FileReader. If a logic error causes a null to be passed to the constructor, the constructor will throw NullPointerException
. The application can catch this exception, but it probably makes more sense to eliminate the bug that caused the exception to occur.
Errors and runtime exceptions are collectively known as unchecked exceptions.
A program can catch exceptions by using a combination of the try
, catch
blocks.
try
block identifies a block of code in which an exception can occur.catch
block identifies a block of code, known as an exception handler, that can handle a particular type of exception. The writeList()
method below calls a method process()
that can cause two type of exceptions. It uses a try-catch construct to deal with each exception.
public void writeList() {
print("starting method");
try {
print("starting process");
process();
print("finishing process");
} catch (IndexOutOfBoundsException e) {
print("caught IOOBE");
} catch (IOException e) {
print("caught IOE");
}
print("finishing method");
}
Some possible outputs:
No exceptions | IOException | IndexOutOfBoundsException |
---|---|---|
starting method starting process finishing process finishing method | starting method starting process caught IOE finishing method | starting method starting process caught IOOBE finishing method |
You can use a finally
block to specify code that is guaranteed to execute with or without the exception. This is the right place to close files, recover resources, and otherwise clean up after the code enclosed in the try
block.
The writeList()
method below has a finally
block:
public void writeList() {
print("starting method");
try {
print("starting process");
process();
print("finishing process");
} catch (IndexOutOfBoundsException e) {
print("caught IOOBE");
} catch (IOException e) {
print("caught IOE");
} finally {
// clean up
print("cleaning up");
}
print("finishing method");
}
Some possible outputs:
No exceptions | IOException | IndexOutOfBoundsException |
---|---|---|
starting method starting process finishing process cleaning up finishing method | starting method starting process caught IOE cleaning up finishing method | starting method starting process caught IOOBE cleaning up finishing method |
The try
statement should contain at least one catch
block or a finally block and may have multiple catch
blocks.
The class of the exception object indicates the type of exception thrown. The exception object can contain further information about the error, including an error message.
You can use the throw
statement to throw an exception. The throw statement requires a object as the argument.
Here's an example of a throw
statement.
if (size == 0) {
throw new EmptyStackException();
}
In Java, Checked exceptions are subject to the Catch or Specify Requirement: code that might throw checked exceptions must be enclosed by either of the following:
try
statement that catches the exception. The try
must provide a handler for the exception.throws
clause that lists the exception.Unchecked exceptions are not required to follow to the Catch or Specify Requirement but you can apply the requirement to them too.
Here's an example of a method specifying that it throws certain checked exceptions:
public void writeList() throws IOException, IndexOutOfBoundsException {
print("starting method");
process();
print("finishing method");
}
Some possible outputs:
No exceptions | IOException | IndexOutOfBoundsException |
---|---|---|
starting method finishing method | starting method | starting method |
Java comes with a collection of built-in exception classes that you can use. When they are not enough, it is possible to create your own exception classes.
The Main
class below parses a string descriptor of a rectangle of the format "WIDTHxHEIGHT"
e.g., "3x4"
and prints the area of the rectangle.
public class Main {
public static void printArea(String descriptor){
//TODO: modify the code below
System.out.println(descriptor + "=" + calculateArea(descriptor));
}
private static int calculateArea(String descriptor) {
//TODO: modify the code below
String[] dimensions = descriptor.split("x");
return Integer.parseInt(dimensions[0]) * Integer.parseInt(dimensions[1]);
}
public static void main(String[] args) {
printArea("3x4");
printArea("5x5");
}
}
3x4=12
5x5=25
Update the code of printArea
to print an error message if WIDTH
and/or HEIGHT
are not numbers e.g., "Ax4"
calculateArea
will throw the unchecked exception NumberFormatException
if the code tries to parse a non-number to an integer.
Update the code of printArea
to print an error message if the descriptor is missing WIDTH
and/or HEIGHT
e.g., "x4"
calculateArea
will throw the unchecked exception IndexOutOfBoundsException
if one or both dimensions are missing.
Update the code of calculateArea
to throw the checked exception IllegalShapeException
if there are more than 2 dimensions e.g., "5x4x3"
and update the printArea
to print an error message for those cases. Here is the code for the IllegalShapeException.java
public class IllegalShapeException extends Exception {
//no other code needed
}
Here is the expected behavior after you have done the above changes:
public class Main {
//...
public static void main(String[] args) {
printArea("3x4");
printArea("3xy");
printArea("3x");
printArea("3");
printArea("3x4x5");
}
}
3x4=12
WIDTH or HEIGHT is not a number: 3xy
WIDTH or HEIGHT is missing: 3x
WIDTH or HEIGHT is missing: 3
Too many dimensions: 3x4x5
Partial solution
Given below is an extract from the -- Java Tutorial, with some adaptations.
You can use polymorphism to write code that can work with multiple types, but that approach has some shortcomings.
Consider the following Box
class. It can be used only for storing Integer
objects.
public class BoxForIntegers {
private Integer x;
public void set(Integer x) {
this.x = x;
}
public Integer get() {
return x;
}
}
To store String
objects, another similar class is needed, resulting in the duplication of the entire class. As you can see, if you need to store many different types of objects, you could end up writing many similar classes.
public class BoxForString {
private String x;
public void set(String x) {
this.x = x;
}
public String get() {
return x;
}
}
One solution for this problem is to use polymorphism i.e., write the Box
class to store Object
objects.
public class Box {
private Object x;
public void set(Object x) {
this.x = x;
}
public Object get() {
return x;
}
}
The problem with this solution is, since its methods accept or return an Object
, you are free to pass in whatever you want, provided that it is not one of the primitive types. There is no way to verify, at compile time, how the class is used. One part of the code may place an Integer
in the box and expect to get Integers
out of it, while another part of the code may mistakenly pass in a String
, resulting in a runtime error.
Generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. Much like the more familiar , type parameters provide a way for you to re-use the same code with different inputs. The difference is that the inputs to formal parameters are values, while the inputs to type parameters are types.
A generic Box
class allows you to define what type of elements will be put in the Box
. For example, you can instantiate a Box
object to keep Integer
elements so that any attempt to put a non-Integer
object in that Box
object will result in a compile error.
This section includes extract from the -- Java Tutorial, with some adaptations.
The definition of a generic class includes a type parameter section, delimited by angle brackets (<>
). It specifies the type parameters (also called type variables) T1
, T2
, ..., and Tn
. A generic class is defined with the following format:
class name<T1, T2, ..., Tn> { /* ... */ }
Here is a generic Box
class. The class declaration Box<T>
introduces the type variable, T
, which is also used inside the class to refer to the same type.
Using Object
as the type:
public class Box {
private Object x;
public void set(Object x) {
this.x = x;
}
public Object get() {
return x;
}
}
A generic Box
using type parameter T
:
public class Box<T> {
private T x;
public void set(T x) {
this.x = x;
}
public T get() {
return x;
}
}
As you can see, all occurrences of Object
are replaced by T
.
To reference the generic Box
class from within your code, you must perform a generic type invocation, which replaces T
with some concrete value, such as Integer
. It is similar to an ordinary method invocation, but instead of passing an argument to a method, you are passing a type argument enclosed within angle brackets — e.g., <Integer>
or <String, Integer>
— to the generic class itself. Note that in some cases you can omit the type parameter i.e., <>
if the type parameter can be inferred from the context.
Using the generic Box
class to store Integer
objects:
Box<Integer> integerBox;
integerBox = new Box<>(); // type parameter omitted as it can be inferred
integerBox.set(Integer.valueOf(4));
Integer i = integerBox.get(); // returns an Integer
Box<Integer> integerBox;
simply declares that integerBox
will hold a reference to a "Box of Integer", which is how Box<Integer>
is read.integerBox = new Box<>();
instantiates a Box<Integer>
class. Note the <>
(an empty pair of angle brackets, also called the diamond operator) between the class name and the parenthesis.The compiler is able to check for type errors when using generic code.
The code below will fail because it creates a Box<String>
and then tries to pass Double
objects into it.
Box<String> stringBox = new Box<>();
stringBox.set(Double.valueOf(5.0)); //compile error!
A generic class can have multiple type parameters.
The generic OrderedPair
class, which implements the generic Pair interface:
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
The following statements create two instantiations of the OrderedPair
class:
Pair<String, Integer> p1 = new OrderedPair<>("Even", 8);
Pair<String, String> p2 = new OrderedPair<>("hello", "world");
The code, new OrderedPair<String, Integer>
, instantiates K
as a String
and V
as an Integer
. Therefore, the parameter types of OrderedPair
's constructor are String
and Integer
, respectively.
A type variable can be any non-primitive type you specify: any class type, any interface type, any array type, or even another type variable.
By convention, type parameter names are single, uppercase letters. The most commonly used type parameter names are:
E
- Element (used extensively by the Java Collections Framework)K
- KeyN
- NumberT
- TypeV
- ValueS
, U
, V
etc. - 2nd, 3rd, 4th typesThis section uses extracts from the -- Java Tutorial, with some adaptations.
A collection — sometimes called a container — is simply an object that groups multiple elements into a single unit. Collections are used to store, retrieve, manipulate, and communicate aggregate data.
Typically, collections represent data items that form a natural group, such as a poker hand (a collection of cards), a mail folder (a collection of letters), or a telephone directory (a mapping of names to phone numbers).
The collections framework is a unified architecture for representing and manipulating collections. It contains the following:
Interfaces: These are abstract data types that represent collections. Interfaces allow collections to be manipulated independently of the details of their representation.
Example: the List<E>
interface can be used to manipulate list-like collections which may be implemented in different ways such as ArrayList<E>
or LinkedList<E>
.
Implementations: These are the concrete implementations of the collection interfaces. In essence, they are reusable data structures.
Example: the ArrayList<E>
class implements the List<E>
interface while the HashMap<K, V>
class implements the Map<K, V>
interface.
Algorithms: These are the methods that perform useful computations, such as searching and sorting, on objects that implement collection interfaces. The algorithms are said to be polymorphic: that is, the same method can be used on many different implementations of the appropriate collection interface.
Example: the sort(List<E>)
method can sort a collection that implements the List<E>
interface.
A well-known example of collections frameworks is the C++ Standard Template Library (STL). Although both are collections frameworks and the syntax look similar, note that there are important philosophical and implementation differences between the two.
The following list describes the core collection interfaces:
Collection
— the root of the collection hierarchy. A collection represents a group of objects known as its elements. The Collection interface is the least common denominator that all collections implement and is used to pass collections around and to manipulate them when maximum generality is desired. Some types of collections allow duplicate elements, and others do not. Some are ordered and others are unordered. The Java platform doesn't provide any direct implementations of this interface but provides implementations of more specific subinterfaces, such as Set
and List
. Also see the Collection
API.
Set
— a collection that cannot contain duplicate elements. This interface models the mathematical set abstraction and is used to represent sets, such as the cards comprising a poker hand, the courses making up a student's schedule, or the processes running on a machine. Also see the Set
API.
List
— an ordered collection (sometimes called a sequence). List
s can contain duplicate elements. The user of a List
generally has precise control over where in the list each element is inserted and can access elements by their integer index (position). Also see the List
API.
Queue
— a collection used to hold multiple elements prior to processing. Besides basic Collection
operations, a Queue
provides additional insertion, extraction, and inspection operations. Also see the Queue
API.
Map
— an object that maps keys to values. A Map
cannot contain duplicate keys; each key can map to at most one value. Also see the Map
API.
Others: Deque
, SortedSet
, SortedMap
The ArrayList
class is a resizable-array implementation of the List
interface. Unlike a normal array
, an ArrayList
can grow in size as you add more items to it. The example below illustrates some of the useful methods of the ArrayList
class using an ArrayList
of String
objects.
import java.util.ArrayList;
public class ArrayListDemo {
public static void main(String args[]) {
ArrayList<String> items = new ArrayList<>();
System.out.println("Before adding any items:" + items);
items.add("Apple");
items.add("Box");
items.add("Cup");
items.add("Dart");
print("After adding four items: " + items);
items.remove("Box"); // remove item "Box"
print("After removing Box: " + items);
items.add(1, "Banana"); // add "Banana" at index 1
print("After adding Banana: " + items);
items.add("Egg"); // add "Egg", will be added to the end
items.add("Cup"); // add another "Cup"
print("After adding Egg: " + items);
print("Number of items: " + items.size());
print("Index of Cup: " + items.indexOf("Cup"));
print("Index of Zebra: " + items.indexOf("Zebra"));
print("Item at index 3 is: " + items.get(2));
print("Do we have a Box?: " + items.contains("Box"));
print("Do we have an Apple?: " + items.contains("Apple"));
items.clear();
print("After clearing: " + items);
}
private static void print(String text) {
System.out.println(text);
}
}
Before adding any items:[]
After adding four items: [Apple, Box, Cup, Dart]
After removing Box: [Apple, Cup, Dart]
After adding Banana: [Apple, Banana, Cup, Dart]
After adding Egg: [Apple, Banana, Cup, Dart, Egg, Cup]
Number of items: 6
Index of Cup: 2
Index of Zebra: -1
Item at index 3 is: Cup
Do we have a Box?: false
Do we have an Apple?: true
After clearing: []
Add the missing methods to the class given below so that it produces the output given.
Use an ArrayList
to store the numbers.
public class Main {
//TODO: add your methods here
public static void main(String[] args) {
System.out.println("Adding numbers to the list");
addNumber(3);
addNumber(8);
addNumber(24);
System.out.println("The total is: " + getTotal());
System.out.println("8 in the list : " + isFound(8) );
System.out.println("5 in the list : " + isFound(5) );
removeNumber(8);
System.out.println("The total is: " + getTotal());
}
}
Adding numbers to the list
[3]
[3, 8]
[3, 8, 24]
The total is: 35
8 in the list : true
5 in the list : false
[3, 24]
The total is: 27
Hint
HashMap
is an implementation of the Map
interface. It allows you to store a collection of key-value pairs. The example below illustrates how to use a HashMap<String, Point>
to maintain a list of coordinates and their identifiers e.g., the identifier x1
is used to identify the point 0,0
where x1
is the key and 0,0
is the value.
import java.awt.Point;
import java.util.HashMap;
import java.util.Map;
public class HashMapDemo {
public static void main(String[] args) {
HashMap<String, Point> points = new HashMap<>();
// put the key-value pairs in the HashMap
points.put("x1", new Point(0, 0));
points.put("x2", new Point(0, 5));
points.put("x3", new Point(5, 5));
points.put("x4", new Point(5, 0));
// retrieve a value for a key using the get method
print("Coordinates of x1: " + pointAsString(points.get("x1")));
// check if a key or a value exists
print("Key x1 exists? " + points.containsKey("x1"));
print("Key y1 exists? " + points.containsKey("y1"));
print("Value (0,0) exists? " + points.containsValue(new Point(0, 0)));
print("Value (1,2) exists? " + points.containsValue(new Point(1, 2)));
// update the value of a key to a new value
points.put("x1", new Point(-1,-1));
// iterate over the entries
for (Map.Entry<String, Point> entry : points.entrySet()) {
print(entry.getKey() + " = " + pointAsString(entry.getValue()));
}
print("Number of keys: " + points.size());
points.clear();
print("Number of keys after clearing: " + points.size());
}
public static String pointAsString(Point p) {
return "[" + p.x + "," + p.y + "]";
}
public static void print(String s) {
System.out.println(s);
}
}
Coordinates of x1: [0,0]
Key x1 exists? true
Key y1 exists? false
Value (0,0) exists? true
Value (1,2) exists? false
x1 = [-1,-1]
x2 = [0,5]
x3 = [5,5]
x4 = [5,0]
Number of keys: 4
Number of keys after clearing: 0
The class given below keeps track of how many people signup to attend an event on each day of the week. Add the missing methods so that it produces the output given.
Use an HashMap
to store the number of entries for each day.
public class Main {
private static HashMap<String, Integer> roster = new HashMap<>();
//TODO: add your methods here
public static void main(String[] args) {
addToRoster("Monday"); // i.e., one person signed up for Monday
addToRoster("Wednesday"); // i.e., one person signed up for Wednesday
addToRoster("Wednesday"); // i.e., another person signed up for Wednesday
addToRoster("Friday");
addToRoster("Monday");
printRoster();
}
}
Monday => 2
Friday => 1
Wednesday => 2
Hint
When writing JUnit tests for a class Foo
, the common practice is to create a FooTest
class, which will contain various test methods for testing methods of the Foo
class.
Suppose we want to write tests for the IntPair
class below.
public class IntPair {
int first;
int second;
public IntPair(int first, int second) {
this.first = first;
this.second = second;
}
/**
* Returns The result of applying integer division first/second.
* @throws Exception if second is 0.
*/
public int intDivision() throws Exception {
if (second == 0){
throw new Exception("Divisor is zero");
}
return first/second;
}
@Override
public String toString() {
return first + "," + second;
}
}
Here's a IntPairTest
class to match (using JUnit 5).
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class IntPairTest {
@Test
public void intDivision_nonZeroDivisor_success() throws Exception {
// normal division results in an integer answer 2
assertEquals(2, new IntPair(4, 2).intDivision());
// normal division results in a decimal answer 1.9
assertEquals(1, new IntPair(19, 10).intDivision());
// dividend is zero but divisor is not
assertEquals(0, new IntPair(0, 5).intDivision());
}
@Test
public void intDivision_zeroDivisor_exceptionThrown() {
try {
assertEquals(0, new IntPair(1, 0).intDivision());
fail(); // the test should not reach this line
} catch (Exception e) {
assertEquals("Divisor is zero", e.getMessage());
}
}
@Test
public void testStringConversion() {
assertEquals("4,7", new IntPair(4, 7).toString());
}
}
@Test
annotation.assertEquals(expected, actual)
methods (provided by JUnit) to compare the expected output with the actual output. If they do not match, the test will fail.assertNull
, assertNotNull
, assertTrue
, assertFalse
etc. [more ...]testStringConversion
but when writing test methods, sometimes another convention is used:unitBeingTested_descriptionOfTestInputs_expectedOutcome
intDivision_zeroDivisor_exceptionThrown
catch
block. But if it is not thrown as expected, the test will reach fail()
line and will fail as a result.What to test for when writing tests? While test case design techniques is a separate topic altogether, it should be noted that the goal of these tests is to catch bugs in the code. Therefore, test using inputs that can trigger a potentially buggy path in the code. Another way to approach this is, to write tests such that if a future developer modified the method to unintentionally introduce a bug into it, at least one of the test should fail (thus alerting that developer to the mistake immediately).
In the example above, the IntPairTest
class tests the IntPair#intDivision(int, int)
method using several inputs, some even seemingly attempting to 'trick' the method into producing a wrong result. If the method still produces the correct output for such 'tricky' inputs (as well as 'normal' outputs), we can have a higher confidence on the method being correctly implemented.
However, also note that the current test cases do not (but probably should) test for the inputs (0, 0
), to confirm that it throws the expected exception.
Given below are some noteworthy JUnit concepts, as per the JUnit 5 User Guide.
Annotations: In addition to the @Test
annotation you've seen already, there are many other annotations in JUnit. For example, the @Disabled
annotation can be used to disable a test temporarily. [more ...]
Pre/post-test tasks: In order to allow individual test methods to be executed in isolation and to avoid unexpected side effects due to mutable test instance state, JUnit creates a new instance of each test class before executing each test method. It is possible to supply code that should be run before/after every test method/class (e.g., for setting up the environment required by the tests, or cleaning up things after a test is completed) by using test instance lifecycle annotations such as @BeforeEach
@AfterAll
. [more ...]
Conditional test execution: It is possible to configure tests to run only under certain conditions. For example, @TestOnMac
annotation can be used to specify tests that should run on Mac OS only. [more ...]
Assumptions: It is possible to specify assumptions that must hold for a test to be executed (i.e., the test will be skipped if the assumption does not hold). [more ...]
Tagging tests: It is possible to tag tests (e.g., @Tag("slow")
so that tests can be selected based on tags. [more ...]
Test execution order: By default, JUnit executes test classes and methods in a deterministic but intentionally nonobvious order. This ensures that subsequent runs of a test suite execute tests in the same order, thereby allowing for repeatable builds. But it is possible to specify a specific testing order. [more ...]
Test hierarchies: Normally, we organize tests into separate test classes. If a more hierarchical structure is needed, the @Nested
annotation can be used to express the relationship among groups of tests. [more ...]
Repeated tests: JUnit provides the ability to repeat a test a specified number of times by annotating a method with @RepeatedTest
and specifying the total number of repetitions desired. [more ...]
Parameterized tests make it possible to run a test multiple times with different arguments. The parameter values can be supplied using a variety of ways e.g., an array of values, enums, a csv file, etc. [more ...]
Dynamic tests: The @TestFactory
annotation can be used to specify factory methods that generate tests dynamically. [more ...]
Timeouts: The @Timeout
annotation allows one to declare that a test should fail if its execution time exceeds a given duration. [more ...]
Parallel execution: By default, JUnit tests are run sequentially in a single thread. Running tests in parallel — for example, to speed up execution — is available as an opt-in feature. [more ...]
Extensions: JUnit supports third-party extensions. The built-in TempDirectory
extension is used to create and clean up a temporary directory for an individual test or all tests in a test class. [more ...]
You can define an enum type by using the enum
keyword. Because they are constants, the names of an enum type's fields are in uppercase letters e.g., FLAG_SUCCESS
by convention.
Defining an enumeration to represent days of a week (code to be put in the Day.java
file):
public enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY
}
Some examples of using the Day
enumeration defined above:
Day today = Day.MONDAY;
Day[] holidays = new Day[]{Day.SATURDAY, Day.SUNDAY};
switch (today) {
case SATURDAY:
case SUNDAY:
System.out.println("It's the weekend");
break;
default:
System.out.println("It's a week day");
}
Note that while enumerations are usually a simple set of fixed values, Java enumerations can have behaviors too, as explained in this tutorial from -- Java Tutorial
Define an enumeration named Priority
. Add the missing describe
method to the code below so that it produces the output given.
public class Main {
// Add your method here
public static void main(String[] args) {
describe("Red", Priority.HIGH);
describe("Orange", Priority.MEDIUM);
describe("Blue", Priority.MEDIUM);
describe("Green", Priority.LOW);
}
}
Red indicates high priority
Orange indicates medium priority
Blue indicates medium priority
Green indicates low priority
Hint
You can organize your types (i.e., classes, interfaces, enumerations, etc.) into packages for easier management (among other benefits).
To create a package, you put a package statement at the very top of every source file in that package. The package statement must be the first line in the source file and there can be no more than one package statement in each source file. Furthermore, the package of a type should match the folder path of the source file. Similarly, the compiler will put the .class
files in a folder structure that matches the package names.
The Formatter
class below (in <source folder>/seedu/tojava/util/Formatter.java
file) is in the package seedu.tojava.util
. When it is compiled, the Formatter.class
file will be in the location <compiler output folder>/seedu/tojava/util
:
package seedu.tojava.util;
public class Formatter {
public static final String PREFIX = ">>";
public static String format(String s){
return PREFIX + s;
}
}
Package names are written in all lower case (not camelCase), using the dot as a separator. Packages in the Java language itself begin with java
. or javax
. Companies use their reversed Internet domain name to begin their package names.
For example, com.foobar.doohickey.util
can be the name of a package created by a company with a domain name foobar.com
To use a public from outside its package, you must do one of the following:
The Main
class below has two import statements:
import seedu.tojava.util.StringParser
: imports the class StringParser
in the seedu.tojava.util
packageimport seedu.tojava.frontend.*
: imports all the classes in the seedu.tojava.frontend
packagepackage seedu.tojava;
import seedu.tojava.util.StringParser;
import seedu.tojava.frontend.*;
public class Main {
public static void main(String[] args) {
// Using the fully qualified name to access the Processor class
String status = seedu.tojava.logic.Processor.getStatus();
// Using the StringParser previously imported
StringParser sp = new StringParser();
// Using classes from the tojava.frontend package
Ui ui = new Ui();
Message m = new Message();
}
}
Note how the class can still use the Processor
without importing it first, by using its fully qualified name seedu.tojava.logic.Processor
Importing a package does not import its sub-packages, as packages do not behave as hierarchies despite appearances.
import seedu.tojava.frontend.*
does not import the classes in the sub-package seedu.tojava.frontend.widget
.
If you do not use a package statement, your type doesn't have a package -- a practice not recommended (except for small code examples) as it is not possible for a type in a package to import a type that is not in a package.
Optionally, a static import can be used to import static members of a type so that the imported members can be used without specifying the type name.
The class below uses static imports to import the constant PREFIX
and the method format()
from the seedu.tojava.util.Formatter
class.
import static seedu.tojava.util.Formatter.PREFIX;
import static seedu.tojava.util.Formatter.format;
public class Main {
public static void main(String[] args) {
String formatted = format("Hello");
boolean isFormatted = formatted.startsWith(PREFIX);
System.out.println(formatted);
}
}
Formatter
class
Note how the class can use PREFIX
and format()
(instead of Formatter.PREFIX
and Formatter.format()
).
When using the commandline to compile/run Java, you should take the package into account.
If the seedu.tojava.Main
class is defined in the file Main.java
,
<source folder>
, the command is:javac seedu/tojava/Main.java
<compiler output folder>
, the command is:java seedu.tojava.Main
You can use the java.io.File
class to represent a file object. It can be used to access properties of the file object.
This code creates a File
object to represent a file fruits.txt
that exists in the data
directory relative to the current working directory and uses that object to print some properties of the file.
import java.io.File;
public class FileClassDemo {
public static void main(String[] args) {
File f = new File("data/fruits.txt");
System.out.println("full path: " + f.getAbsolutePath());
System.out.println("file exists?: " + f.exists());
System.out.println("is Directory?: " + f.isDirectory());
}
}
full path: C:\sample-code\data\fruits.txt
file exists?: true
is Directory?: false
If you use backslash to specify the file path in a Windows computer, you need to use an additional backslash as an escape character because the backslash by itself has a special meaning. e.g., use "data\\fruits.txt"
, not "data\fruits.txt"
. Alternatively, you can use forward slash "data/fruits.txt"
(even on Windows).
You can read from a file using a Scanner
object that uses a File
object as the source of data.
This code uses a Scanner
object to read (and print) contents of a text file line-by-line:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class FileReadingDemo {
private static void printFileContents(String filePath) throws FileNotFoundException {
File f = new File(filePath); // create a File for the given file path
Scanner s = new Scanner(f); // create a Scanner using the File as the source
while (s.hasNext()) {
System.out.println(s.nextLine());
}
}
public static void main(String[] args) {
try {
printFileContents("data/fruits.txt");
} catch (FileNotFoundException e) {
System.out.println("File not found");
}
}
}
i.e., contents of the data/fruits.txt
5 Apples
3 Bananas
6 Cherries
You can use a java.io.FileWriter
object to write to a file.
The writeToFile
method below uses a FileWriter
object to write to a file. The method is being used to write two lines to the file temp/lines.txt
.
import java.io.FileWriter;
import java.io.IOException;
public class FileWritingDemo {
private static void writeToFile(String filePath, String textToAdd) throws IOException {
FileWriter fw = new FileWriter(filePath);
fw.write(textToAdd);
fw.close();
}
public static void main(String[] args) {
String file2 = "temp/lines.txt";
try {
writeToFile(file2, "first line" + System.lineSeparator() + "second line");
} catch (IOException e) {
System.out.println("Something went wrong: " + e.getMessage());
}
}
}
Contents of the temp/lines.txt
:
first line
second line
Note that you need to call the close()
method of the FileWriter
object for the writing operation to be completed.
You can create a FileWriter
object that appends to the file (instead of overwriting the current content) by specifying an additional boolean parameter to the constructor.
The method below appends to the file rather than overwrites.
private static void appendToFile(String filePath, String textToAppend) throws IOException {
FileWriter fw = new FileWriter(filePath, true); // create a FileWriter in append mode
fw.write(textToAppend);
fw.close();
}
The java.nio.file.Files
is a utility class that provides several useful file operations. It relies on the java.nio.file.Paths
file to generate Path
objects that represent file paths.
This example uses the Files
class to copy a file and delete a file.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class FilesClassDemo {
public static void main(String[] args) throws IOException{
Files.copy(Paths.get("data/fruits.txt"), Paths.get("temp/fruits2.txt"));
Files.delete(Paths.get("temp/fruits2.txt"));
}
}
The techniques above are good enough to manipulate simple text files. Note that it is also possible to perform file I/O operations using other classes.
Access level modifiers determine whether other classes can use a particular field or invoke a particular method.
There are two levels of access control:
At the class level:
public
: the class is visible to all classes everywhereAt the member level:
public
or no modifier (package-private): same meaning as when used with top-level classesprivate
: the member can only be accessed in its own classprotected
: the member can only be accessed within its own package (as with package-private) and, in addition, by a subclass of its class in another packageThe following table shows the access to members permitted by each modifier.
Modifier | ||||
---|---|---|---|---|
public | ||||
protected | ||||
no modifier | ||||
private |
Access levels affect you in two ways:
Java does not directly support constants. The convention is to use a static
final
variable where a constant is needed. The static
modifier causes the variable to be available without instantiating an object. The final
modifier causes the variable to be unchangeable. Java constants are normally declared in ALL CAPS separated by underscores.
Here is an example of a constant named MAX_BALANCE
which can be accessed as Account.MAX_BALANCE
.
public class Account{
public static final double MAX_BALANCE = 1000000.0;
}
Math.PI
is an example constant that comes with Java.
Casting is the action of converting from one type to another. You can use the (newType)
syntax to cast a value to a type named newType
.
When you cast a primitive value to another type, there may be a loss of precision.
The code below casts a double
value to an int
value and casts it back to a double
. Note the loss of precision.
double d = 5.3;
System.out.println("Before casting to an int: " + d);
int i = (int)d; // cast d to an int
System.out.println("After casting to an int: " + i);
d = (double)i; // cast i back to a double
System.out.println("After casting back a double: " + d);
Before casting to an int: 5.3
After casting to an int: 5
After casting back a double: 5.0
Downcasting is when you cast an object reference from a superclass to a subclass.
Assume the following class hierarchy:
class Animal{
void speak(){
System.out.println("I'm an animal");
}
}
class Cat extends Animal{
@Override
void speak() {
System.out.println("I'm a Cat");
}
}
class DomesticCat extends Cat{
@Override
void speak() {
System.out.println("I'm a DomesticCat");
}
void catchMice(){
// ...
}
}
The foo
method below downcasts an Animal
object to its subclasses.
public static void foo(Animal a){
a.speak();
Cat c = (Cat)a; // downcast a to a Cat
c.speak();
DomesticCat dc = (DomesticCat)a; // downcast a to a DomesticCat
dc.speak();
dc.catchMice();
}
Note that the dc.catchMice()
line will not compile if a
is not downcast to a DomesticCat
object first. Reason: the catchMice
method is specific to the DomesticCat
class not not present in the Animal
or the Cat
classes.
Furthermore, the foo
method will fail at runtime if the argument a
is not a DomesticCat
object. Reason: an object cannot be cast to another class unless the object is of that class to begin with e.g., you cannot cast a Dog
object into a Cat
object.
Upcasting is when you cast an object reference from a subclass to a superclass. However, upcasting is done automatically by the compiler even if you do not specify it explicitly.
This example upcasts a Cat
object to its superclass Animal
:
Cat c = new Cat();
Animal a1 = (Animal)c; //upcasting c to the Animal class
Animal a2 = c; //upcasting is implicit
Note that due to polymorphism, the behavior of the object will reflect the actual type of the object irrespective of the type of the variable holding a reference to it.
The call to the speak()
method in the code below always executes the speak()
method of the DomesticCat
class because the actual type of the object remains DomesticCat
although the reference to it is being downcast/upcast to various other types.
Animal a = new DomesticCat(); //implicit upcast
a.speak();
Cat c = (Cat)a; //downcast
c.speak();
DomesticCat dc = (DomesticCat)a; //downcast
dc.speak();
I'm a DomesticCat
I'm a DomesticCat
I'm a DomesticCat
Casting to an incompatible type can result in a ClassCastException
at runtime.
This code will cause a ClassCastException
:
Object o = new Animal();
Integer x = (Integer)o;
Exception in thread "main" java.lang.ClassCastException: misc.casting.Animal cannot be
cast to java.lang.Integer at misc.casting.CastingExamples.main(CastingExamples.java:14)
You can use the instanceof
operator to check if a cast is safe to perform.
This code checks if the object a
is an instance of the Cat
class before casting it to a Cat
.
Cat c;
if (a instanceof Cat){
c = (Cat)a;
}
Java applications are typically delivered as JAR (short for Java Archive) files. A JAR contains Java classes and other resources (icons, media files, etc.).
An executable JAR file can be launched using the java -jar
command e.g., java -jar foo.jar
launches the foo.jar
file.
The IDE or build tools such as Gradle can help you to package your application as a JAR file.
See the tutorial Working with JAR files @se-edu/guides to learn how to create and use JAR files.
Variable Arguments (Varargs) is a syntactic sugar type feature that allows writing a method that can take a variable number of arguments.
The search
method below can be called as search()
, search("book")
, search("book", "paper")
, etc.
public static void search(String ... keywords){
// method body
}
Resources:
Java 8 introduced a number of new features (e.g. Lambdas, Streams) that are not trivial to learn but also extremely useful to know.
Here is an overview of new Java 8 features (written by Benjamin Winterberg).
Tutorials:
A video tutorial by well-known Java coach Venkat Subramaniam
A a more detailed version covering similar grounds
JavaFX is a technology for building Java-based GUIs. Previously it was a part Java itself, but has become a third-party dependency since then. It is now being maintained by OpenJDK.
Refer to the JavaFX tutorial @SE-EDU/guides to learn how to get started with JavaFX.