2.0.3 - Variables and Data Types

Learning Objectives

  • Understand the concept of variables and their role in Java programming
  • Learn about primitive data types and their characteristics
  • Master variable declaration and initialization
  • Gain proficiency in using different types of literals in Java
  • Recognize common pitfalls and best practices when working with variables and data types

Introduction

Programs need to remember things while they run—numbers, text, true/false values. Variables are named containers that hold these values in memory. Instead of telling the computer "store this number at memory address 0x7fff5fbff8ac," you can write studentAge and the computer handles the details. Variables make programming possible without having to think about raw memory addresses.

Java requires you to specify what type of data each variable will hold. This might seem like extra work compared to languages like Python, but it catches mistakes early. If you accidentally try to store text in a variable meant for numbers, Java stops you before the code even runs. This upfront declaration also helps Java run faster because it knows exactly how to handle each piece of data.

Java has eight basic data types called primitives. These are the building blocks for everything else in Java. Understanding these primitives—what they can store, how much space they take, and how to use them correctly—is fundamental to writing Java programs that work as expected.

The Eight Primitive Types

Java gives you exactly eight primitive types. Each one is designed for a specific kind of data:

Data Type Size Description Range
byte 8 bits Very small integer values -128 to 127
short 16 bits Small integer values -32,768 to 32,767
int 32 bits Standard integer values -2,147,483,648 to 2,147,483,647
long 64 bits Large integer values -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
float 32 bits Decimal numbers with single precision ±3.4E-38 to ±3.4E+38 with 6-7 significant digits
double 64 bits Decimal numbers with double precision ±1.7E-308 to ±1.7E+308 with 15 significant digits
char 16 bits Single Unicode character 0 to 65,535 (representing Unicode characters)
boolean 1 bit True/false values true or false

Think of these types as different-sized boxes. A byte is a tiny box that can only hold small numbers from -128 to 127. An int is a bigger box that holds most normal-sized numbers you'd use in everyday programs. A long is even bigger for really large numbers like the national debt.

Why have different sizes? Memory efficiency. If you're storing ages for a million students, using byte (which holds numbers up to 127) instead of int saves a lot of space. But if you need to store someone's bank balance in cents, you'd need long because it might exceed what int can hold.

The four integer types (byte, short, int, long) only store whole numbers. They use something called two's complement representation, which basically means one bit tells you if the number is positive or negative, and the rest give you the actual value. That's why a byte goes from -128 to 127 instead of 0 to 255.

The two decimal types (float and double) store numbers with fractional parts like 3.14 or 98.6. A float has about 6-7 digits of precision, while a double has about 15 digits. For most programming, you'll use double because it's more accurate. One warning: these types can't represent every decimal number exactly. The number 0.1 stored as a double is actually a tiny bit off from exactly 0.1. If you're writing financial software where every penny matters, you'll need to use BigDecimal instead of double.

The char type holds a single character like 'A' or '7' or '!'. It uses Unicode, which means it can represent characters from almost any language in the world.

The boolean type is the simplest—it's either true or false. That's it. You use booleans for things like "Is the student enrolled? Yes or no."

Choosing the Right Type

package academy.javapro.module2;

public class StudentRecordWithDataTypeDemo {
    public static void main(String[] args) {
        // Using different primitive types
        byte semester = 3;                      // Current semester (1-8 typically)
        short yearLevel = 2;                    // Student's year level (1st year, 2nd year, etc.)       
        int id = 1001;                          // Student ID
        long enrollmentNumber = 202412345678L;  // Year + sequence number
        float height = 5.9F;                    // Height in feet
        double weight = 68.5;                   // Weight in kg
        char grade = 'A';                       // Academic grade
        boolean isActive = true;                // Enrollment status

        // Display student information
        System.out.println("Student Details:");
        System.out.println("ID: " + id);
        System.out.println("Semester: " + semester);
        System.out.println("Year Level: " + yearLevel);
        System.out.println("Enrollment Number: " + enrollmentNumber);
        System.out.println("Height (ft): " + height);
        System.out.println("Weight (kg): " + weight);
        System.out.println("Grade: " + grade);
        System.out.println("Active Status: " + isActive);

        System.out.println(); // Empty line for spacing

        // Simple overflow demonstration
        byte smallNumber = 127;  // This is the biggest number a byte can hold
        System.out.println("Biggest byte number: " + smallNumber);

        // What happens when we try to make it bigger?
        smallNumber = (byte) (smallNumber + 1);
        System.out.println("After adding 1: " + smallNumber);
        System.out.println("It wrapped around to the smallest number!");
    }
}

This program shows how to pick types based on what you're storing. Semester numbers (1 through 8) fit perfectly in a byte. Student IDs use int because schools might have thousands of students but won't exceed 2 billion. Enrollment numbers that combine the year with a long sequence could be huge, so we use long.

See that L at the end of 202412345678L? That's required. Without it, Java assumes you're writing an int, and that number is too big for an int. The L tells Java "this is a long number." You can use lowercase l, but it looks like the number 1, so everyone uses uppercase L.

The F in 5.9F works the same way. Java assumes all decimal numbers are double by default. If you want a float, you need to add the F. Otherwise you'll get an error saying you're trying to put a double into a float variable.

Notice char grade = 'A'; uses single quotes. Double quotes make it a String, which is completely different from a char. Single quotes = one character. Double quotes = text that can be many characters long.

Declaring Variables

Every variable declaration follows this basic pattern:

type variableName = value;

The type comes first (int, double, etc.). Then the variable name you choose. Then an equals sign. Then the actual value. End with a semicolon.

You can declare a variable without giving it a value right away:

int age;

But Java won't let you use that variable until you assign something to it. Try to print it before assigning a value, and the code won't even compile. This is Java protecting you from using garbage data.

You can also declare multiple variables of the same type in one line:

int x = 5, y = 10, z = 15;

This is legal but makes code harder to read. Most programmers put each variable on its own line. The only exception is sometimes in for loops where you need to declare multiple loop variables.

Java 10 added a shortcut called var where the compiler figures out the type for you:

var id = 1001;        // Java knows this is an int
var height = 5.9F;    // Java knows this is a float
var name = "Alice";   // Java knows this is a String

This only works when you're assigning a value immediately—Java needs to see what you're putting in the variable to figure out its type. Many beginners like var because it's less typing, but being explicit about types often makes code clearer, especially when you're still learning.

Understanding Literals

A literal is a value you write directly in your code: 42, 3.14, 'X', true. Java has rules about what type each literal becomes.

Whole numbers like 42 are automatically int. If you need a long, add L at the end: 42L.

Decimal numbers like 3.14 are automatically double. If you need a float, add F at the end: 3.14F.

Characters use single quotes: 'A', '7', '!'. You can also use special codes for characters. '\n' means newline, '\t' means tab. Unicode codes work too: '\u00A9' is the © symbol.

Booleans are just true or false (lowercase). Unlike some languages where 0 means false and 1 means true, Java is strict. You can't write boolean flag = 1; and expect it to work. It has to be true or false.

If you try to assign a literal that's too big for the type, Java catches it immediately:

byte b = 200;  // ERROR! 200 is too big for a byte

The maximum value a byte can hold is 127, so this won't compile. You'd need to use short or int for 200.

When Numbers Go Wrong: Overflow

The demonstration at the end of our example shows something tricky. We store 127 in a byte (the maximum a byte can hold), then add 1. What do you think happens? Does Java give you an error?

Nope. The number wraps around to -128.

This is called overflow, and it happens when you exceed the limits of a type. In binary representation, 127 is 01111111. Add 1 and you get 10000000, which the computer interprets as -128. It's like an odometer rolling over from 99999 to 00000.

The scary part? Java doesn't warn you. No error message, no exception thrown. Your program just keeps running with the wrong number. This can cause serious bugs that are hard to track down.

Notice we had to write (byte) (smallNumber + 1). When you do math with bytes, Java automatically converts them to ints for the calculation. Then you need to explicitly convert (cast) back to byte. Without that cast, the code won't compile because you're trying to put an int result into a byte variable.

The compound assignment operator += handles this automatically:

smallNumber += 1;  // This works because += includes the cast

Overflow is a real problem in professional programs. If you're calculating array sizes or processing financial data and overflow happens, you'll get completely wrong results. Java 8 added special methods like Math.addExact() that throw an exception when overflow occurs instead of silently giving you the wrong answer. Use these when accuracy is critical.

Decimal types (float and double) handle overflow differently. If you exceed their range, you get special values called Infinity or -Infinity. Divide by zero with decimals and you get NaN ("Not a Number") instead of an error. NaN is weird—any calculation involving NaN produces NaN, and even NaN == NaN is false. To check for NaN, you need to use Double.isNaN().

Summary

Variables are named containers for data in memory, and every variable in Java needs a declared type. Java provides eight primitive types for different kinds of data: four integer types of increasing size (byte, short, int, long), two decimal types (float, double), one character type (char), and one true/false type (boolean). Choosing the right type means balancing memory usage against the range of values you need to store.

Declaring a variable means stating its type, giving it a name, and optionally assigning an initial value. Java requires types to be declared upfront, which lets the compiler catch mistakes before your program runs. You can use the var keyword to let Java infer the type from the value you're assigning, but this only works when you provide an initial value.

Literals are the actual values you write in code—numbers, characters, true/false. Java has defaults: whole numbers are ints, decimals are doubles, but you can override these with suffixes like L for long or F for float. Character literals use single quotes, and booleans must be explicitly true or false.

Overflow happens when arithmetic produces a value outside a type's range. Integer types wrap around silently from the maximum to the minimum with no warning, which can cause subtle bugs. Floating-point types produce special values like Infinity or NaN instead. Production code that handles large numbers needs overflow protection—either by using larger types or by using Java's Math.xxxExact() methods that throw exceptions on overflow.

Type conversion follows clear rules: smaller types can be assigned to larger types automatically (widening), but going from larger to smaller requires an explicit cast (narrowing) because data might be lost. When you mix types in calculations, Java promotes everything to the largest type present to prevent precision loss. Understanding these rules helps you write code that handles data correctly without unexpected truncation or type mismatches.