FirstHack Learn
Log in Sign up free

Variables and Types

9 min read · 37 views

Variables and Types

Java is statically typed: every variable has a type fixed when you declare it, checked by the compiler, and unchangeable afterwards. This is the same discipline as C and C++, and the opposite of Python.

Java also splits its types into two groups that behave differently in ways that will confuse you until you see the split clearly. That split is the real subject of this tutorial.

The eight primitives

Primitives hold a value directly. They are not objects, they have no methods, and their sizes are fixed by the language rather than by the machine, which is part of how Java achieves portability.

Type Size Range or purpose Example
byte 1 byte -128 to 127 byte b = 100;
short 2 bytes About ±32 thousand short s = 1000;
int 4 bytes About ±2.1 billion int age = 19;
long 8 bytes About ±9.2 quintillion long pop = 1400000000L;
float 4 bytes Decimals, ~7 digits float g = 9.8f;
double 8 bytes Decimals, ~15 digits double pi = 3.14159;
char 2 bytes One Unicode character char grade = 'A';
boolean 1 bit logically true or false boolean ok = true;

Two details matter immediately. A long literal needs an L suffix and a float literal needs an f, because a plain whole number is an int and a plain decimal is a double. Without the suffix, long big = 10000000000; fails to compile: the literal overflows int before it is ever assigned.

Java's char is two bytes because it stores a Unicode code unit, not an ASCII byte. And boolean is a genuine separate type. Unlike C, you cannot write if (5); the condition must actually be a boolean. That single rule removes the if (x = 5) bug entirely.

Java
public class Main {
    public static void main(String[] args) {
        int rollNumber = 42;
        double cgpa = 8.65;
        char section = 'B';
        boolean hostelResident = false;
        long cityPopulation = 12400000L;
        float weight = 62.5f;

        System.out.println("Roll: " + rollNumber);
        System.out.println("CGPA: " + cgpa);
        System.out.println("Section: " + section);
        System.out.println("Hostel: " + hostelResident);
        System.out.println("Population: " + cityPopulation);
        System.out.println("Weight: " + weight);

        System.out.println("int max: " + Integer.MAX_VALUE);
        System.out.println("int min: " + Integer.MIN_VALUE);
        System.out.println("Overflow: " + (Integer.MAX_VALUE + 1));
    }
}

That last line is worth staring at. Adding one to the largest int does not raise an error. It wraps around to the most negative int. Java does not check arithmetic overflow, so choose long when values might get large.

Objects and references

Everything that is not one of those eight is an object. Objects have methods, are created with new, and live on the heap. A variable of an object type does not hold the object; it holds a reference to it, which is an arrow pointing at where the object lives.

This changes what assignment means. int b = a; copies the value, so changing b leaves a alone. For objects, Student b = a; copies the arrow, so both names point at the same object and a change through either is visible through the other.

A reference can also hold null, meaning "pointing at nothing". Calling a method on a null reference throws NullPointerException, the most common runtime error in Java.

Each primitive has a matching wrapper class: int has Integer, double has Double, boolean has Boolean. You need them where an object is required, such as inside an ArrayList, which cannot hold primitives. Java converts between them automatically, a feature called autoboxing.

Strings

String is a class, not a primitive, but it is special enough to get its own quoting syntax and its own + operator.

The critical property is that a String is immutable. Once created, its characters can never change. Every method that appears to modify a string actually builds and returns a new one.

Java
public class Main {
    public static void main(String[] args) {
        String course = "Computer Science";

        System.out.println("Original:  " + course);
        System.out.println("Upper:     " + course.toUpperCase());
        System.out.println("After:     " + course);

        String changed = course.toUpperCase();
        System.out.println("Stored:    " + changed);

        System.out.println("Length:    " + course.length());
        System.out.println("Char at 0: " + course.charAt(0));
        System.out.println("Substring: " + course.substring(0, 8));
        System.out.println("Index of 'Science': " + course.indexOf("Science"));

        String a = "hello";
        String b = "hel" + "lo";
        System.out.println("a.equals(b): " + a.equals(b));
        System.out.println("Ignoring case: " + a.equalsIgnoreCase("HELLO"));
    }
}

course is unchanged after toUpperCase(), because the method returned a new string that nobody stored. This catches every beginner once.

⚠️Compare strings with equals, never with ==

For objects, == compares references: it asks whether two names point at the same object, not whether the contents match. Two separately built strings with identical characters can be different objects, so == gives false. Use s1.equals(s2). Small literals often compare true by accident because Java reuses them from an internal pool, which makes this bug worse: it works in your test and fails on real input.

Casting

Widening a smaller type into a larger one is automatic and safe, because the value always fits. Assigning an int to a double needs nothing from you.

Narrowing is the other direction and can lose data, so Java refuses to do it silently. You must write an explicit cast to say you accept the loss.

Java
public class Main {
    public static void main(String[] args) {
        int marks = 78;
        double asDouble = marks;
        System.out.println("Widened: " + asDouble);

        double precise = 9.87;
        int truncated = (int) precise;
        System.out.println("Narrowed: " + truncated);

        int big = 300;
        byte small = (byte) big;
        System.out.println("300 as a byte: " + small);

        int total = 250, count = 4;
        System.out.println("Integer division: " + (total / count));
        System.out.println("Real division:    " + ((double) total / count));

        String numberText = "42";
        int parsed = Integer.parseInt(numberText);
        System.out.println("Parsed + 8 = " + (parsed + 8));

        double d = Double.parseDouble("3.75");
        System.out.println("Parsed double: " + d);
    }
}

(int) 9.87 gives 9, not 10. A cast truncates towards zero; it does not round. Use Math.round() when you want rounding. And casting 300 into a byte produces a meaningless number, because the value does not fit and the extra bits are simply discarded.

Note that a cast does not convert text. (int) "42" will not compile. Turning a string into a number is a parse, done with Integer.parseInt.

final

final on a variable means it can be assigned exactly once. Writing final double PI = 3.14159; makes any later assignment a compile error.

Use it for genuine constants and for values that must not drift once set. The convention is capitals with underscores, as in MAX_STUDENTS. On an object reference, final fixes the reference, not the object: you cannot repoint it, but you can still modify what it points at.

Common mistakes

  • Comparing strings with ==. Use .equals(). This works by luck with literals and fails with computed strings.
  • Forgetting L or f suffixes. long x = 10000000000; does not compile because the literal is an int first.
  • Expecting a string method to modify the string. Strings are immutable. Assign the result: s = s.trim();.
  • Integer division where a decimal was wanted. total / count with two ints truncates. Cast one: (double) total / count.
  • Calling a method on null. String s = null; s.length(); throws NullPointerException. Check for null first.
  • Using an uninitialised local variable. Java refuses to compile it, which is a feature. Give it a starting value.
  • Assuming a cast rounds. (int) 9.87 is 9. Use Math.round(9.87) for 10.

Once the primitive-versus-object split is clear, most of Java's surprises stop being surprising. Input and formatted output come next, and there are typing exercises waiting on the Practice page.

Create a free account to track what you have finished.