Java Fundamentals - Notes Part 1
Java Fundamentals Post for first part of notes
- 1. A Hello World Program
- 2. Using Variables
- 3. Strings: Working with Text
- 4. While Loops
- 5. For Loops
- 6. If Statements
- 7. Getting User Input
- 8. Do While
- 9. Switch
- 10. Arrays
- 11. Arrays of Strings
- 12. Multi-Dimensional Arrays
- 13. Classes and Objects
- 14. Methods
- 15. Getters and Return Values
- 16. Method Parameters
- 17. Setters and "this" Keyword
- 18. Constructors
- 19. Static and Final
- 20. String Builder and String Formatting
- 21. The toString() Method
- 22. Inheritance
public class Application {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Application.main(null);
Notes:
- Variables are essentially containers that can store values.
- Different variable types are like containers that store different types of values.
- You can declare and initialize variables on the same line of code (e.g. int myNumber = 7;).
- There are 8 primitive data types in Java: byte (8-bit integer), short (16-bit integer), int (32-bit integer), long (64-bit integer), float (32-bit single-precision floating point, can contain decimals), double (64-bit double-precision floating point, can contain decimals), boolean (can only contain two possible values: true or false), and char (16-bit Unicode character).
public class Application {
public static void main(String[] args) {
// Declare variable
int myNumber;
// Initialize variable value
myNumber = 7;
// Output variable value
System.out.println(myNumber);
// Different variable types
int myNumber2 = 8;
short myShort = 777;
long myLong = 9797;
double myDouble = 3.1415;
// Include the f at the end of the value
float myFloat = 324.3f;
char myChar = 'A';
boolean myBoolean = true;
byte myByte = 127;
/* You can't define a variable twice. Once a variable has been defined, you can access and change it by its variable name
int a = 7;
int a = 8;
*/
System.out.println(myNumber2);
System.out.println(myShort);
System.out.println(myLong);
System.out.println(myDouble);
System.out.println(myFloat);
System.out.println(myChar);
System.out.println(myBoolean);
System.out.println(myByte);
}
}
Application.main(null);
Notes:
- String is a class, or a type of object. More specifically, it is a non-primitive data type in Java.
- Concatenation is the action of combining multiple Strings together to form a new String, which is done by appending the next String to the end of the previous String.
- The System.out.println() function can be used to print out other variable types (e.g. int).
public class Application {
public static void main(String[] args) {
int myInt = 7;
// String is the class, and "Hello" is an instance of the String object.
String text = "Hello";
String blank = " ";
String name = "Bob";
//Concatenation of Strings
String greeting = text + blank + name;
double myDouble = 7.1;
System.out.println(greeting);
System.out.println("Hello" + " " + "Bob");
// Printing out different variable types. Notice that the values of myInt and myDouble are printed as is.
System.out.println("My integer is: " + myInt);
System.out.println("My number is: " + myDouble);
}
}
Application.main(null);
Notes:
- Note that there is a difference between parameters and arguments. The key distinction is that parameters are the variables written inside the functions definition, while arguments are the actual variable values passed to functions to run them.
- The argument written in a while loop is the condition it needs to satisfy in order to keep looping.
- However, certain variables, primarily the ones contained within the condition, need to be changed during iterations for the sake of preventing an infinite loop (at some point, the condition needs to be false, so the loop stops).
public class Application {
public static void main(String[] args) {
int value = 0;
// The boolean data type determine whether its value is true or false
boolean loop = value < 20;
System.out.println(loop);
// The while loop is followed with curly brackets instead of a semi-colon. This is for the sake of creating a block of code.
while (value < 10) {
System.out.println("Number " + value);
// Increment by 1
value = value + 1;
}
}
}
Application.main(null);
Notes:
- Like the while loop, the for loop is followed by curly brackets. Within the brackets defines the code block that will be executed in each iteration of the loop.
- The for loop contains 3 sections (inside the parenthesis following the for keyword). The 1st section defines what code is executed/initialized at the very beginning of the for loop. The 2nd section defines the condition, as long as the condition is true, the loop will keep iterating. The condition is checked at the start of each iteration. The 3rd section defines the code that is executed once after every iteration. The sections are separated by semi-colons, and not all sections need to have code within them.
- The System.out.printf is a format specifier that takes multiple arguments. In our example, the first argument is a String, and the second argument is a value to fill in as the format specifier. Unlike println, it does not print values on a new line.
public class Application {
public static void main(String[] args) {
// i++ means increment i by 1. It is the same as i = i + 1.
// After 5 iterations, the for loop will stop.
// Iteration visualization: i = 0, i = 1, i = 2, i = 3, i = 4 (loop stops here, since in the next iteration, i equals 5, which is not less than 5).
for (int i = 0; i < 5; i++) {
// Print out the string, but replace %d (int format specifier) with the value of i.
// \n indicates new line of the specified value is printed.
System.out.printf("The value of i is: %d\n", i);
}
}
}
Application.main(null);
Notes:
- When comparing primitive type values, use == and != to compare values. = is for assignment of values to variables.
- To create an if statement, write the keyword if, then follow it up with circle brackets. Within the brackets lies the condition that must be true in order for the block of code following the if statement (typically within curly brackets) to run.
- The else statement directly follows a complete if statement (after the code block of the if statement) or complete else if statement; it is the last conditional statement in a line of conditionals (when there is an if statement followed by one or more else if statements, all connected with each other in terms of the conditional process) and defines the code that will run if the condition in all the previous conditional statements are false.
- An else if statement directly follows a complete if statement or another else if statement, and contains a new condition. If this new condition is met, the code within the else if code block will run. An else if statement will only run if the previous statements (if and else if) are false.
- A general rule of thumb is that if you have a conditional line with if, else if, and else statements, only one of those conditional statements should run. But if you have multiple if statements following one another, there is a possibility that more than one of them will run, since there can be more than one condition that is met.
- The break keyword "breaks" a loop, or stops a loop from continue executing. In other words, the program jumps out of the loop and continues from it.
public class Application {
public static void main(String[] args) {
// == indicates that the 2 values are equal to each other, while != indicates that the 2 values are not equal to each other.
boolean cond = 5 != 2;
System.out.println(cond);
int myInt = 5;
// The statement "Yes, it's true!"" will print because the value of myInt, 5, is in fact less than 30.
if (myInt < 30) {
System.out.println("Yes, it's true!");
}
int num = 15;
if (num < 10) {
System.out.println("True");
} else if (num > 20) {
System.out.println("False");
} else {
System.out.println("None of the above");
}
int loop = 0;
// This while loop will become an infinite loop without the break statement, since the value true is always true.
while (true) {
System.out.println("Looping: " + loop);
if (loop == 5) {
// Stop loop execution once loop has a value of 5
break;
}
// Increment loop by 1 in each iteration
loop++;
System.out.println("Running");
}
}
}
Application.main(null);
Notes:
- To get user input in Java, the Scanner library in Java can be used. To import java libraries, use the import keyword followed by the reference to the library.
- To import the Scanner class, type import java.util.Scanner, since the Scanner library is a part of the java.util package.
- The Scanner library creates a prompt on which users can type on. The new keyword creates a new object of a referenced class. System.in is an argument passed to the Scanner constructor, and is an input stream object.
- nextLine(), nextInt(), and nextDouble() are all example methods of the Scanner class that store user inputs into variables of specific data types.
import java.util.Scanner;
public class Application {
public static void main(String[] args) {
// Create Scanner object variable called "input" of class type Scanner
Scanner input = new Scanner(System.in);
// Prompt user for input
System.out.println("Enter a line of text: ");
// nextLine() is a method of the Scanner class, that stores whatever the user types into a variable of type String
String line = input.nextLine();
System.out.println("Enter an integer: ");
// nextInt() is another method of the Scanner class that stores user input into variable of type integer.
// The program will crash, however, in the case that the user does not input a valid integer. An exception will be thrown.
// The program, therefore, currently isn't very robust. The Scanner does a method that solves this problem.
int myInt = input.nextInt();
// Usage of nextDouble() to store double type values that users input
System.out.println("Enter a double: ");
double value = input.nextDouble();
// Concatenation of user input values
System.out.println("You wrote: " + line + "-" + myInt + "-" + value);
}
}
Application.main(null);
Notes:
- In Java, for variables in the same scope of code, each variable has to have a unique name (case-sensitive).
- To multi-line comment in Java, type / /, and anything between that container will pass as a comment.
- In a do while loop, the statement within the do code block will always execute at least once, since the condition within the while keyword comes after the do keyword. After one execution, the condition in the while loop will be checked. If the condition is still true, then the code block will run again, and the condition will be checked again, and repeat.
- A variable only exists within the inner-most brackets in which it is declared. It does however, exist within brackets that appear within the inner-most brackets that it itself is contained in (kinda like lower scopes or child brackets). For example, variables within global scope typically exist within the scopes created by functions inside the global scope.
public class Application {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
/*System.out.println("Enter a number: ");
int value = input.nextInt();
while (value != 5) {
System.out.println("Enter a number: ");
// No need to declare the variable value, since it has already been defined by type int. Here, its value is updated.
value = input.nextInt();
}*/
// Variable scope accounted for, so that the variable value exists within the do while statement
int value = 0;
// do statement is always executed at least once. After one execution the while condition is checked.
do {
System.out.println("Enter a number: ");
value = input.nextInt();
System.out.println(value);
} while(value != 5);
System.out.println("Got 5!");
}
}
Application.main(null);
Notes:
- The switch statement allows you to define a variety of actions that can be taken depending on a particular value.
- The argument passed to a switch statement is typically a variable name. Within the switch, case keywords are used to define different possible values of that variable, as well as the actions that will be taken if the variable happens to have their respective value. The break keyword should be used at the end of each case to prevent more than one case action being run (if you only want one case to be run). You can have multiple cases for each switch statement.
- The default keyword is used within a switch statement, acting sort of like a case itself and defining the code that will be run if none of the cases are met.
- The values defined by cases need to constant. In other words, they are usually constant values of the variable passed to the switch statement.
public class Application {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
// The most common variable types that are checked by a switch statement are String and int.
System.out.println("Please enter a command: ");
String text = input.nextLine();
System.out.println(text);
switch (text) {
// Indent code block under each case for proper syntax.
case "start":
System.out.println("Machine started!");
break;
case "stop":
System.out.println("Machine stopped!");
break;
default:
System.out.println("Command not recognized");
}
}
}
Application.main(null);
Notes:
- The key distinction between value types (e.g. int) and reference types (e.g. arrays) is that memory is created to hold value types, while reference types refer to a list of value types. So, think of a value type as a bucket that stores something, and a reference type as sort of a label that refers to value types.
- The values within an array allocate memory to value types.
- In Java, arrays and many other data structures followed 0-based indexing, where the 1st element is located at the 0th index, 2nd is located at the 1st index, and so on.
public class Application {
public static void main(String[] args) {
// int value type
int value = 7;
// Declare array that stores values of type int.
int[] values;
// Initialize array values to have a size of 3 int values. The new keyword allocates a specified amount of memory.
values = new int[3];
// Access the element at the 0th index of the array. Java sets the default value for arrays to 0.
System.out.println(values[0]);
// Change the element at index 0 to 10.
values[0] = 10;
values[1] = 20;
values[2] = 30;
System.out.println(values[0]);
System.out.println(values[1]);
System.out.println(values[2]);
// Iterating through the array, making sure to access the elements at all indexes in the array.
// The length method returns the size of the array.
for (int i = 0; i < values.length; i++) {
System.out.println(values[i]);
}
// Initializing an array in one line. Allocating memory and setting data values at the same time.
int[] numbers = {5, 6, 7};
for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers[i]);
}
}
}
Application.main(null);
Notes:
- Enhanced for loops are an alternative way (alternate to the conventional for loop) of iterating through an array. In an enhanced for loop, the counter is always incremented by one, which means the loop executes in sequence. Nonetheless, it still simplifies the process of array iteration. An enhanced for loop essentially retrieves an element of the list during each iteration.
- The core difference between instantiation and initialization is that instantiation involves creating an object of a particular class using the new keywords, while initialization involves assigned a value to a particular variable.
- For primitive data types, the variable stores enough memory to hold any valid (within integer size constraints) integer value. Integer is a primitive type, as indicated by the lowercase declaration of int.
- String is a non-primitive data type, as indicated by the capitalization in the declaration of String. Non-primitive data types allocate enough memory for a reference (the program does not initially know how long the string will be based on the declaration), or address, to some values.
- To recap, reference variable types store references to the actual data, while value type variable directly store the actual data. When working with reference types, it is possible for two variables to reference the same data (objects), which means that changes to one variable can impact the object referenced by the other variable.
- The main difference between primitive types and non-primitive data types is that primitive types are predefined (enough memory for data is allocated during declaration), while non-primitive types are not predefined, as their values are specified by the programmer.
public class Application {
public static void main(String[] args) {
// Declare an array that stores String value types, and has a size of 3.
String[] words = new String[3];
words[0] = "Hello";
words[1] = "to";
words[2] = "you";
System.out.println(words[2]);
// Initialize and instantiate array with values in one line
// Each String value type is wrapped within double quotation marks.
String[] fruits = {"apple", "banana", "pear", "kiwi"};
// Enhanced for loop - The : symbol indicates "in". For every "fruit" or value in the array "fruits", print it to the console.
for(String fruit : fruits) {
System.out.println(fruit);
}
// Non-primitive type int
int value = 123;
// Non-primitive type String. The null value means the variable points to "nothing"
String text = null;
System.out.println(text);
// An array further references String data type references to actual values. The array is allocating enough memory for 2 references to String values.
String[] texts = new String[2];
// Java sets the default values for Strings in an array to 0.
System.out.println(texts[0]);
// Change the value of the element at the 0th index to "one", causing the array to allocate memory to a String reference to an actual String value (memory).
texts[0] = "one";
}
}
Application.main(null);
Notes:
- Multi-dimensional arrays are essentially arrays of arrays. For example, in a 2D array, each element is an array by itself, which means that each element in the 2D array contains more elements within themselves.
- Each element in an multi-dimensional array is a reference to an array, and not every array in the multi-dimensional array needs to be the same length.
- When accessing a value type in a multi-dimensional array, you need to specify multiple indices. In a 2D array, you specify the row index first, then the column index. Remember that arrays use zero-based indexing. So, the row index indicates which array within the 2D array you are accessing, and the column index indicates which value within that 1D array you are accessing.
public class Application {
public static void main(String[] args) {
// 1D array, since to specify the position of an element, only one index or dimension is needed.
int[] values = {1, 2, 3};
System.out.println(values[2]);
// Syntax for 2D array
int[][] grid = {
{1, 2, 3},
{4, 5},
{6, 7, 8, 9}
};
System.out.println(grid[1][1]);
System.out.println(grid[0][2]);
// Declare a 2D array of references to Strings, specifying its size (square or rectangular shaped)
String[][] texts = new String[2][3];
// Change value of element at row index 0 and column index 1
texts[0][1] = "Hello there";
System.out.println(texts[0][1]);
// Iterating through 2D array. Iterate through each row, and in each row, iterate through each column
for (int row = 0; row < grid.length; row++) {
for (int col = 0; col < grid[row].length; col++) {
// \t indicates tab character
System.out.print(grid[row][col] + "\t");
}
// Create a new line for every new row
System.out.println();
}
// Do not specify column size of the 2D array, which allows you to create row arrays of different sizes (different number of columns)
String[][] words = new String[2][];
System.out.println(words[0]);
// Establish the size of the array in the first row of the 2D array
words[0] = new String[3];
words[0][1] = "hey there";
System.out.println(words[0][1]);
System.out.println(words[1]);
}
}
Application.main(null);
Notes:
- If a class is declared public, the class name must be the same as the file name. You can usually only have one public class in a file, but you can have multiple non-public classes in a file.
- A class is essentially a template or blueprint for creating objects. Almost everything in Java is basically an object. A class can be viewed as a type. Objects are instances of classes, or in other words copies of particular classes. This mean that an object has the attributes and methods defined in its class type. An object typically has 3 primary characteristics: identity, state (attributes), and behavior (methods).
- Classes can contain data/attributes, or states. Classes can contain subroutines, or methods/functions, such as the main method, which automatically executes the code within it.
- The dot syntax: objectName.propertyName can be used to directly access an attribute or method of an object of a class (if encapsulation is not used but more on that later).
// Class
public class Application {
// main method with proper syntax to run program
public static void main(String[] args) {
// Initialize an object of class type "Person", with the variable name "person1"
Person person1 = new Person();
// Instantiate values for the data stored within the person1 object.
// The dot syntax specifies the property of a particular object.
person1.name = "Joe Mama";
person1.age = 19;
// Create new object of class type Person
Person person2 = new Person();
person2.name = "someone else";
person2.age = 21;
// Access the data stored within the person1 object.
System.out.println(person1.name);
System.out.println(person1.age);
System.out.println(person2.name);
System.out.println(person2.age);
}
}
// Create new class "Person"
class Person {
// Data
// Instance variables represent data or states.
// Declare variables which do not have values yet.
String name;
int age;
// Subroutines (methods)
}
Application.main(null);
Notes:
- A subroutine that is a part of a class is called a method. A method can be viewed as a behavior of a class, which performs a specific task(s). The code block (within the curly brackets of the method) following the method name is what the method runs when called upon. In order for a method to actually run, it needs to be called.
- Method names should follow camel case naming convention, where the first letter of the first word is lowercase, but the first letters of the words after the first word are uppercase.
- Methods have access to instance variables within the same class. Method names are followed by parenthesis (), while data/attribute names are not.
class Person {
String name;
int age;
// Subroutines
// Creating a method called "speak()"
// The speak() method concatenates the data of the class into a String.
void speak() {
// Repeat 3 times.
for (int i = 0; i < 3; i++) {
System.out.println("My name is: " + name + " and I am " + age + " years old");
}
}
void sayHello() {
System.out.println("Hey there!");
}
}
public class Application {
public static void main(String[] args) {
Person person1 = new Person();
person1.name = "Joe Mama";
person1.age = 17;
// Call the speak() method
person1.speak();
person1.sayHello();
// person1 and person2 have different outputs for speak(), because they have different data.
Person person2 = new Person();
person2.name = "somebody";
person2.age = 20;
person2.speak();
person2.sayHello();
}
}
Application.main(null);
Notes:
- Getter methods are typically used to retrieve/return data from a object of a particular class, or calculate and return a value based on the properties of the object.
- The void keyword in a Java method indicates that the method does not return a value. void is a method return type, and there are various kinds of return types in Java. The return type indicates the type of data a method returns when called upon, and is written before the method name.
- The return keyword within a method block, upon run upon, causes the method to stop running. It then causes the method to "return" or store the specified value next to the return keyword. The method, when called upon, returns or represents that specific value.
- The return value should be able to have the same data type as that specified next to the method name. Every method should always end up returning a value that matches the specified return type.
- Encapsulation involves "hiding" data from a certain class from other classes. Encapsulation of a certain class prevents other classes from directly accessing its data. It is important to note that instance variables encapsulated within a particular class can be directly accessed within that class, but can only be retrieved by separate classes using getter methods.
- Getter methods, which are typically public, are usually used to "get" data from a separate class whose data has been encapsulated. In these cases, the data from the separate class can not be directly accessed using the object.property notation, so getter methods need to be used (e.g. object.getProperty()).
- The naming convention of a getter method usually involves putting the lowercase keyword "get" in front of the data name, then capitalizing the first letter of all of the words of the data name.
class Person {
// Instance data
String name;
int age;
// The speak() method has void, so it does not return anything. In this case, this method is used to output a message.
void speak() {
System.out.println("My name is: " + name);
}
// The calculateYearsToRetirement() method returns an int value
int calculateYearsToRetirement() {
int yearsLeft = 65 - age;
return yearsLeft;
}
// The getter method "getAge()" returns the age (type int) attribute of the object of this particular class type on which it is called upon.
int getAge() {
return age;
}
// The getter method "getName()" returns the name (type String) attribute.
String getName() {
return name;
}
}
public class Application {
public static void main(String[] args) {
Person person1 = new Person();
// Set the values of the attributes of the person1 object.
// Here, encapsulation is not used, as this separate class is able to directly access the data within the Person class.
person1.name = "Dylan";
person1.age = 21;
person1.speak();
// Set the variable "years" to the return value of the method "calculateYearsToRetirement".
int years = person1.calculateYearsToRetirement();
System.out.println("Years until retirement: " + years);
int age = person1.getAge();
String name = person1.getName();
System.out.println("Name is: " + name);
System.out.println("Age is: " + age);
}
}
Application.main(null);
Notes:
- Values can be passed into methods. The parenthesis after the method definition hold the parameters of the method, which specify the kind of values that the method can accept into its code block and use. Parameter variables are only available to the scope of code within the method, though their values can be used to change the values of variables in a wider scope.
- Method arguments are that actual values passed to the method when it is called, and are used while the method is running. Once an argument value is passed, the parameter variable defined will then store that argument, and can be used within the method.
- Multiple method parameters (and arguments) should be separated by commas, and when the arguments are passed to the method, the arguments should correctly correspond (same variable type to respective parameter) to their respective parameters, in terms of position (order in which parameters and arguments are defined).
- When you pass a variable as a method argument, its value will be assigned to the parameter variable defined in the method, not its original argument variable name.
- Non-primitive variable and primitive variables pass slightly differently. For non-primitive variables like Strings (reference to memory), the method changes the reference label of the argument value passed by giving that same value a different reference label (i.e. variable name). For primitive variables like integers (memory that contains data), the method allocates memory for the parameter variable to hold the value transferred from the argument variable.
class Robot {
// Create a public method called "speak()"
// "text" variable is a parameter of the speak() method
public void speak(String text) {
System.out.println(text);
}
// Create public method that takes of parameter of type int
public void jump(int height) {
System.out.println("Jumping: " + height);
}
// Create a public method that takes multiple parameters of different data types
public void move(String direction, double distance) {
System.out.println("Moving " + distance + " meters in direction " + direction);
}
}
public class Application {
public static void main(String[] args) {
Robot sam = new Robot();
// Pass an argument to the method speak()
sam.speak("Hey, I'm sam");
// Pass an argument to the method jump()
sam.jump(7);
// Pass 2 arguments of different variable types to the method move()
sam.move("West", 12.2);
// Pass the value of the String (reference) variable "greeting" to the speak() method
String greeting = "Hello there";
sam.speak(greeting);
// Pass the value of the int (actual memory) variable "value" to the jump() method
int value = 15;
sam.jump(value);
}
}
Application.main(null);
Notes:
- Setters, also known as mutators, are methods used to update the value of a specific variable typically found within a class. Getter and setter methods are usually used when instance variables are encapsulated (set to private), which means that the variables of the class cannot be directly accessed.
- Setter and getter methods allow you to, from a separate class, retrieve and modify the attributes of another class with out directly accessing the instance variables of that class. Setters and getters are used in conjunction with encapsulation.
- In the case of the instance variable having the same name as the parameter variable, since the parameter variable was declared first, it takes precedence and masks the instance variable's name.
- The "this" keyword refers (think of a substitute name sort of) to the current object within which it is declared. It is typically used to access attributes and methods within the same object.
class Frog {
private String name;
private int age;
private String alternateName;
// Setter method sets the instance variable name to the value of the variable newName
public void setName(String newName) {
name = newName;
}
// Naming convention of a setter method. The setter method should not return anything (void return type), since it performs the action of modifying a variable.
public void setAge(int age) {
this.age = age;
}
// In this case, use the "this" keyword, since the instance variable has the same name as the parameter variable.
// The "this" keyword refers to the instance variable of the class, while alternateName refers to the parameter variable.
public void setAlternateName(String alternateName) {
// In the example we have, "this" refers to the Frog object frog1 when run.
this.alternateName = alternateName;
}
// You do not need the "this" keyword here, since there is no variable ambiguity.
// We are calling the methods within the same class.
public void setInfo(String name, int age) {
setName(name);
setAge(age);
}
// Use the public keyword to minimize error from get methods. More on that later.
public String getName() {
return name;
}
// You do not need the "this" keyword for getter methods, since there is no variable ambiguity.
// There is not parameter variable.
public int getAge() {
return age;
}
public String getAlternateName() {
return alternateName;
}
}
public class Application {
public static void main(String[] args) {
Frog frog1 = new Frog();
// Setting the name variable of the Frog object using dot notation.
//frog1.name = "Bertie";
//frog1.age = 1;
// Setting the name variable of the Frog object using a setter method.
frog1.setName("Dylan");
frog1.setAge(2);
frog1.setAlternateName("Obito");
System.out.println(frog1.getName());
System.out.println(frog1.getAge());
System.out.println(frog1.getAlternateName());
frog1.setInfo("Chris", 3);
System.out.println(frog1.getName());
System.out.println(frog1.getAge());
}
}
Application.main(null);
Notes:
- A constructor is a special type of method that is by default run every time an instance (object) of a particular class is created. A constructor does not have a return type, and always has to have the same name as the class.
- A constructor is typically used to initialize the instance variables of an object, or perform initial tasks (by passing arguments into constructor parameters)
- Like methods, there can be multiple constructors with the same name, as long as the parameters are different each time. Java automatically selects the correct constructor on the creation of an object based on the parameter list.
- The default constructor is when no constructor is defined at all, which means that the instantiation of the object takes no parameters and performs no other initial actions.
- Multiple constructors are typically ran all at once when you want a particular instance variable/task(s) to always be initialized.
- Use the "this" keyword to run a constructor within another constructor. Note that the inner constructor ran should always be at the top of the code block of the outer constructor.
- Constructors can only have access modifiers (e.g. public, protected, private, or default). A public constructor means objects of the class can be defined anywhere else. A private constructor means objects of the class can only be defined inside that class. A protected constructor means objects of the class can be defined within the same package or any subclass (can be outside of package). A constructor with default access modifier means objects of the class can be defined within the same package or any subclass (within package).
class Machine {
private String name;
private int code;
// Naming convention of constructor. There should always be a public keyword in front of the constructor name.
public Machine() {
System.out.println("First constructor running!");
// Initialize the value of the private instance variable name.
name = "Arnie";
}
// Different constructor that sits the value of name based on a parameter value
public Machine(String name) {
// Use "this" to call another constructor within the constructor.
// Initialize the values of the private instance variables name and code, by default.
// The third constructor will be ran first in the second constructor
this(name, 0);
System.out.println("Second constructor running!");
this.name = name;
}
public Machine(String name, int code) {
System.out.println("Third constructor running!");
this.name = name;
this.code = code;
}
public String getName() {
return name;
}
public int getCode() {
return code;
}
}
public class Application {
public static void main(String[] args) {
// Create an instance/object of the class Machine. The instance is created primarily with the "new" keyword.
// Once the object is created, the constructor is ran automatically.
Machine machine1 = new Machine();
System.out.println(machine1.getName());
// Run the constructor without storing the new instance within a variable.
//new Machine();
// Run second constructor by passing different arguments
Machine machine2 = new Machine("Bertie");
System.out.println(machine2.getName());
System.out.println(machine2.getCode());
// Run third constructor by passing different arguments
Machine machine3 = new Machine("Charlie", 7);
System.out.println(machine3.getName());
System.out.println(machine3.getCode());
}
}
Application.main(null);
Notes:
- A static variable, sort of like a global variable, belongs to the class instead of an instance/object of the class, meaning that there is only one copy of it. An instance variable (non-static variable), sort of like a local variable, is created for a specific object each time an object is initialized.
- There is only one space of memory allocated for a static variable, regardless of the number of objects created. This means that a static variable is shared amongst multiple objects of the same class. So, once the value of a static variable is set, it will stay that way for all subsequent objects of the class, unless changed later on. In regard to accessing static variables, they can be directly accessed within a class, can be accessed using dot notation and the class name as the reference outside of the class, and can be accessed using getter and setter methods (if they are private).
- An instance (non-static) method is created each time an instance of a class is created, while a static method belongs to the class itself rather than objects on that class. Static methods can be ran without creating an object of the class it is in, and static methods can directly access static attributes and methods but not instance variables. This means that for a static method to access instance attributes and methods, an object of the class (that the attributes and methods are contained within) needs to be initialized first, then the instance variables can be accessed with the object as a reference.
- Instance methods can access both instance and static attributes and methods, since objects of a class are created after static data and methods are initialized.
- The Math.class library of Java contains mathematical constants and operations. Constant variables cannot be assigned a new value, hence they are "constant". Constant variables are conventionally named in all uppercase, with underscores sometimes.
- To create a constant variable in Java, use the "final" keyword, since it indicates that the variable can only be assigned a value once (it cannot be reassigned a value).
- Static variables and methods can be used to keep track of the objects created of a specific class. This is because static variables and methods maintain the values/actions they have been assigned through accessing the class itself; creating new instances of the class do not reset static variables and methods.
- Static variables can be used to assign an instance variable unique ID to each object created of a class, since they can be modified from their current value each time a constructor of the class is called, and can be assigned to instance variables. Instance variables can also be assigned to static variables, since although they are created after static variables, static variables can still be modified within the class.
class Thing {
// Create 2 public instance variables that can be directly accessed by another class.
// name is an instance variable.
public String name;
// description is a static variable.
public static String description;
// Create a public, final, and static integer variable called "LUCKY_NUMBER".
// Final variables need to be assigned a value once they are declared.
public final static int LUCKY_NUMBER = 777;
// Initialize the static variable to be 0.
public static int count = 0;
// The variable id should be unique for each object.
public int id;
// Constructor that runs whenever a instance of Thing is created
public Thing() {
// Assign the value of the static variable count to the instance variable id.
id = count;
// Increment count by 1 each time an object of Thing is created. The static variable count belongs to the class itself.
count++;
}
// Non-static method
public void showName() {
// Access instance and static variables, printing out the object's id (instance), description (static), and name (instance).
System.out.println("Object ID: " + id + ", " + description + ": " + name);
}
// Static method
public static void showInfo() {
// Access static variable within a static method.
System.out.println(description);
// Won't work, since static data/methods are created before any objects as well as the instance variables.
// System.out.println(name);
}
}
public class Application {
public static void main(String[] args) {
// Instantiate the static variable "description" of the class Thing
// Since description is static, an object of Thing does not have to be created to access it, rather we use the class as a reference to access description
// Here, we access description using the dot notation, with the class name used instead of object name
Thing.description = "I am a thing";
System.out.println(Thing.description);
// Call a static method of the class Thing, referring the class name instead of the object name, since static properties refer to the class itself
Thing.showInfo();
System.out.println("Before creating objects, count is: " + Thing.count);
Thing thing1 = new Thing();
Thing thing2 = new Thing();
thing1.name = "Bob";
thing2.name = "Sue";
// Call non-static methods of objects of the class Thing.
thing1.showName();
thing2.showName();
// Static variable count changes each time an object is created.
System.out.println("After creating objects, count is: " + Thing.count);
// Access the constant PI variable of the Math class/library.
System.out.println(Math.PI);
// Access the constant/final LUCKY_NUMBER variable of the Thing class.
System.out.println(Thing.LUCKY_NUMBER);
}
}
Application.main(null);
Notes:
- In Java, Strings are technically immutable, meaning that once they are created, they cannot be changed. When you initialize the value for a String variable, that value will remain in the program for the rest of its life. Each time you perform actions/modifications to a String variable, it is actually being reassigned to a new specified value (after the modifications), and the previous values stay in the program's memory.
- In Java, the StringBuilder class allows you to essentially create a String variable/object that is mutable, which means that whenever modifications are made to it, its value is actually changed within the program; it is not creating a new value every time, but is instead modifying the current content.
- In short, the StringBuilder is a more memory-efficient alternative to the classic String in terms of appending/concatenating text.
- Method chaining involves calling multiple methods at once on a variable. Each method in the chain returns the current state of the object. For example, the append() method of the StringBuilder class returns a reference to the StringBuilder object each time it is called.
- StringBuffer is a thread-safe alternative to StringBuilder. A StringBuffer is safe to access through multiple different threads. StringBuilder is more "lightweight" because it is not as thread-safe as StringBuffer.
- In Java you can implement different types of String formatting using the backslash syntax. For example, \t creates a new String tab, and \n creates a new String line.
- The println() syntax: the "print" part means the program will print the specified text within the println method. The "ln" part means the program will print a new line after the text.
- The printf() method allows you to print formatting Strings. printf does not print a new line at the end. For each formatting String, the printf will look at the arguments and replace each formatting String (with specified type) with the appropriate corresponding (desired arguments should be placed in same order as formatting Strings) argument value.
public class Application {
public static void main(String[] args) {
// Concatenate Strings to the String variable info.
// Each concatenation involves assigning info to the new combined String value.
String info = "";
info += "My name is Bob.";
info += " ";
info += "I am a builder.";
System.out.println(info);
// Initialize StringBuilder Object sb with an initial value of an empty String.
StringBuilder sb = new StringBuilder("");
// Concatenate Strings to the sb object using the append() method. sb's current value is modified each time.
sb.append("My name is Sue.");
sb.append(" ");
sb.append("I am a lion tamer.");
// Print out the String value of the sb StringBuilder object using the toString() method.
System.out.println(sb.toString());
// Initialize StringBuilder object s with no initial value.
StringBuilder s = new StringBuilder();
// Using method chaining to append multiple different Strings at a time to the StringBuilder object s.
// Put semi-colon after last method (or last part of code) is called.
s.append("My name is Roger.").append(" ").append("I am a skydiver.");
System.out.println(s.toString());
// Multi-slash comment lol
///////// Formatting ///////
// String formatting using the backslash format.
System.out.print("Here is some text.\tThat was a tab.\nThat was a new line.");
// This text will be printed on the same line as the previous text.
System.out.println("More text.");
/// Formatting integers
// %d represents integer value.
// The number of format Strings should be the same as the number of arguments.
// %10d indicates that the format String will be outputted in a field that is 10 characters wide (right-aligned). %-10d indicates that it will be left-aligned in the 10-character wide field.
System.out.printf("Total cost %-10d: quantity is %10d\n", 5, 120);
for (int i = 0; i < 20; i++) {
// All of the printed text will line up since the format character is always right-aligned.
// The String format syntax involves 2 main components: the % which indicates a format String, and the character at the end which indicates the type of String formatting.
// The flags in between the % and end character specify various properties of the format String, such as the width of output space (specified by number).
// %s indicates a format String of type String.
System.out.printf("%2d: %s\n", i, "Here is some text");
}
/// Formatting floating point values
// The .2 within the floating point String formatting specifies that the floating point String should only contain up to 2 decimal places.
// The floating point value will round.
System.out.printf("Total value: %.2f\n", 5.678);
// Specify both the width (right-aligned and left-parted with spaces for positive width. Opposite for negative width) of character output and the number of decimal places.
// Note that the width includes all of the characters being outputted.
System.out.printf("Total value: %6.1f\n", 343.23523);
}
}
Application.main(null);
Notes:
- Every Java object has the Object class that is their ultimate parent class. This means that all objects inherit all of the properties/methods of the Objet class.
- The toString() method is a built-in method of Java that returns a certain value, usually of an object, in a String format. It typically is the String representation of an object (by default if no toString() method is defined), but can be modified to return certain properties of an object.
- The toString() method can redefined in a class to help you better identify a particular object and its properties.
class Frog {
private int id;
private String name;
public Frog(int id, String name) {
this.id = id;
this.name = name;
}
// Syntax for creating a custom toString() method for the Frog class.
// Modifying the toString() method from the Object class.
public String toString() {
/* One possibility of returning data */
/*
// Use StringBuilder to be more memory efficient.
StringBuilder sb = new StringBuilder();
sb.append(id).append(":").append(name);
// StringBuilder has its own toString() method to effectively return its object value in a String format.
return sb.toString();
*/
/* Another possibility of returning data */
// Use String.method() to return a formatted String with the same data values as before. Works similarly to printf().
return String.format("%-4d: %s", id, name);
}
}
public class Application {
public static void main(String[] args) {
// Initialize an object of the ultimate parent Object class.
Object obj = new Object();
// Initialize objects of the Frog class, which has the Object class as its ultimate parent class.
Frog frog1 = new Frog(7, "Freddy");
Frog frog2 = new Frog(5, "Roger");
// Output value of the toString() method for the different objects of the Frog class.
System.out.println(frog1.toString());
System.out.println(frog2.toString());
}
}
Application.main(null);
Notes:
- Inheritance is essentially a mechanism that allows for hierarchy amongst different classes. Using the extends keyword, a child class is able to inherit all of the properties/attributes and methods of the parent class. In addition, more attributes and methods can be defined in the child class, which belong to the child class but not the parent class.
- A child class can override the attributes and methods inherited from the parent class (To override an attribute or method, the same modifiers and variable name should be used, but with a different value/body defined for the attribute or method. Changing the modifier with the same name would produce an error, but changing the name or changing the parameters would create a new method that would be called a different way). Inheritance is transitive, which means that a child class 1 that inherits from another child class 2 ultimately inherits from the parent class of child class 2. In this case, parent class is the superclass of both child class 2 and child class 1.
- A child class can inherit static attributes and methods from a parent class, but it cannot override the original values and bodies of those attributes and methods (if they have any). If the child class changes the values and bodies of static attributes and methods, those values and bodies will be hidden/overturned by the original values and bodies of the parent class's static attributes and methods.
- Privately declared properties can only be directly accessed within the class. This means that a child class does not inherit the private variables of its parent class. However, a child class can inherit private variables from its parent class if those private variables have getter and setter methods, which allow the child class to actually access and change the inherited encapsulated variables (because the child class inherits the public getters and setters).
- Access modifiers (e.g. public, protected, default, and private) are basically keywords that define the visibility of the properties, methods, and constructors of a class. Variables without access modifiers are typically accessible throughout the package.
- The protected keyword indicates that a variable is accessible anywhere in the package and can be inherited by child classes. Try to avoid overriding the variables of a parent class from a child class.
- The super keyword refers to the parent class of the child class it is defined within. It can be used as a superclass constructor, where the child class's constructor first calls the constructor of the parent class to possibly instantiate needed variables (mostly inherited variables) or call needed methods in the parent class from the child class, using super(parameters if necessary); (Subclass does not inherit constructor, but can manually call the constructor of the super class). It can also be used to call superclass and attributes methods, where the child class implements the parent class's version of a particular variable or method, using the super keyword plus the dot notation (e.g. super.attributeName, super.methodName()).
// Syntax of creating the parent class Machine.
class Machine {
// private variable name that is specific to the parent class, and cannot be directly accessed by the child class.
private String name = "Machine Type 1";
// Variable without access modifier that can be accessed by the child class, since the child class is in the same package as the parent class.
String description = "I am something";
// Variable with protected access modifier that is inherited by the child class.
protected int year = 2023;
public void start() {
System.out.println("Machine started.");
}
public void stop() {
System.out.println("Machine stopped.");
}
}
// Syntax of creating a child class Car of the parent class Machine.
class Car extends Machine {
// Override (same name and parameters) the start() method from the parent class inside the child class
// The annotation (@) of an override.
@Override
public void start() {
// Call the parent class's version of the start() method using the super keyword.
super.start();
System.out.println("Car started.");
}
// The wipeWindShield() method belongs to the child class but not the parent class.
// However, all of the methods of the parent class belong to the child class.
public void wipeWindShield() {
System.out.println("Wiping windshield.");
}
public void showInfo() {
// The child class inherits non-private variables from the parent class.
System.out.println("Car description: " + description + ", Year Made: " + year);
}
}
public class Application {
public static void main(String[] args) {
// Initialize object of the Machine parent class
// mach1 can only run the defined methods of Machine (and only defined in Machine), since it is defined as a variable of type Machine that points an object of the Machine class.
Machine mach1 = new Machine();
mach1.start();
mach1.stop();
System.out.println();
// Initialize object of the Car child class, which inherits the attributes and methods of the Machine parent class
// car1 can run the defined methods of both Machine and Car, since it is defined as a variable of type Car which points to an object of the Car class, which inherited from the Machine class.
Car car1 = new Car();
car1.start();
car1.wipeWindShield();
car1.showInfo();
car1.stop();
System.out.println();
// The Car child class is of type Machine but the Machine parent class is not of type Car.
// car2 can only run the defined methods of Machine (though it will also run the redefined methods of Machine inside the Car class), since it is defined as a variable of type Machine that points to an object of the Car class.
Machine car2 = new Car();
car2.start();
car2.stop();
// This will not work, as all cars may be machines, but not all machines are cars
// The object type of Machine will conflict with the variable type of Car, since Machine is the parent, not the child, of Car
// Car car3 = new Machine();
// Creating objects for both the Parent and Child classes
Parent parent1 = new Parent("Bob");
System.out.println(parent1.getName1());
Child child1 = new Child("John");
System.out.println(child1.getName1());
System.out.println(child1.getName2());
}
}
class Parent {
String name1;
// Parent constructor
public Parent(String name1) {
this.name1 = name1;
}
public String getName1() {
return name1;
}
}
// The Child class inherits the name1 instance variable from the Parent class
class Child extends Parent {
String name2;
// Child constructor
public Child(String name2) {
// Invoke the parent's constructor and pass the appropriate arguments.
// Calling the parent's constructor allows the Child class to instantiate the value of the name1 instance variable found in the Parent class
// The value of the name1 variable here belongs to the object of the Child class, but not the Parent class
super("Jonathan");
// Initialize the name2 instance variable of the Child class.
this.name2 = name2;
}
public String getName2() {
return name2;
}
}
Application.main(null);