Syntax and Basic Types
Primitive types, wrapper classes, variables, scope, lifecycle, basic keywords and type casting
The building blocks of Java: primitive types, wrapper classes, variables, scope, lifecycle, and type casting.
1. Definition
What is it?
Java is a strongly typed language, meaning every variable must have its type declared (or inferred by the compiler via var). The language fundamentals are built on primitive types and their corresponding wrapper classes.
Why does it exist?
- Type safety: the compiler catches type errors at compile-time
- Performance: primitive types are stored directly on the stack, no heap allocation needed
- Platform independence: type sizes are fixed across all platforms (e.g.,
intis always 32 bits)
Where does it fit?
This is the most fundamental layer of Java â everything else (OOP, Collections, Streams) builds on top of it.
2. Core Concepts
2.1 Primitive Types
Java has exactly 8 primitive types:
| Type | Size | Default | Range | Usage |
|---|---|---|---|---|
byte |
8 bit | 0 |
-128 to 127 | Memory-efficient arrays, I/O |
short |
16 bit | 0 |
-32,768 to 32,767 | Rarely used |
int |
32 bit | 0 |
-2ÂłÂč to 2ÂłÂč-1 | Default integer type |
long |
64 bit | 0L |
-2â¶Âł to 2â¶Âł-1 | Large numbers, timestamps |
float |
32 bit | 0.0f |
~±3.4Ă10Âłâž | Rarely used, prefer double |
double |
64 bit | 0.0d |
~±1.7Ă10Âłâ°âž | Default decimal type |
char |
16 bit | '\u0000' |
0 to 65,535 | Unicode character |
boolean |
~1 bit* | false |
true/false |
Logical value |
*The
booleansize is implementation-dependent: the JVM typically stores it as anint(32 bits) on the stack.
Key rules:
- Primitives cannot be
null - Primitives cannot be used in generics (
List<int>â,List<Integer>â ) - Integer literals default to
int, decimal literals default todouble
2.2 Wrapper Classes
Every primitive has a corresponding wrapper class:
| Primitive | Wrapper | Cache Range |
|---|---|---|
byte |
Byte |
-128 to 127 (full range) |
short |
Short |
-128 to 127 |
int |
Integer |
-128 to 127 |
long |
Long |
-128 to 127 |
float |
Float |
no cache |
double |
Double |
no cache |
char |
Character |
0 to 127 |
boolean |
Boolean |
TRUE / FALSE (both) |
Autoboxing/Unboxing:
Integer x = 42; // autoboxing: int â Integer
int y = x; // unboxing: Integer â int
Integer z = null;
int w = z; // â ïž NullPointerException!
Integer Cache (-128 to 127):
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true (from cache)
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false (new objects!)
System.out.println(c.equals(d)); // true (value comparison)
2.3 Variables, Scope, Lifecycle
Variable types:
| Type | Declared | Default | Lifetime |
|---|---|---|---|
| Local variable | Inside method | â None (must initialize) | End of block |
| Instance variable | In class (non-static) | â Yes | Until object is GC'd |
| Class variable | static |
â Yes | Program termination |
| Parameter | Method signature | Provided by caller | End of method |
Scope rules:
public void example() {
int x = 10; // scope: entire method
if (x > 5) {
int y = 20; // scope: this if block only
System.out.println(x + y); // OK
}
// System.out.println(y); // â Compile error! y not visible here
}
2.4 Type Casting
Implicit (widening) â automatic, lossless:
byte â short â int â long â float â double
char â int
Explicit (narrowing) â manual, may lose data:
double d = 9.78;
int i = (int) d; // i = 9 (truncation, NOT rounding!)
int big = 130;
byte b = (byte) big; // b = -126 (overflow!)
Special rules:
- Conversion between
floatanddoublemay lose precision intâfloatconversion can also lose precision (float has only 24-bit mantissa)
2.5 Literals, Defaults, and Compiler Inference
Literal syntax is part of everyday Java work, and it is also a common source of subtle bugs.
- Integer literals like
42areintby default - Long literals require the
Lsuffix:42L - Floating-point literals like
3.14aredoubleby default - Float literals require the
fsuffix:3.14f - Underscores improve readability:
1_000_000 - Binary, octal, and hexadecimal literals are supported:
0b1010,077,0xFF
The compiler also performs constant folding and range checks for compile-time constants. byte b = 10; compiles because the value fits into the target type, while byte b = 1000; does not.
Default values also matter during initialization:
- instance fields get default values automatically
- local variables never do
- reference types default to
null - numeric primitives default to
0/0.0 booleandefaults tofalse
2.6 Basic Java Keywords â a Quick Map
In early Java learning, many keywords keep appearing before their deeper meaning is obvious. This section gives you a mental map: not every detail is fully covered here, but you can already understand what role each keyword usually plays.
publicâ visible from anywhere; typically used for the outward API.privateâ visible only inside the declaring class; used to protect internal state.protectedâ visible in the package and to subclasses; common in inheritance APIs.classâ declares a class; defines an object type.newâ instantiates a new object; triggers heap allocation.thisâ reference to the current object; useful for field access and constructor chaining.staticâ class-level member, not tied to an instance; common for utilities and constants.finalâ single assignment or no overriding/inheritance; common for constants and invariants.abstractâ incomplete implementation to be completed by a subtype.extendsâ a class inherits from another class.implementsâ a class fulfills an interface contract.interfaceâ behavioral contract used for abstraction and polymorphism.
Useful beginner rules:
- Fields are most often
private, while the outward-facing API is oftenpublic. newcreates the object, while the constructor initializes it.- Do not mix static and instance thinking: in a static context there is no
this. finaldoes not always mean deep immutability; it may freeze the reference while the referenced object stays mutable.
public class User {
private final String name;
public User(String name) {
this.name = name;
}
public static User guest() {
return new User("guest");
}
}
This short example already shows public, private, final, this, static, new, and class together. The later OOP chapters go deeper, but their basic role is already visible here.
3. Practical Usage
When to use what?
| Situation | Recommendation | Why |
|---|---|---|
| General integers | int |
Default, most common |
| Financial calculations | BigDecimal â ïž |
NOT double! Precision errors! |
| Numbers in collections | Integer (wrapper) |
Generics requirement |
| Large dataset, memory matters | primitive arrays | Wrapper has 16+ bytes/element overhead |
| Boolean flag | boolean primitive |
Simple, no null concerns |
| Nullable boolean (from DB) | Boolean wrapper |
null = "we don't know" |
When NOT to use?
- â
doublefor financial calculations â0.1 + 0.2 != 0.3 - â
==for wrapper comparison â breaks outside cache range - â
floatwhen not necessary âdoubleis the default,floatis rare
Practical Decision Checklist
When choosing a type in production code, ask these questions in order:
- Can the value be absent? If yes, a wrapper or dedicated domain type may be appropriate.
- Is arithmetic precision business-critical? If yes, prefer
BigDecimalfor decimal business values. - Will this run in a tight loop or large dataset? If yes, avoid unnecessary boxing.
- Will this value cross an API boundary? Prefer explicit, intention-revealing types over âconvenientâ ones.
Examples:
int retryCountis usually better thanInteger retryCountLong orderIdmay be justified if persistence can represent an âunsavedâ state asnullBigDecimal amountcommunicates domain precision better thandouble amountboolean enabledis clearer thanBoolean enabledunless three-state logic is truly required
4. Code Examples
Basic Example â Types and Conversion
public class TypeBasics {
public static void main(String[] args) {
// Primitives
int count = 42;
double price = 19.99;
boolean active = true;
char grade = 'A';
// Widening (automatic)
long bigCount = count; // int â long, OK
double bigPrice = count; // int â double, OK
// Narrowing (explicit cast required)
int rounded = (int) price; // 19 (truncation!)
byte small = (byte) count; // 42 (fits)
// Wrapper autoboxing
Integer boxed = count; // autoboxing
int unboxed = boxed; // unboxing
// var (Java 10+) â type inferred by compiler
var name = "Alice"; // String
var number = 42; // int (NOT Integer!)
var list = List.of(1, 2, 3); // List<Integer>
}
}
Advanced Example â Financial Calculation Pitfall
public class MoneyPitfall {
public static void main(String[] args) {
// â WRONG: double precision error
double price = 0.1;
double quantity = 3;
System.out.println(price * quantity);
// Output: 0.30000000000000004 (!)
// â
CORRECT: use BigDecimal
BigDecimal bdPrice = new BigDecimal("0.1");
BigDecimal bdQuantity = new BigDecimal("3");
System.out.println(bdPrice.multiply(bdQuantity));
// Output: 0.3
}
}
Common Pitfall â Integer Cache
public class IntegerCacheTrap {
public static void main(String[] args) {
// Within cache (-128..127): WORKS
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true â
// Outside cache: DOES NOT WORK
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false â
System.out.println(c.equals(d)); // true â
// â ïž Null unboxing
Integer nullable = null;
// int value = nullable; // NullPointerException!
}
}
5. Trade-offs
| Aspect | Primitive | Wrapper |
|---|---|---|
| ⥠Performance | Fast (stack, no allocation) | Slower (heap allocation, GC) |
| đŸ Memory | Minimal (e.g., int = 4 bytes) |
Significant (e.g., Integer = ~16 bytes) |
| đ§ Maintainability | Simple but can't be null | Requires null handling but more expressive |
| đ Flexibility | Can't be used in generics | Works with collections and generics |
Concrete numbers:
int[1000]â ~4 KB memoryInteger[1000]â ~20 KB memory (5x more!)- One million
Integerautoboxing operations â significant GC pressure
6. Common Mistakes
1. Using `==` with wrappers
// â Wrong
if (value1 == value2) { ... } // reference comparison!
// â
Correct
if (value1.equals(value2)) { ... }
// or
if (Objects.equals(value1, value2)) { ... } // null-safe
2. Using `double` for financial calculations
// â Never do this
double balance = 1000.00;
balance -= 999.99;
// balance = 0.010000000000047748 (NOT 0.01!)
// â
Use BigDecimal
BigDecimal balance = new BigDecimal("1000.00");
balance = balance.subtract(new BigDecimal("999.99"));
// balance = 0.01 â
3. Null unboxing
// â NullPointerException thrown
Integer count = getCountFromDb(); // may return null
int total = count + 1; // NPE!
// â
Null check
int total = (count != null) ? count + 1 : 1;
// or use Optional
int total = Optional.ofNullable(count).orElse(0) + 1;
4. Narrowing cast overflow
// â Silently produces wrong result
int big = 128;
byte b = (byte) big; // b = -128 (overflow, no exception!)
// â
Check before casting
if (big >= Byte.MIN_VALUE && big <= Byte.MAX_VALUE) {
byte b = (byte) big;
} else {
throw new ArithmeticException("Value out of byte range");
}
// or Java 8+:
byte b = Math.toIntExact(big); // ArithmeticException on overflow
7. Deep Dive
When does primitive vs wrapper choice matter?
- Hot path (tight loops, real-time processing): always use primitives. Autoboxing in a million-iteration loop causes measurable slowdown.
- Domain models: use wrapper if the value is nullable (e.g.,
Integer ageâ "we don't know the age" vsint ageâ "age is 0"). - API design:
Optional<Integer>is better than a nullableInteger.
Integer Cache Customization
The JVM's -XX:AutoBoxCacheMax=<size> flag can extend the Integer cache range. In high-frequency systems (e.g., IDs ranging 1â10,000), this can improve performance.
`var` usage â when yes, when no?
// â
Good: right-hand side makes type obvious
var users = new ArrayList<User>();
var response = httpClient.send(request, BodyHandlers.ofString());
// â Bad: type is not obvious
var result = process(); // what type is this?
var x = flag ? getA() : getB(); // which type?
JIT Optimization
The JIT compiler can often eliminate autoboxing via escape analysis when the wrapper object doesn't "escape" the method. But don't rely on this â always measure in production code!
Primitive Streams vs Boxing Overhead
This topic becomes relevant again in the Stream API chapter. Stream<Integer> looks convenient, but repeated boxing and unboxing can become expensive in aggregation-heavy code.
int sum = IntStream.range(0, 1_000_000)
.filter(value -> value % 2 == 0)
.sum();
If the data is truly numeric, IntStream, LongStream, and DoubleStream are often better fits than object streams.
API Boundaries and Null Semantics
Choosing between int and Integer is not only about performance. It also defines business meaning.
int quantitymeans the value must always existInteger quantitymeans absence is representableOptionalIntcan express optionality without boxing in internal APIs
That semantic signal often matters more than the raw memory cost.
Floating Point Comparison
// â Never use == with double
double a = 0.1 + 0.2;
if (a == 0.3) { ... } // false!
// â
Epsilon-based comparison
private static final double EPSILON = 1e-10;
if (Math.abs(a - 0.3) < EPSILON) { ... } // true
8. Interview Questions
Why can `Integer` cause a `NullPointerException` even if you never call a method on it?
Because unboxing is an implicit method call inserted by the compiler. If Integer value = null; and later int x = value;, Java effectively performs value.intValue(), which throws NullPointerException.
When would you prefer a wrapper over a primitive in a domain model?
Use a wrapper when missingness is meaningful. For example, Integer age can represent âunknownâ, while int age cannot. The choice should be driven by domain semantics first, not habit.
Why does `short s = 1; s += 1;` compile, but `s = s + 1;` does not?
+= performs an implicit cast after evaluation. s + 1 is promoted to int, so plain assignment back to short would be narrowing and requires an explicit cast.
Why is `BigDecimal` recommended for money instead of `double`?
Because double stores values in binary floating-point format, so many decimal fractions cannot be represented exactly. BigDecimal stores decimal values with controlled precision and rounding, which is essential for business calculations.
9. Glossary
| Term | Definition |
|---|---|
| Autoboxing | Automatic conversion of a primitive type to its wrapper class (int â Integer) |
| Unboxing | Automatic conversion of a wrapper class to its primitive (Integer â int) |
| Widening | Implicit type conversion from a smaller type to a larger one (lossless) |
| Narrowing | Explicit type conversion from a larger type to a smaller one (may lose data) |
| Integer Cache | The JVM caches Integer objects for values -128 to 127 via Integer.valueOf() |
| Stack | Memory area where local primitive variables and references are stored |
public |
Access level visible from anywhere |
private |
Access level visible only inside the declaring class |
class |
Java keyword used to declare a class |
new |
Keyword that initiates creation of a new object |
| String Pool | â See: String Handling |
| Escape Analysis | JIT technique: determines if an object "escapes" its method scope |
10. Cheatsheet
- đą 8 primitive types:
byte,short,int,long,float,double,char,boolean - đŠ Wrapper = primitive + null + generics compatibility (but more expensive)
- â ïž
==on wrappers compares references, use.equals()! - đ° For money, ALWAYS use
BigDecimal, neverdouble - đŻ Integer cache: -128 to 127 (extendable via JVM flag)
- đ Widening is automatic, narrowing requires explicit cast
- đ« Null unboxing = NPE â always check for null!
- đ
var(Java 10+) â only when type is obvious from context - đ
public/private/protectedâ define where a member is visible from - đïž
class+newâ one declares a type, the other creates an instance - ⥠Hot path = primitive, domain model = wrapper if nullable needed
- đ§ź
floatâ 7 decimal digits,doubleâ 15 decimal digits precision
đź Games
13 questions