Java Class Attributes and Methods
An item's quantity belongs to that item. A shared limit belongs to the class. Learn how fields and methods express those different responsibilities, then build stock adjustments that cannot silently oversell or overfill an item.
Before you start: review classes, objects, and receivers, returned values, fields and locals, and numeric conversion. Every example is a complete editable Java 21 program. Use Run code; no input or external service is required.
What you will be able to do
- Identify fields, parameters, and local variables in a class.
- Keep per-item state in instance fields and call behavior through the correct receiver.
- Distinguish a shared static field from an instance field.
- Use a static helper and a static final constant with class-qualified names.
- Reject invalid stock changes before assigning a new quantity.
- Test independent objects, repeated calls, boundaries, and read-only queries.
Start with one question: who owns this value?
A desk has twelve pens and four notebooks. The quantities answer different questions about different items. If selling a pen changes the notebook count, the model has attached state to the wrong owner. This lesson turns the object model from the previous lesson into a practical decision about every field and method you declare.
An instance member belongs to a particular object: one item's name, quantity, or stock-changing action. A static member belongs to the class rather than one instance. A shared warehouse label or a common maximum can make sense at class level in this small exercise, but each item's quantity must remain an instance field.
Do not choose static because it looks shorter or because main is static. Decide whether the member needs a specific object and whether a change should affect just that object or shared class state. Our examples simulate stock only in memory; they do not update a shop, database, payment, or real warehouse.
Attributes are fields that describe an item's current state
Tutorials often call object data attributes; Java declares them as fields. In StockItem, name and quantity sit inside the class body but outside all methods. Each new StockItem receives its own instance fields. The initializers give an unnamed item zero stock before main assigns a name or performs an adjustment.
The expression pens.quantity selects the field on the object referenced by pens. notebooks.quantity selects the corresponding field on a different object. Both fields have the same declaration and type, but they are separate pieces of state. Updating the first to nine does not touch the second's four.
Direct assignments are useful for seeing ownership, but they can bypass a model's rules. These package-accessible fields are deliberately simple so Main can inspect them in one file. They are not protected from invalid writes. The encapsulation lesson will introduce private fields; for now, use the stock method for normal changes and keep the limitation explicit.
Main.java · Put each quantity on its own instance
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pens: 9
Notebooks: 4
Call an instance method on the item that should change
adjustStock receives a signed delta, meaning a change rather than a replacement quantity. Positive twelve adds twelve units; negative three removes three. Calling pens.adjustStock(-3) makes pens the receiver, so the method reads and eventually assigns that object's quantity, not every item's quantity.
The method returns true only when a nonzero adjustment is accepted. It returns false for a rejected request and leaves the quantity unchanged. This boolean is an outcome, not the resulting stock count. After selling three of twelve pens, the return value is true while the quantity is nine.
Printing stays in main. The instance method checks a rule and performs a state transition; main decides how to communicate that result. Calling the same method on notebooks uses the same implementation but a different receiver and therefore a different quantity field.
Main.java · Change only the receiving item's quantity
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Restock pens: true
Sell pens: true
Restock notebooks: true
Pens | stock: 9
Notebooks | stock: 4
Trace one adjustment from call to result
pens.adjustStock(-3)selects pens as the receiver and supplies -3 as delta.- The method reads the current quantity, twelve, from pens.
- The local candidate becomes nine. Validation accepts that proposed value.
- Only then does the method assign nine to pens.quantity and return true.
- Notebooks still has four. No operation targeted its quantity.
A field remembers; a parameter and a local serve one call
quantity is the remembered state on the object. delta is a parameter receiving one request's change. candidate is a local variable holding one proposed result. Those three names have different jobs: the request and the proposal are temporary working values, while the accepted quantity must remain available for a later call.
Every invocation computes a fresh candidate from the current quantity and the new delta. Adding eight, removing three, and adding two therefore gives eight, five, then seven. There is no shared scratch field to reset and no local quantity initialized back to zero on each invocation.
A local variable named quantity inside adjustStock could hide the field and accidentally keep all work in that local variable. Avoid that distraction here by giving the proposed value the distinct name candidate. The next lesson will explain this and constructor parameters in detail; you do not need either feature to understand this update.
Main.java · A local candidate is temporary; a field keeps state
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
true
true
true
Unnamed | stock: 7
A static field is shared, so use it only for shared meaning
warehouseLabel in this separate example is static. It represents one class-wide label, not a label stored separately on each stock item. Changing StockItem.warehouseLabel to East room changes the shared setting; neither item's quantity changes because those quantities are still instance fields.
Write the class name when accessing a static member. Java allows some static access through instance expressions, but that spelling misleadingly suggests the value belongs to the receiver. StockItem.warehouseLabel makes the ownership visible. There is no implicit per-item copy just because two StockItem objects exist.
A mutable static field is not automatically a good application design. In a larger system with several warehouses, the label would probably belong to a Warehouse object instead. In these single-threaded console examples the setting is shared within the running program, not synchronized across servers, persisted to a database, or made thread-safe by the static keyword.
Main room: 12, 4
East room: 12, 4
Predict a change in ownership
Make quantity static in this example and assign twelve to pens, then four to notebooks. Both reads now return four. Two independent objects do not make two copies of a static field. Restore instance quantity before continuing.
A static helper does not have a current item
isValidQuantity asks whether an explicit number is within the exercise's allowed range. That question does not need a particular StockItem, so a static method is reasonable. Main calls StockItem.isValidQuantity(1000) without creating an object merely to ask about a number.
A static method has no implicit receiver and cannot directly use an instance's quantity as if there were a current item. If it needs instance data, it must be given an object reference or receive the needed values. Being static does not prevent a method from changing an explicitly supplied object; this helper is read-only because of its implementation and contract.
An instance method can call this static helper, as adjustStock does to check its current quantity. The reverse distinction remains important: StockItem.adjustStock(5) is not valid because adjustStock needs the item to change. Creating an item and calling item.adjustStock(5) supplies that receiver.
Main.java · Ask a class-level question without creating an item
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Negative: false
Zero: true
At limit: true
Above limit: false
static and final answer different questions
MAX_QUANTITY is declared static final int with value 1000. static makes this limit class-wide; final prevents reassigning that field after initialization. The uppercase name with underscores is the usual convention for constants, not the mechanism that protects the value.
Use StockItem.MAX_QUANTITY at call sites instead of repeating an unexplained 1000. The validator, adjustment rule, and examples then refer to the same policy value. If the teaching policy changes, the limit has one named definition. final without static could describe an instance field; static without final could describe mutable shared state.
A final reference is different from an immutable object. final StockItem item prevents assigning a new reference to item, but item.adjustStock(3) can still change the referenced object's mutable fields. A primitive constant such as MAX_QUANTITY stores an unchanging number; a final reference does not freeze an entire object graph.
Main.java · Read a shared limit, then apply it to an instance
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Limit: 1000
Fill: true
Overfill: false
Unnamed | stock: 1000
Validate the proposed value before committing a change
The exercise allows quantity from zero through MAX_QUANTITY, inclusive. A zero delta is rejected because this method's success result means an actual change occurred. The method also rejects an already-invalid current quantity rather than silently repairing state written directly by a caller. Those are explicit contracts, not universal inventory policies.
Compute the proposed sum in long: cast quantity before adding delta. Both parameters are ints, so adding them as ints first could overflow before assigning into a wider variable. The long candidate safely represents any sum of these two int values. Check it against zero and the maximum, then narrow only after the allowed range is established.
No assignment to quantity happens on a rejected path. Removing six from five, adding above the maximum, or using an extreme int delta must return false and preserve the five. This validate-then-assign structure avoids partial updates; it is a single-threaded teaching contract, not a database transaction or a guarantee of safe concurrent stock reservation.
Main.java · Reject without partially updating state
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Remove from empty: false
Add five: true
Remove six: false
Zero change: false
Huge addition: false
Huge removal: false
Unnamed | stock: 5
Why use long when the maximum is only one thousand?
The accepted result is small, but the incoming delta can be any int, including 2147483647. The intermediate sum must be evaluated safely before it can be rejected. Widening one operand before addition preserves the proposed value; narrowing is safe only after the range check.
Try removing five from five: the result zero is accepted. Removing six is rejected, and the original five must remain. Test both the returned boolean and the stored field.
A report method should observe rather than consume state
summary returns a display String containing the name and current quantity. It does not subtract stock, rename the item, print, or cache a shared last result. Reading the same unchanged object twice should therefore produce the same text and leave both fields unchanged.
Do not confuse a returned String snapshot with a live connection to the object. If you store before = pens.summary(), then change pens, the earlier String does not rewrite itself. Call summary again when you want the new report. The object's current field state and a previously returned value are separate things.
This query/action distinction makes mistakes easier to isolate. A wrong quantity points toward setup or adjustment, while a wrong label with correct state points toward formatting. Test state directly as well as reading reports; repeating the same wrong report would not prove that the model is correct.
Main.java · Returning a report must not consume inventory
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pens | stock: 12
Pens | stock: 12
Quantity remains: 12
Choose member kinds by responsibility, not by convenience
The completed model has instance name and quantity because they describe one item, instance adjustStock and summary because they use that item's state, a static validation helper because it checks an explicit number, and a static final maximum because every item in this exercise follows one limit.
The final-reference example adds a useful cross-check: a stable variable binding and mutable per-object state can coexist. Do not use final as a substitute for private fields or a deliberate public API. Likewise, do not use static to make all objects share data merely because they share a class declaration.
Before running the lab, name the owner of every value and the receiver of every action. Then check two independent objects, exact boundaries, rejected changes, and repeated reads. This model deliberately leaves custom constructors, private access, inheritance, and persistent inventory for later work so the ownership decision stays easy to see.
Main.java · A final reference is not an immutable object
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pens | stock: 3
Repair exercise: two items share one quantity
Two independently created items unexpectedly report the same quantity. This program compiles, but its field has the wrong owner. Repair the field declaration without changing the creation or report statements.
Main.java · Find the wrong field owner
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
The starter intentionally uses misleading instance-style access to a static field. It currently prints:
Pens: 4
Notebooks: 4
Reveal the explained repair
The two new expressions already created distinct objects, so adding another new would not solve the problem. static quantity is one class field, and both assignments write it. Remove static so each item has its own quantity. The instance-style spelling in the starter hid the shared ownership; use class-qualified spelling for genuinely static members in normal code.
Main.java · Give each item its own quantity
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pens: 12
Notebooks: 4
Independent lab: two items, one policy, separate quantities
Implement two independent stock items and a shared quantity policy. Requests use a signed delta, accepted changes return true, and rejected or zero changes return false without altering state. This is a local console simulation, not connected commerce or persistent inventory.
- Declare one public Main and a separate package-private StockItem in Main.java. Give each instance String name = "Unnamed" and int quantity = 0. Neither field is static.
- Define static final int MAX_QUANTITY = 1000 and static boolean isValidQuantity(int value), accepting zero through the maximum inclusive.
- Implement boolean adjustStock(int delta): reject invalid current state or zero delta, calculate a long candidate before narrowing, reject results outside the valid range, otherwise assign and return true. Every rejected path must preserve quantity.
- Implement String summary() returning name + " | stock: " + quantity. Keep both model methods free of console output, and keep summary read-only.
- In main create Pens and Notebooks separately. Restock by 12 and 4, remove 3 pens, attempt to remove 10 pens, attempt zero change, add 996 notebooks, then attempt one more notebook. Print each returned result in the supplied order.
- Print both final summaries and the reference-inequality check. Save quantities, call both summaries again, and report that the saved quantities still match. Verify lower and upper bounds and extreme deltas before considering the lab finished.
Use the exact request order in the requirements. Expected output:
Limit: 1000
Restock pens: true
Restock notebooks: true
Sell three pens: true
Oversell pens: false
Zero change: false
Fill notebooks: true
Overfill notebooks: false
Pens: Pens | stock: 9
Notebooks: Notebooks | stock: 1000
Different objects: true
Queries preserve quantities: true
Main.java · Build independent stock behavior
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Hint 1: place each member deliberately
Make name and quantity instance fields. MAX_QUANTITY is a static final primitive. The validator is static because it receives the number it checks. adjustStock and summary need the receiving item's fields and therefore remain instance methods.
Hint 2: separate proposal from commitment
Reject invalid current stock and zero delta first. Compute candidate as a long sum with an operand cast before addition. If it is outside zero through the maximum, return false before any assignment. Otherwise store the checked int and return true. summary must contain no state-changing assignment.
Reveal the complete explained solution
Main.java · Complete stock adjustment model
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pens reaches twelve then nine; the removal of ten and zero request leave nine unchanged. Notebooks reaches four then exactly one thousand, and the extra unit is rejected. Both instances follow the same class-level limit but keep separate quantities. main prints outcomes and reports; each model method owns its own calculation or observation without console side effects.
Test changes and boundaries
Predict the result and remaining stock for every request. A rejection is correct only if it returns false and leaves the original state unchanged. Reset restores the supplied example.
- For a new item, reject -1 and 0, accept +1, then accept -1 to return exactly to zero. A rejected request must not change name or quantity.
- Accept +1000 on an empty item and reject +1 afterward. Accept -1000 to empty it. Test both boundaries and one step beyond each.
- From quantity five, try Integer.MAX_VALUE and Integer.MIN_VALUE. Reject both without overflow or mutation; the final quantity must remain five.
- Run two items with identical names but different quantities. Change one and verify the other is unaffected; make the quantity static deliberately, predict the failure, then restore instance ownership.
- Repeat mixed adjustments and summaries. Verify a saved summary String remains the old report after a later adjustment, that queries do not change fields, and that direct invalid field assignment is detected rather than silently repaired by adjustStock.
Common mistakes and how to diagnose them
Making quantity static because main is static
A static entry point can create and use objects. Keep quantity on the instance and call pens.adjustStock(...). Making quantity static shares one count across otherwise independent objects; it does not solve the design problem.
Accessing instance state without an object in a static helper
A class-level method has no current StockItem. Pass the needed value to a static validator, or make the operation an instance method when it acts on one item's state. Do not guess which object's quantity a static call should use.
Changing quantity before checking the result
quantity += delta commits too early. Reversing it later is easy to get wrong, especially around overflow. Compute a widened local candidate, validate it, and assign only on success.
Casting after int addition has already overflowed
long candidate = (long) (quantity + delta) widens the already-computed int result. Cast an operand first: (long) quantity + delta. The lab tests extreme delta values as well as normal small changes.
Keeping the remembered quantity in a fresh local variable
A local initialized to zero on every call cannot remember accepted adjustments across calls. Keep the durable state in an instance field and the proposed calculation in a local candidate.
Treating final as deep immutability
A final primitive constant cannot be reassigned; a final object reference cannot point to a different object. The latter still permits mutations supported by that object. Neither static nor final replaces encapsulation or concurrency control.
Frequently asked questions
Are attributes and fields the same here?
Yes: attributes is the tutorial term for the stored data we declare as Java fields. A parameter belongs to one method call and a local variable belongs to its execution; neither automatically becomes object state.
Can an instance method call a static method?
Yes. adjustStock calls isValidQuantity to check an explicit number. A static method does not need a current item. To call an instance method from main, use a reference to the object that should receive the call.
Does static mean read-only or thread-safe?
No. It describes class-level ownership. A mutable static setting can change, and concurrent access still needs an appropriate design. final has a different assignment rule; neither keyword turns this simulation into a shared transactional inventory system.
Why reject a zero delta?
This exercise defines true as an accepted nonzero change. Returning false for zero makes that meaning consistent. Another API could accept no-op requests, but its documented contract and tests would need to say so.
Why not use private fields and constructors already?
Those are the next steps in the course. Package-accessible fields keep member ownership visible in this introductory one-file example. Direct writes can bypass the rules, so the method also rejects invalid current state; this is still not full encapsulation.
Choose the owner before choosing the modifier
Instance fields preserve one object's state. Instance methods act on the receiving object. Static members represent class-level responsibilities, and final restricts assignment rather than freezing every referenced object. Local variables are for temporary calculations, not remembered inventory.
Before moving on, explain why a static quantity breaks independent items, why a rejected adjustment must not assign anything, and why the candidate is widened before addition. Next: constructors and the this keyword, where you will create deliberately initialized objects instead of assigning starting fields afterward.
Can you choose the right owner for each member?
Answer all six questions and read the explanations. Answers are temporary on this page and do not save course progress.
References and related learning
The stock scenario, examples, explanations, and exercises are original SovranCode teaching material. The Java 21 specification documents the language rules behind member ownership and assignment.
- Java 21 specification: static fields
- Java 21 specification: static methods
- Java 21 specification: final variables
Review objects and identity · Review variable scope · Review numeric conversions · Java course roadmap.
Last updated: