0% found this document useful (0 votes)
54 views

Java Basics Continued

Class attributes, also known as fields or variables, are defined within a class. They can be accessed using dot notation on an object of that class. Constructors are special methods that initialize objects and can set initial values for attributes. Constructors may take parameters to initialize attributes.

Uploaded by

Amir Khan
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
54 views

Java Basics Continued

Class attributes, also known as fields or variables, are defined within a class. They can be accessed using dot notation on an object of that class. Constructors are special methods that initialize objects and can set initial values for attributes. Constructors may take parameters to initialize attributes.

Uploaded by

Amir Khan
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 39

Java Class Attributes

In the previous chapter, we used the term "variable" for x in the example (as
shown below). It is actually an attribute of the class. Or you could say that
class attributes are variables within a class:

Example
Create a class called "Main" with two attributes: x and y:

public class Main {

int x = 5;

int y = 3;

Another term for class attributes is fields.

Accessing Attributes
You can access attributes by creating an object of the class, and by using the
dot syntax (.):

The following example will create an object of the Main class, with the
name myObj. We use the x attribute on the object to print its value:

Example
Create an object called "myObj" and print the value of x:

public class Main {

int x = 5;

public static void main(String[] args) {

Main myObj = new Main();

System.out.println(myObj.x);

}
Modify Attributes
You can also modify attribute values:

Example
Set the value of x to 40:

public class Main {

int x;

public static void main(String[] args) {

Main myObj = new Main();

myObj.x = 40;

System.out.println(myObj.x);

Or override existing values:

Example
Change the value of x to 25:

public class Main {

int x = 10;

public static void main(String[] args) {

Main myObj = new Main();

myObj.x = 25; // x is now 25

System.out.println(myObj.x);

}
If you don't want the ability to override existing values, declare the attribute
as final:

Example
public class Main {

final int x = 10;

public static void main(String[] args) {

Main myObj = new Main();

myObj.x = 25; // will generate an error: cannot assign a value to a


final variable

System.out.println(myObj.x);

The final keyword is useful when you want a variable to always store the same
value, like PI (3.14159...).

The final keyword is called a "modifier". You will learn more about these in
the Java access modifiers.
Multiple Objects
If you create multiple objects of one class, you can change the attribute values
in one object, without affecting the attribute values in the other:

Example

Change the value of x to 25 in myObj2, and leave x in myObj1 unchanged:


public class Main {
int x = 5;
public static void main(String[] args) {
Main myObj1 = new Main(); // Object 1
Main myObj2 = new Main(); // Object 2
myObj2.x = 25;
System.out.println(myObj1.x); // Outputs 5
System.out.println(myObj2.x); // Outputs 25
}

Multiple Attributes
You can specify as many attributes as you want:

Example
public class Main {
String fname = "John";
String lname = "Doe";
int age = 24;
public static void main(String[] args) {
Main myObj = new Main();
System.out.println("Name: " + myObj.fname + " " + myObj.lname);
System.out.println("Age: " + myObj.age);
}

}
Java Class Methods
You learned from the Java Methods chapter that methods are declared within a
class, and that they are used to perform certain actions:

Example
Create a method named myMethod() in Main:

public class Main {

static void myMethod() {

System.out.println("Hello World!");

myMethod() prints a text (the action), when it is called. To call a method, write
the method's name followed by two parentheses () and a semicolon;

Example
Inside main, call myMethod():

public class Main {

static void myMethod() {

System.out.println("Hello World!");

public static void main(String[] args) {

myMethod();

// Outputs "Hello World!"


Static vs. Non-Static
Static Method
A static method is also called a class method and is common across the objects of the
class and this method can be accessed using class name as well.

Non-Static Method
Any method of a class which is not static is called non-static method or an instance
method.
Following are some of the important differences between static and non-static method.

Sr. Key Static Non-Static


No.

Access A static method can A non-static method can


access only static access both static as well as
1 members and cannot non-static members.
access non-static
members.

Overriding A static method cannot be A non-static method can be


2 overridden being. overridden.

Keyword A static method is A normal method is not


3 declared using static required to have any special
keyword. keyword.
Example
Example of static vs non-static method

public class JavaTester {

public static void main(String args[]) {

Tiger.roar();

Tiger tiger = new Tiger();

tiger.eat();

class Tiger {

public void eat(){

System.out.println("Tiger eats");

public static void roar(){

System.out.println("Tiger roars");

Output
Tiger roars
Tiger eats
Access Methods With an Object
Example
Create a Car object named myCar. Call
the fullThrottle() and speed() methods on the myCar object, and run the
program:

// Create a Main class


public class Main {

// Create a fullThrottle() method


public void fullThrottle() {
System.out.println("The car is going as fast as it can!");
}

// Create a speed() method and add a parameter


public void speed(int maxSpeed) {
System.out.println("Max speed is: " + maxSpeed);
}

// Inside main, call the methods on the myCar object


public static void main(String[] args) {
Main myCar = new Main(); // Create a myCar object
myCar.fullThrottle(); // Call the fullThrottle() method
myCar.speed(200); // Call the speed() method
}
}

// The car is going as fast as it can!


// Max speed is: 200
Example explained
1) We created a custom Main class with the class keyword.

2) We created the fullThrottle() and speed() methods in the Main class.

3) The fullThrottle() method and the speed() method will print out some
text, when they are called.

4) The speed() method accepts an int parameter called maxSpeed - we will use
this in 8).

5) In order to use the Main class and its methods, we need to create
an object of the Main Class.

6) Then, go to the main() method, which you know by now is a built-in Java
method that runs your program (any code inside main is executed).

7) By using the new keyword we created an object with the name myCar.

8) Then, we call the fullThrottle() and speed() methods on the myCar object,
and run the program using the name of the object (myCar), followed by a dot
(.), followed by the name of the method (fullThrottle(); and speed(200);).
Notice that we add an int parameter of 200 inside the speed() method.

Remember that..
The dot (.) is used to access the object's attributes and methods.

To call a method in Java, write the method name followed by a set of


parentheses (), followed by a semicolon (;).

A class must have a matching filename (Main and Main.java).


Using Multiple Classes
Like we specified in the classes topic, it is a good practice to create an object of
a class and access it in another class.

Remember that the name of the java file should match the class name. In this
example, we have created two files in the same directory:

 Main.java
 Second.java

Main.java

public class Main {

public void fullThrottle() {

System.out.println("The car is going as fast as it can!");

public void speed(int maxSpeed) {

System.out.println("Max speed is: " + maxSpeed);

Second.java

class Second {

public static void main(String[] args) {

Main myCar = new Main(); // Create a myCar object

myCar.fullThrottle(); // Call the fullThrottle() method

myCar.speed(200); // Call the speed() method

And the output will be:

The car is going as fast as it can!


Max speed is: 200
Java Constructors
A constructor in Java is a special method that is used to initialize objects. The
constructor is called when an object of a class is created. It can be used to set
initial values for object attributes:

Example
Create a constructor:

// Create a Main class


public class Main {
int x; // Create a class attribute

// Create a class constructor for the Main class


public Main() {
x = 5; // Set the initial value for the class attribute x
}

public static void main(String[] args) {


Main myObj = new Main(); // Create an object of class Main (This will
call the constructor)
System.out.println(myObj.x); // Print the value of x
}
}

// Outputs 5

Note that the constructor name must match the class name, and it cannot
have a return type (like void).

Also note that the constructor is called when the object is created.

All classes have constructors by default: if you do not create a class constructor
yourself, Java creates one for you. However, then you are not able to set initial
values for object attributes.
Constructor Parameters
Constructors can also take parameters, which is used to initialize attributes.

The following example adds an int y parameter to the constructor. Inside the
constructor we set x to y (x=y). When we call the constructor, we pass a
parameter to the constructor (5), which will set the value of x to 5:

Example
public class Main {

int x;

public Main(int y) {

x = y;

public static void main(String[] args) {

Main myObj = new Main(5);

System.out.println(myObj.x);

// Outputs 5

You can have as many parameters as you want:


Example
public class Main {

int modelYear;

String modelName;

public Main(int year, String name) {

modelYear = year;

modelName = name;

public static void main(String[] args) {

Main myCar = new Main(1969, "Mustang");

System.out.println(myCar.modelYear + " " + myCar.modelName);

// Outputs 1969 Mustang


Modifiers
By now, you are quite familiar with the public keyword that appears in almost all
of our examples:

public class Main

The public keyword is an access modifier, meaning that it is used to set the
access level for classes, attributes, methods and constructors.

We divide modifiers into two groups:

 Access Modifiers - controls the access level


 Non-Access Modifiers - do not control access level, but provides other
functionality

Access Modifiers
For classes, you can use either public or default:

Modifier Description

public The class is accessible by any other class

default The class is only accessible by classes in the same package.

For attributes, methods and constructors, you can use the one of the
following:
Modifier Description

public The code is accessible for all classes

private The code is only accessible within the declared class

default The code is only accessible in the same package. This is used when you don't
specify a modifier.

protected The code is accessible in the same package and subclasses.

Non-Access Modifiers
For classes, you can use either final or abstract:

Modifier Description

final The class cannot be inherited by other classes (You will learn more about inheritance
below.)

abstract The class cannot be used to create objects (To access an abstract class, it must be
inherited from another class.)
For attributes and methods, you can use the one of the following:

Modifier Description

final Attributes and methods cannot be overridden/modified

static Attributes and methods belongs to the class, rather than an object

abstract Can only be used in an abstract class, and can only be used on methods. The
method does not have a body, for example abstract void run();. The body is
provided by the subclass (inherited from). You will learn more about inheritance
and abstraction in the Inheritance and Abstraction below.
Final
If you don't want the ability to override existing attribute values, declare
attributes as final:

Example
public class Main {

final int x = 10;

final double PI = 3.14;

public static void main(String[] args) {

Main myObj = new Main();

myObj.x = 50; // will generate an error: cannot assign a value to a


final variable

myObj.PI = 25; // will generate an error: cannot assign a value to a


final variable

System.out.println(myObj.x);

}
Static
A static method means that it can be accessed without creating an object of the
class, unlike public:

Example
An example to demonstrate the differences between static and public methods:

public class Main {


// Static method
static void myStaticMethod() {
System.out.println("Static methods can be called without creating
objects");
}

// Public method
public void myPublicMethod() {
System.out.println("Public methods must be called by creating
objects");
}

// Main method
public static void main(String[ ] args) {
myStaticMethod(); // Call the static method
// myPublicMethod(); This would output an error

Main myObj = new Main(); // Create an object of Main


myObj.myPublicMethod(); // Call the public method
}
}
Abstract
An abstract method belongs to an abstract class, and it does not have a body.
The body is provided by the subclass:

Example
// Code from filename: Main.java
// abstract class
abstract class Main {
public String fname = "John";
public int age = 24;
public abstract void study(); // abstract method
}

// Subclass (inherit from Main)


class Student extends Main {
public int graduationYear = 2018;
public void study() { // the body of the abstract method is provided here
System.out.println("Studying all day long");
}
}
// End code from filename: Main.java

// Code from filename: Second.java


class Second {
public static void main(String[] args) {
// create an object of the Student class (which inherits attributes and methods
from Main)
Student myObj = new Student();

System.out.println("Name: " + myObj.fname);


System.out.println("Age: " + myObj.age);
System.out.println("Graduation Year: " + myObj.graduationYear);
myObj.study(); // call abstract method
}
}
Java Packages & API
A package in Java is used to group related classes. Think of it as a folder in a
file directory. We use packages to avoid name conflicts, and to write a better
maintainable code. Packages are divided into two categories:

 Built-in Packages (packages from the Java API)


 User-defined Packages (create your own packages)

Built-in Packages
The Java API is a library of prewritten classes, that are free to use, included in
the Java Development Environment.

The library contains components for managing input, database programming,


and much more. The complete list can be found at Oracles
website: https://docs.oracle.com/javase/8/docs/api/.

The library is divided into packages and classes. Meaning you can either
import a single class (along with its methods and attributes), or a whole
package that contain all the classes that belong to the specified package.

To use a class or a package from the library, you need to use


the import keyword:

Syntax
import package.name.Class; // Import a single class

import package.name.*; // Import the whole package


Import a Class
If you find a class you want to use, for example, the Scanner class, which is
used to get user input, write the following code:

Example
import java.util.Scanner;

In the example above, java.util is a package, while Scanner is a class of


the java.util package.

To use the Scanner class, create an object of the class and use any of the
available methods found in the Scanner class documentation. In our example,
we will use the nextLine() method, which is used to read a complete line:

Example
Using the Scanner class to get user input:

import java.util.Scanner;

class MyClass {

public static void main(String[] args) {

Scanner myObj = new Scanner(System.in);

System.out.println("Enter username");

String userName = myObj.nextLine();

System.out.println("Username is: " + userName);

}
Import a Package
There are many packages to choose from. In the previous example, we used
the Scanner class from the java.util package. This package also contains date
and time facilities, random-number generator and other utility classes.

To import a whole package, end the sentence with an asterisk sign (*). The
following example will import ALL the classes in the java.util package:

Example
import java.util.*;

Java Inheritance (Subclass and


Superclass)
In Java, it is possible to inherit attributes and methods from one class to
another. We group the "inheritance concept" into two categories:

 subclass (child) - the class that inherits from another class


 superclass (parent) - the class being inherited from

To inherit from a class, use the extends keyword.

In the example below, the Car class (subclass) inherits the attributes and
methods from the Vehicle class (superclass):

Example
class Vehicle {

protected String brand = "Ford"; // Vehicle attribute

public void honk() { // Vehicle method

System.out.println("Tuut, tuut!");

}
class Car extends Vehicle {

private String modelName = "Mustang"; // Car attribute

public static void main(String[] args) {

// Create a myCar object

Car myCar = new Car();

// Call the honk() method (from the Vehicle class) on the myCar object

myCar.honk();

// Display the value of the brand attribute (from the Vehicle class)
and the value of the modelName from the Car class

System.out.println(myCar.brand + " " + myCar.modelName);

Did you notice the protected modifier in Vehicle?

We set the brand attribute in Vehicle to a protected access modifier. If it was


set to private, the Car class would not be able to access it.

Why And When To Use "Inheritance"?


- It is useful for code reusability: reuse attributes and methods of an existing
class when you create a new class.
The final Keyword
If you don't want other classes to inherit from a class, use the final keyword:

If you try to access a final class, Java will generate an error:

final class Vehicle {

...

class Car extends Vehicle {

...

The output will be something like this:

Main.java:9: error: cannot inherit from final Vehicle


class Main extends Vehicle {
^
1 error)
Abstract Classes and Methods
Data abstraction is the process of hiding certain details and showing only
essential information to the user.
Abstraction can be achieved with either abstract classes or interfaces (which
you will learn more about in the next chapter).

The abstract keyword is a non-access modifier, used for classes and methods:

 Abstract class: is a restricted class that cannot be used to create objects


(to access it, it must be inherited from another class).

 Abstract method: can only be used in an abstract class, and it does not
have a body. The body is provided by the subclass (inherited from).

An abstract class can have both abstract and regular methods:

abstract class Animal {


public abstract void animalSound();
public void sleep() {
System.out.println("Zzz");
}
}

From the example above, it is not possible to create an object of the Animal
class:

Animal myObj = new Animal(); // will generate an error

Remember from Inheritance that we use the extends keyword to inherit from a
class.
Example
// Abstract class
abstract class Animal {
// Abstract method (does not have a body)
public abstract void animalSound();
// Regular method
public void sleep() {
System.out.println("Zzz");
}
}

// Subclass (inherit from Animal)


class Pig extends Animal {
public void animalSound() {
// The body of animalSound() is provided here
System.out.println("The pig says: wee wee");
}
}

class Main {
public static void main(String[] args) {
Pig myPig = new Pig(); // Create a Pig object
myPig.animalSound();
myPig.sleep();
}
}

Why And When To Use Abstract Classes and Methods?


To achieve security - hide certain details and only show the important details of
an object.

Note: Abstraction can also be achieved with Interfaces, which you will learn
more about next.
Interfaces
Another way to achieve abstraction in Java, is with interfaces.

An interface is a completely "abstract class" that is used to group related


methods with empty bodies:

Example
// interface

interface Animal {

public void animalSound(); // interface method (does not have a body)

public void run(); // interface method (does not have a body)

To access the interface methods, the interface must be "implemented" (kinda


like inherited) by another class with the implements keyword (instead of extends).
The body of the interface method is provided by the "implement" class:

Example
// Interface
interface Animal {
public void animalSound(); // interface method (does not have a body)
public void sleep(); // interface method (does not have a body)
}

// Pig "implements" the Animal interface


class Pig implements Animal {
public void animalSound() {
// The body of animalSound() is provided here
System.out.println("The pig says: wee wee");
}
public void sleep() {
// The body of sleep() is provided here
System.out.println("Zzz");
}
}

class Main {
public static void main(String[] args) {
Pig myPig = new Pig(); // Create a Pig object
myPig.animalSound();
myPig.sleep();
}
}

Notes on Interfaces:

 Like abstract classes, interfaces cannot be used to create objects (in


the example above, it is not possible to create an "Animal" object in the
MyMainClass)
 Interface methods do not have a body - the body is provided by the
"implement" class
 On implementation of an interface, you must override all of its methods
 Interface methods are by default abstract and public
 Interface attributes are by default public, static and final
 An interface cannot contain a constructor (as it cannot be used to create
objects)

Why And When To Use Interfaces?


1) To achieve security - hide certain details and only show the important details
of an object (interface).

2) Java does not support "multiple inheritance" (a class can only inherit from
one superclass). However, it can be achieved with interfaces, because the class
can implement multiple interfaces. Note: To implement multiple interfaces,
separate them with a comma (see example below).
Multiple Interfaces
To implement multiple interfaces, separate them with a comma:

Example
interface FirstInterface {
public void myMethod(); // interface method
}

interface SecondInterface {
public void myOtherMethod(); // interface method
}

class DemoClass implements FirstInterface, SecondInterface {


public void myMethod() {
System.out.println("Some text..");
}
public void myOtherMethod() {
System.out.println("Some other text...");
}
}

class Main {
public static void main(String[] args) {
DemoClass myObj = new DemoClass();
myObj.myMethod();
myObj.myOtherMethod();
}
}
Java Exceptions
When executing Java code, different errors can occur: coding errors made by
the programmer, errors due to wrong input, or other unforeseeable things.

When an error occurs, Java will normally stop and generate an error message.
The technical term for this is: Java will throw an exception (throw an error).

Java try and catch


The try statement allows you to define a block of code to be tested for errors
while it is being executed.

The catch statement allows you to define a block of code to be executed, if an


error occurs in the try block.

The try and catch keywords come in pairs:

Syntax
try {
// Block of code to try
}
catch(Exception e) {
// Block of code to handle errors
}

Consider the following example:

This will generate an error, because myNumbers[10] does not exist.

public class Main {

public static void main(String[ ] args) {

int[] myNumbers = {1, 2, 3};

System.out.println(myNumbers[10]); // error!

}
The output will be something like this:

Exception in thread "main"


java.lang.ArrayIndexOutOfBoundsException: 10
at Main.main(Main.java:4)

If an error occurs, we can use try...catch to catch the error and execute some
code to handle it:

Example
public class Main {

public static void main(String[ ] args) {

try {

int[] myNumbers = {1, 2, 3};

System.out.println(myNumbers[10]);

} catch (Exception e) {

System.out.println("Something went wrong.");

The output will be:

Something went wrong.


Finally
The finally statement lets you execute code, after try...catch, regardless of the
result:

Example
public class Main {

public static void main(String[] args) {

try {

int[] myNumbers = {1, 2, 3};

System.out.println(myNumbers[10]);

} catch (Exception e) {

System.out.println("Something went wrong.");

} finally {

System.out.println("The 'try catch' is finished.");

The output will be:

Something went wrong.


The 'try catch' is finished.

The throw keyword


The throw statement allows you to create a custom error.

The throw statement is used together with an exception type. There are many
exception types available in
Java: ArithmeticException, FileNotFoundException, ArrayIndexOutOfBoundsException , Secu
rityException, etc:

Example
Throw an exception if age is below 18 (print "Access denied"). If age is 18 or
older, print "Access granted":

public class Main {


static void checkAge(int age) {
if (age < 18) {
throw new ArithmeticException("Access denied - You must be at least 18 years
old.");
}
else {
System.out.println("Access granted - You are old enough!");
}
}

public static void main(String[] args) {


checkAge(15); // Set age to 15 (which is below 18...)
}
}

The output will be:


Exception in thread "main" java.lang.ArithmeticException: Access denied - You
must be at least 18 years old.
at Main.checkAge(Main.java:4)
at Main.main(Main.java:12)

If age was 20, you would not get an exception:

Example
checkAge(20);
The output will be:
Access granted - You are old enough!
Java Threads
Threads allows a program to operate more efficiently by doing multiple things at
the same time.

Threads can be used to perform complicated tasks in the background without


interrupting the main program.

Creating a Thread
There are two ways to create a thread.

It can be created by extending the Thread class and overriding its run() method:

Extend Syntax
public class Main extends Thread {

public void run() {

System.out.println("This code is running in a thread");

Another way to create a thread is to implement the Runnable interface:

Implement Syntax
public class Main implements Runnable {

public void run() {

System.out.println("This code is running in a thread");

}
Running Threads
If the class extends the Thread class, the thread can be run by creating an
instance of the class and call its start() method:

Extend Example
public class Main extends Thread {
public static void main(String[] args) {
Main thread = new Main();
thread.start();
System.out.println("This code is outside of the thread");
}
public void run() {
System.out.println("This code is running in a thread");
}
}

If the class implements the Runnable interface, the thread can be run by passing
an instance of the class to a Thread object's constructor and then calling the
thread's start() method:

Implement Example
public class Main implements Runnable {
public static void main(String[] args) {
Main obj = new Main();
Thread thread = new Thread(obj);
thread.start();
System.out.println("This code is outside of the thread");
}
public void run() {
System.out.println("This code is running in a thread");
}
}
Differences between "extending" and "implementing" Threads

The major difference is that when a class extends the Thread class, you cannot
extend any other class, but by implementing the Runnable interface, it is
possible to extend from another class as well, like: class MyClass extends
OtherClass implements Runnable.

Commonly used Constructors of Thread class:


o Thread()
o Thread(String name)
o Thread(Runnable r)
o Thread(Runnable r,String name)

Commonly used methods of Thread class:


1. public void run(): is used to perform action for a thread.
2. public void start(): starts the execution of the thread.JVM calls the run() method on the
thread.
3. public void sleep(long miliseconds): Causes the currently executing thread to sleep
(temporarily cease execution) for the specified number of milliseconds.
4. public String getName(): returns the name of the thread.
5. public void setName(String name): changes the name of the thread.
6. public Thread currentThread(): returns the reference of currently executing thread.
7. public int getId(): returns the id of the thread.
8. public Thread.State getState(): returns the state of the thread.
9. public boolean isAlive(): tests if the thread is alive.
10. public void suspend(): is used to suspend the thread(depricated).
11. public void resume(): is used to resume the suspended thread(depricated).
12. public void stop(): is used to stop the thread(depricated).
Java User Input
The Scanner class is used to get user input, and it is found in
the java.util package.

To use the Scanner class, create an object of the class and use any of the
available methods found in the Scanner class documentation. In our example, we
will use the nextLine() method, which is used to read Strings:

Example
import java.util.Scanner; // Import the Scanner class

class Main {

public static void main(String[] args) {

Scanner myObj = new Scanner(System.in); // Create a Scanner object

System.out.println("Enter username");

String userName = myObj.nextLine(); // Read user input

System.out.println("Username is: " + userName); // Output user input

}
Input Types
In the example above, we used the nextLine() method, which is used to read
Strings. To read other types, look at the table below:

Method Description

nextBoolean() Reads a boolean value from the user

nextByte() Reads a byte value from the user

nextDouble() Reads a double value from the user

nextFloat() Reads a float value from the user

nextInt() Reads a int value from the user

nextLine() Reads a String value from the user

nextLong() Reads a long value from the user

nextShort() Reads a short value from the user

In the example below, we use different methods to read data of various types:
Example
import java.util.Scanner;

class Main {

public static void main(String[] args) {

Scanner myObj = new Scanner(System.in);

System.out.println("Enter name, age and salary:");

// String input

String name = myObj.nextLine();

// Numerical input

int age = myObj.nextInt();

double salary = myObj.nextDouble();

// Output input by user

System.out.println("Name: " + name);

System.out.println("Age: " + age);

System.out.println("Salary: " + salary);

Note: If you enter wrong input (e.g. text in a numerical input), you will get an
exception/error message (like "InputMismatchException").

You might also like