Sign In
Sign In

Installing Java Using apt on Ubuntu

Installing Java Using apt on Ubuntu
Hostman Team
Technical writer
Java
09.07.2024
Reading time: 4 min

Java and the JVM (Java Virtual Machine) are often used in various applications. Therefore, it's important to know how to install free packages and alternative releases from Oracle, including JRE (Java Runtime Environment) and JDK (Java Development Kit). 

This tutorial will guide you through installing Java on Ubuntu using apt and selecting the preferred version.

Prerequisites

You will need a system (your local machine or a cloud server) with Ubuntu 20.04 pre-installed, with root and sudo accounts, and a configured firewall.

Installing JRE/JDK

The simplest way to get started is to use the release included in the standard Ubuntu package. Ubuntu 22.04 comes with OpenJDK 11, which includes open-source JRE and JDK packages.

First, download updates to the package index:

sudo apt update

Before installing Java on Ubuntu, check if it has already been installed:

java -version

If Java is not installed, you will see:

Command 'java' not found, but can be installed with:
apt install openjdk-11-jre-headless  # version 11.0.20.1+1-0ubuntu1~22.04, or
apt install default-jre              # version 2:1.11-72build2
apt install openjdk-17-jre-headless  # version 17.0.8.1+1~us1-0ubuntu1~22.04
apt install openjdk-18-jre-headless  # version 18.0.2+9-2~22.04
apt install openjdk-19-jre-headless  # version 19.0.2+7-0ubuntu3~22.04
apt install openjdk-8-jre-headless   # version 8u382-ga-1~22.04.1

Now, install the OpenJDK package:

sudo apt install default-jre

After installation, verify the JRE presence:

java -version

You should see:

openjdk version "11.0.23" 2024-04-16
OpenJDK Runtime Environment (build 11.0.23+9-post-Ubuntu-1ubuntu122.04.1)
OpenJDK 64-Bit Server VM (build 11.0.23+9-post-Ubuntu-1ubuntu122.04.1, mixed mode, sharing)

To compile and run even specific applications, you will also need the JDK package:

sudo apt install default-jdk

Verify the compiler version:

javac -version

You should see:

javac 11.0.23

Installing Oracle JDK

In some cases, you may need the Oracle JDK package. This installation of Java on Ubuntu will differ from the methods described above, as it requires manual installation. You cannot install it from Ubuntu repositories using apt-get install, but the process is still relatively easy.

The latest version is Java 22. However, Java 21 has the longest support. We will install Java 21 in Ubuntu using wget and a download link copied from the Downloads section on the Oracle website.

Run:

wget https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb

Now install:

sudo apt install ./jdk-21_linux-x64_bin.deb

Check the Java version:

java -version

You should see:

java version "21.0.3" 2024-04-16 LTS
Java(TM) SE Runtime Environment (build 21.0.3+7-LTS-152)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.3+7-LTS-152, mixed mode, sharing)

Managing Java Versions

You can install multiple versions of Java on a single machine. To see the default version, use:

sudo update-alternatives --config java

If you've installed the previously mentioned versions, you will see:

Image1

Enter the desired version number or press Enter to keep the current settings (indicated by an asterisk). This also applies to other modules like the compiler javac, keytool, javadoc, jarsigner, etc.:

sudo update-alternatives --config javac

Setting the JAVA_HOME Variable

Most Java applications use the JAVA_HOME environment variable to determine the installation directory. 

In the previous chapter, we run this command:

sudo update-alternatives --config java

Check the paths in its output.

In our case, the directories are:

  • OpenJDK 11: /usr/lib/jvm/java-11-openjdk-amd64/bin/java

  • OpenJDK 8: /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java

  • Oracle Java 21: /usr/lib/jvm/java-8-oracle/jre/bin/java

Copy the desired path and edit the /etc/environment file with nano:

sudo nano /etc/environment

Add a new line with the copied path:

JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64/bin/"

This sets the JAVA_HOME variable for all user accounts. Save and exit the file, then reload the environment variables:

source /etc/environment

Check if the changes were applied:

echo $JAVA_HOME

You should see:

/usr/lib/jvm/java-11-openjdk-amd64/bin/

This change applies to the current user, while others need to restart the system.

Conclusion

We have covered the installation of Java in Ubuntu, including standard JRE/JDK versions and official Oracle releases. After installing the platform, you can use Java applications and dependencies without restrictions.

Java
09.07.2024
Reading time: 4 min

Similar

Java

Java main() Method

The main method in Java is a fundamental component for any standalone application. It serves as the entry point where the program begins execution. Understanding the syntax and functionality of this method is crucial for every Java programmer. This article delves into the specifics of the main method, explaining its syntax, the role of command-line arguments, and providing practical examples to illustrate its use. Syntax Explanation: public static void main(String[] args) The Java main method is defined as: public static void main(String[] args) Breaking down the syntax: public: This is an access modifier, meaning the method is accessible from anywhere. static: This keyword means the method belongs to the class rather than an instance of the class. It can be invoked without creating an object of the class. void: This signifies that the method does not return any value. main: This is the name of the method. The Java runtime environment looks for this specific method signature to start execution. String[] args: This parameter is an array of String objects, which stores command-line arguments passed to the program. Understanding the String[] args Parameter The String[] args parameter in the main method is crucial for passing information to the program at runtime. Each element in this array is a command-line argument provided when the program is executed. For example, if a program is run with java MyClass arg1 arg2, args[0] would be arg1 and args[1] would be arg2. public class MyClass { public static void main(String[] args) { for (String arg : args) { System.out.println(arg); } } In the above code, each command-line argument passed to the program is printed to the console. How to Use Command-Line Arguments Command-line arguments allow users to pass data into a Java program at the time of execution, making the program more flexible and dynamic. Here's a simple example of how to use command-line arguments in a Java program: public class SumArgs { public static void main(String[] args) { int sum = 0; for (String arg : args) { sum += Integer.parseInt(arg); } System.out.println("Sum: " + sum); } } If this program is run with java SumArgs 10 20 30, it will output Sum: 60. This demonstrates how command-line arguments can be used to input data without modifying the program's source code. Typical Use Cases and Examples Configuration Parameters: Command-line arguments are often used to pass configuration settings to a program. For example, specifying the path to a configuration file or setting a debug mode. public class ConfigLoader { public static void main(String[] args) { if (args.length > 0) { String configFilePath = args[0]; System.out.println("Loading configuration from: " + configFilePath); // Load and process the configuration file } else { System.out.println("No configuration file path provided."); } } } File Processing: Programs that perform operations on files often accept file paths as command-line arguments. public class FilePrinter { public static void main(String[] args) { if (args.length > 0) { String filePath = args[0]; // Code to read and print file content } else { System.out.println("Please provide a file path."); } } } Testing and Debugging: Command-line arguments can be used to quickly test and debug different scenarios by changing the input parameters without altering the source code. Conclusion The public static void main(String[] args) method is an essential part of any Java application. Its syntax and functionality are designed to provide a standardized entry point for program execution. Understanding how to leverage String[] args for command-line arguments can significantly enhance the flexibility and utility of your programs. By mastering the main method, Java developers can build more dynamic and configurable applications, making their code more versatile and easier to manage.
31 July 2024 · 4 min to read
Java

Method Overloading and Overriding in Java

A method in Java is a function that defines what an object of a class can do. One of the main tasks of a method is to perform actions on the object's data. They can change values, transform data, and print to the console. Methods can be overloaded and overridden. In this article, we'll explore how to do this and the differences between these two mechanisms. Method Overloading Method overloading in Java means using one method name with different parameters. To understand this mechanism, let's start with a simple example - creating a helper to greet users. public class Assistant { public void sayHello(String name) { System.out.println("Good day, " + name + "!"); } public static void main(String[] args) { Assistant assistant = new Assistant(); assistant.sayHello("Michael"); } } The console will display the phrase "Good day, Michael!". Suppose Michael didn't come alone but with his friend Victor. The current implementation of the method greets only Michael and ignores Victor. To fix this, we'll implement two methods in the class with the same name but different parameters. public class Assistant { public void sayHello(String firstGuest) { System.out.println("Good evening, " + firstGuest + "!"); } public void sayHello(String firstGuest, String secondGuest) { System.out.println("Good day, " + firstGuest + " and " + secondGuest + "!"); } public static void main(String[] args) { Assistant assistant = new Assistant(); assistant.sayHello("Michael", "Victor"); } } Now the console will display "Good day, Michael and Victor!". We have overloaded sayHello(). The program is now more flexible - the assistant can greet two guests at once. But what happens if three, four, or five guests arrive? Let's check: public class Assistant { public void sayHello(String firstGuest) { System.out.println("Good evening, " + firstGuest + "!"); } public void sayHello(String firstGuest, String secondGuest) { System.out.println("Good day, " + firstGuest + " and " + secondGuest + "!"); } public static void main(String[] args) { Assistant assistant = new Assistant(); assistant.sayHello("Michael", "Victor", "Mary"); } } We will get an error because sayHello() is ready to accept only two arguments. A straightforward solution is to continue overloading it to accept three, four, five, or more guests. However, this isn't flexible programming. We would constantly need to add more code. We would constantly need to add more code. A more flexible solution is to pass a variable-length argument (String... names). This allows sayHello() to accept any number of strings. To print a greeting for each guest to the console, we can use a loop. public class Assistant { public void sayHello(String firstGuest) { System.out.println("Good evening, " + firstGuest + "!"); } public void sayHello(String... names) { for (String name : names) { System.out.println("Good evening, " + name + "!"); } } public static void main(String[] args) { Assistant assistant = new Assistant(); assistant.sayHello("Michael", "Victor", "Mary", "Andrew", "Anna"); } } The console will display a greeting for each guest: Good evening, Michael! Good evening, Victor! Good evening, Mary! Good evening, Andrew! Good evening, Anna! Managed solution for Backend development Argument Order In the example above, we didn't think about the order of arguments because they were all strings. It doesn't matter whether we greet Michael or Anna first. But the order of arguments matters if the method takes, for example, a string and a number. Look at this: public class User { public static void sayYourAge(String greeting, int age) { System.out.println(greeting + " " + age); } public static void main(String[] args) { sayYourAge(20, "My age is - "); //error! } } A compilation error occurs because we defined sayYourAge() to accept a string first and then a number, but we passed the arguments in the opposite order. To fix the error, pass the arguments in the correct order: public class User { public static void sayYourAge(String greeting, int age) { System.out.println(greeting + " " + age); } public static void main(String[] args) { sayYourAge("My age is - ", 20); } } To avoid errors, you can overload by the order of parameters. For example: public class User { public static void sayYourAge(String greeting, int age) { System.out.println(greeting + " " + age); } public static void sayYourAge(int age, String greeting) { System.out.println(greeting + " " + age); } public static void main(String[] args) { sayYourAge("My age is - ", 20); sayYourAge(20, "My age is - "); } } Now it doesn't matter in which order the arguments are passed—the program will understand both options. Variations of Overloading From the examples above, we can identify three variations of overloading Java methods. By the number of parameters: public class Calculator {    void calculate(int number1, int number2) { }    void calculate(int number1, int number2, int number3) { }} By parameter types: public class Calculator {    void calculate(int number1, int number2) { }    void calculate(double number1, double number2) { }} By parameter order: public class Calculator {    void calculate(double number1, int number2) { }    void calculate(int number1, double number2) { }} Finally, let's reiterate what method overloading in Java means. It is a language mechanism that allows the creation of multiple methods with the same name but different parameters. This is not possible in all languages. Overloading is part of polymorphism, one of the key components of object-oriented programming. The main advantage of overloading in Java is the ability to use similar methods with the same names. Method Overriding Method overriding in Java allows you to take a method from a parent class and create a specific implementation in a subclass. To understand this better, let's look at an example. Suppose you create a class Animal with a method voice(). It allows the animal to make a sound: public class Animal { public void voice() { System.out.println("Speak!"); } } The problem immediately arises - all animals make different sounds. You could create a separate method for each. For example, for a cat, it would be voiceCat(), and for a dog, it would be voiceDog(). But imagine how many lines of code you would have to write to allow all animals to make a sound. This is where the overriding mechanism in Java comes in handy. It allows you to replace the implementation in the subclass. Let's look at examples with a cat and a dog: public class Cat extends Animal { @Override public void voice() { System.out.println("Meow!"); } } public class Dog extends Animal { @Override public void voice() { System.out.println("Woof!"); } } The output will first display "Meow" and then "Woof". To achieve this result, you need to: Create a method in the subclass with the same name as in the parent class. Add the @Override annotation before it. This annotation tells the compiler it is not a mistake; you intentionally override the method. Note that the annotation is not mandatory. Creating a method in the subclass with the same signature as the parent class will still override the method. However, it is recommended that you always use the annotation, as it improves code readability and ensures the compiler checks at compile time that such a method exists in the parent class. You write a custom implementation for each subclass. If you don't do this, the parent class's implementation will be used. Even after overriding, you can refer to the parent class's method as long as it is not defined with the private modifier. To do this, use the super keyword: super.method(); Simple and fully managed app deployment Overriding Restrictions Overriding class methods in Java has several restrictions. The method name must be identical to the parent's method (i.e., the method signature must be identical). The arguments must remain the same as the parent's method. The return type must be the same as the parent's method. The access modifier must be the same as the parent's method. Final methods cannot be overridden. One way to prevent overriding is to declare the method using the final keyword. For example: class Parent { final void show() {} } class Child extends Parent { void show() {} // Error! } This code will return an error because the parent class used the final keyword. Static methods cannot be overridden. If you define the same method signature in the subclass as in the parent class, you will perform hiding. More details can be found in the documentation. Private methods cannot be overridden as they are bound at compile time, not runtime. You cannot narrow the access modifier, for example, from public to private. Widening the access level is possible. You cannot change the return type, but you can narrow it if they are compatible. Overriding a method in Java adheres to the rules listed above, which must be followed. Some methods have their own overriding rules, such as equals() and hashCode(). The most important condition is that if you override equals(), you must also override hashCode(). Otherwise, classes and methods that rely on the contracts of these two methods' standard implementation might work incorrectly. We discussed this in detail in another article. Conclusion Method overriding and overloading in Java are important parts of polymorphism but are different mechanisms. When overloading, you create multiple methods within one class with the same name but different parameters. When overriding, you take the same method and make it do different things depending on which class it is called in. However, Java overloading and overriding share some characteristics. Both mechanisms help make code cleaner and more readable and reduce the number of errors during program execution.
08 July 2024 · 8 min to read
Java

Overriding the hashCode Method in Java

Each object in a program can be represented as an integer. This process is called hashing, and its result is a hash. The hashCode() method in Java generates a hash for objects, making them easier to sort and search. The generated hash code helps organize quick searches and access to objects in associative arrays or hash tables. This method can be compared to a surjection, meaning every element of set B is the image of at least one element of set A. The following illustration clearly depicts this. For all elements in set B, there is at least one corresponding element in set A. The only difference between the hashCode() method and surjection is that any object can be processed by this method. When using this method, sometimes different objects may have the same hash. For instance, objects C and V are mapped to the same element in the illustration above. These cases are called collisions. The equals() method, closely related to the hashCode() method, helps resolve these collisions.  Another article on Hostman covers more details about equals(). If you plan to use an associative array in your project and use objects as keys, it is recommended to override the hashCode() method for faster and more accurate operation. It is also necessary to override it whenever the equals() method is overridden. We will describe how to correctly override the hashCode() method in Java further in this article. Requirements for Implementing the hashCode() Method The declaration of the hashCode() method looks like this: public int hashCode() {    // ...} To correctly implement this method in Java, the following requirements are defined: If the hashCode() method is called multiple times on the same object, it must return the same value each time, provided the object's fields are not modified. If the method is called on two equivalent objects, it must return the same result for both. The equality of such objects can be verified using the equals() method, which should return true if the objects are equal. If the results returned by the method for two objects are identical, it does not guarantee their equality. However, if they are different, it guarantees that the objects are not equal. These requirements highlight the importance of using the hashCode() method together with equals() for object comparison. The latter compares objects, while the former indicates if an object's state has changed. This again confirms that when overriding the equals() method, the hashCode() method should also be overridden. Overriding the hashCode() Method The first idea that might come to mind for overriding hashCode() in Java is to return a constant. @Overridepublic int hashCode() {    return 7;} Implementing the override this way is categorically wrong for several reasons: This implementation meets the requirements and will return the same number for two equal objects, but if the state of one changes, its hash code will not change, violating the requirements mentioned above. Such an implementation guarantees collisions. Given these drawbacks, this approach to overriding the method should be avoided. There are two correct ways to override the hashCode() method in Java: Use an existing algorithm for your own implementation. Use auxiliary methods to generate the hash code. The first approach involves following these rules for overriding hashCode() in Java: Exclude all redundant fields that are not involved in equals(). Choose a base - a starting number for calculating the object's hash code and assign it to a variable total. Developers often use the number 31, but you can choose a different value. Many IDEs generate hash codes using this number. Next, calculate the hash for each remaining field. The table below shows the rules for calculating possible field types: Field Type Rule boolean (f ? 1 : 0) char, short, byte, or int (int) f float Float.floatToIntBits(f) double Double.doubleToLongBits(f) then (int)(f ^ (f >>> 32)) long (int)(f ^ (f >>> 32)) Reference to another object Recursive call to hashCode() Array Process each element of the array as a separate field of the object null return 0 The next rule is to add the calculated hash of each field (let's say it's the variable compute) to the variable total: total = 31 * total + compute; Finally, return the final value of the variable total after all calculations. Below is an example of implementing the override for the Staff class, using the rules listed above: public class Staff { private String FCs; private String city; private int experience; private double wage; private String department; public Staff(String FCs, String city, int experience, double wage, String department) { this.FCs = FCs; this.city = city; this.experience = experience; this.wage = wage; this.department = department; } @Override public int hashCode() { int total = 31; total = total * 31 + (FCs == null ? 0 : FCs.hashCode()); total = total * 31 + (city == null ? 0 : city.hashCode()); total = total * 31 + experience; long lwage = Double.doubleToLongBits(wage); total = total * 31 + (int)(lwage ^ (lwage >>> 32)); total = total * 31 + (department == null ? 0 : department.hashCode()); return total; } // Overriding equals() // ... } The @Override annotation before the method declaration checks that the overridden method exists in the parent class. All string fields (FCs, city, and department) are checked for null before calling their hashCode() method to ensure safety from NullPointerException. The second approach involves using auxiliary methods to generate the hash code, available through the java.util.Objects class starting from Java 8+. An example of this implementation is shown below: @Overridepublic int hashCode() {    return Objects.hash(FCs, city, experience, department);} All standard reference data types in Java (String, Integer, Double, etc.) already have correctly overridden equals() and hashCode() methods. Therefore, they can be safely integrated into collections like HashMap, HashSet, and others. Managed solution for Backend development Key Points to Remember A hash is a number generated for an object using hash functions, including hashCode(). The Java hashCode() method returns an integer hash code value for a selected object. If you plan to use an associative array in your project and use objects as keys, it is recommended to override the hashCode() method. A well-chosen implementation of the hashCode() method will speed up the performance of associative arrays. When overriding equals(), do not forget to override hashCode(), and vice versa. An incorrect implementation of the Java hashCode() method will result in many collisions.
08 July 2024 · 6 min to read

Do you have questions,
comments, or concerns?

Our professionals are available to assist you at any moment,
whether you need help or are just unsure of where to start.
Email us
Hostman's Support