Methods and Classes

Methods and Variables within a class are collectively known as members. There are two types of modifiers that can be used for a member declaration

Every member you declare has an access control, whether you explicity type one or not. Members accessed using the dot operator (.) must belong to the same class, the this keyword is used to reference the currently executing object.

Member Access Modifiers
Public Access When the member is declared protected it means that only the following can access the member
  • class that declared the member
  • any subclasses
  • any class thats in the same package can access it
  • any class
Protected Access When the member is declared protected it means that only the following can access the member
  • class that declared the member
  • any subclasses
  • any class thats in the same package can access it
Default Access When the member has no modifier it has default access only the following can access the member
  • class that declared the member
  • any subclasses
Private Access When the member is declared protected it means that only the following can access the member. Methods that are declared private are implicity final.
  • class that declared the member
Member non-Access Modifers
Final When used this means that the member (method or variable) cannot be overridden. A variable in a interface is implicity marked as final.
Abstract An abstract method is a method that has been declared but not implemented, in other words the method contains no functional code. The abstract method must be implemented in either the subclass or any other subclass that has extend it.
Transient This modifier can only be applied to variables, you are telling the JVM to skip this variable when you attempt to serialize the object declaring it.
Synchronized When used this means that the method can be accessed by only one thread at a time. It can only be applied to methods.
Native Indicates that a method is implemented in a platform-dependent language such as C.
Strictfp Indicates that strict floating-point will be adhered too.
Static

The best way I can describe a static variable or method is that one copy of the static member is contained in memory that allows all instances to access this copy, an example of this is like having a single counter variable that all classes can update.

There is a rule that states that a static method of a class can't access a nonstatic member or variable of its own class. Static methods are also declared final implicity.

You can Make Static:        Methods, Variables and Top-Level nested Classes
You cannot make static:    Constructors, Classes, Interfaces, Inner Classes, Inner Class Methods/Variables, Local variables

There is also no this reference when using static, also all static variables and methods are loaded first in order of appearance, you may find a simple static block of code to initialize variables or execute code.

static variable non-static variable static method non-static method
Static method Y N Y N
Non-Static method Y Y Y Y
Volatile this modifier tells the compiler that the variable can change unexpectedly, this is commonly used when using multi-threading and you want to keep a shared variable that all threads use. Threads can use CPU memory and thus could have different values in a shared variable, a volatile variable keeps the value in main memory and not in cpu memory, this makes sure that all threads have access to the same shared variable value.  

Quick lookup table to show the access to members permitted by each modifier

Modifier
Class
Package
Subclass
World
public
Y
Y
Y
Y
protected
Y
Y
Y
N
no modifier (default)
Y
Y
N
N
private
Y
N
N
N

Now for some examples

Method Examples
Public Access

public void coolMethod() { /* any code that you want */ }

Protected Access protected int coolMethod() { /* any code that you want */ }
Default Access int coolMethod() { /* any code that you want */ }

Note: no access modifier at the beginning of the statement
Private Access private String coolMethod() { /* any code that you want */ }
Final public final void superCoolMethod() { /* any code that you want */ }
Abstract

public abstract int coolMethod();

Note: No curly braces at the end, code for this method must be in a sub-class

Synchronized

public synchronized Record getUserInfo() { /* any code that you want */ }

Note: synchronized can never be used with abstract but you can use the other access modifiers

Static public static void myStaticMethod() { /* any code that you want */ }

Note: this method is shared by all objects within the class, there is only one copy in memory
Local Variable Examples (contained in method or code block)
No Modifier

int bonus = 1.1;

Note: the default modifier is local to the block of code

Final

final int bonus = 1.1;

Note: the final non-access modifier is the only option for a local variable

non-Local (instance) Variable Examples
Public public counter = 0;
Protected protected counter = 0;
Private private counter = 0;
final public final bonus = 1.5;

Note: a final variable has to be initialized, it can be initialized in the constructor of a class, also a variable in an interface is implicity marked as final whether you declare it or not
Static

public class VariableExample {
   private static int classCounter = 0;  // Will get incremented everytime a new class is created
   private int instanceCounter = 0;      // will get incremented when the method increment is called

   public void increment() {
      int localCounter = 0;
      classCounter++;
      instanceCounter++;
      localCounter++;

      System.out.println(
         "classCounter:\t\t" + classCounter + "\n" +
         "instanceCounter:\t\t" + instanceCounter + "\n" +
         "localCounter:\t\t" + localCounter + "\n"
      );
   }

   public static void main(String args[]) {
      VariableExample var1 = new VariableExample();
      VariableExample var2 = new VariableExample();
      VariableExample var3 = new VariableExample();

      var1.increment();
      var2.increment();
      var3.increment();
      var2.increment();
   }
}

# This Java program will increase the static variable classCounter everytime a new class is created, the instanceCounter will only get affect everytime we call the increment method of that instance, the localCounter gets reset everytime we call the increment method

A example using abstraction and using the keywords this and super

Abstraction, this and super public class arrayTest {
   public static void main(String[] args) {

      test1 t1 = new test1();      // create the test1 object which extends the abstract class test2
      t1.testMethod();             // call the abstracted method
      t1.testMethod2();            // calling the normal inheriated method
   }
}

class test1 extends test2{
   int count = 10;                // instance variable

   void testMethod() {            // body of the testMethod to satisify the abstraction
      int count = 5;              // local variable

      System.out.println("local testMethod variable count = " + count);                 // local variable
      System.out.println("test1 instance variable count = " + this.count);              // instance variable
      System.out.println("test2 instance variable count = " + super.count);             // super class variable
      //System.out.println("test3 instance variable count = " + super.super.count);     // illegal super super class variable, use below
      System.out.println("test3 instance variable count = " + super.getSuperCount() );  // get super super count variable
   }
}

abstract class test2 extend test3{            // the abstract class
   int count = 15;                            // super class variable

   abstract void testMethod();                // the abstract method, must at some point provide the body

   void testMethod2(){                        // plain old method, you can mix and match abstract and non-abstract
      System.out.println("testMethod2");
   }

   int getSuperCount() {
      return super.count;                     // return super class test3 count's variable
   }
}

class test3 {
   int count = 20;                            // super super class - getting a bit silly but explains it's possible
}

Note: there is lots going on in this program buts its a good refresher and not a difficult program to work out. By the way there is no such thing as super.super you must get the super class to access the so called super.super class.

You have different rules on returns types depending if you overriding or overloading

Here are some examples regarding the return types

Return a subtype of a type
Number getNumber(){ return Integer.valueOf(1); }
Return a null of a reference type return type
Number getNumber(){ return null; }
Return a narrower primitive data type if the return type is a primitive
long getNumber() { 
  int myInt =2;
  return myInt;
}
Return a primitive data type wrapper if the return type is primitive
long getNumber(){ return Long.valueOf(1); }
Return a wrapper of a primitive to a primitive that is the same or wider
long getNumber(){ return Integer.valueOf(1); }
Just have the return statement but only if modifier is void
public void returnNothing() { return; }

Pass By Value Semantics

When passing a variable or an object to a method two outcomes happen

Passing Variable When you pass a variable to a method, you are passing a copy of the variable which means you can do whatever you want to the variable passed but you will not affect the source of the variable.
Passing Object

When you pass a object to a method you are passing the reference to that object not the object itself, it represents a way to get to a specific object in memory, thus any changes to the object that occur inside the method are being made to the object whose reference was passed.

Below are some examples of passing a variable and an Object to a method

Passing Variables and Objects
public class Test {

    public static void main(String[] args) {
        int i = 3;
        StringObject so = new StringObject("M");                     // Create a new StringObject passing a string to the constructor
        
        System.out.println(passingVariableObject(i, so));            // will display 5Hello, i is passed by value, so is passed by reference
        System.out.println(" " + i + so.toString());                 // will display 3Hello
    }

    static String passingVariableObject(int i, StringObject so){
        i = 5;                                                       // will not change the underlying variable as its passed by value 
        so.s = "Hello";                                              // will change the objects string to "Hello", as its passed by reference
        return " " + i + so;                                         // so Object will use the toString() to displays it's value
    }
}

class StringObject {
    public String s;

    public StringObject(String s) {
        this.s = s;
    }

    @Override
    public String toString() {
        return  s;
    }
}

OverLoading Methods

Overloaded methods lets you reuse the same method name in a class, but with different arguments (and optionally a different return type), you help out other programmers by supplying different argument types so they don't have to perform conversions before invoke your method.

The rules for overloading are as follows

Overloaded Example // Original Method
public void changeSize(int size, String name, float pattern) { ... }

// Overloaded Methods
public void changeSize(int size, String name) { ... }
public void changeSize(float pattern, String name throws IOException) { ... }
public int changeSize(int size, float pattern) { ... }

So how does Java determine what overloaded method to use, its uses a 3 phase check of the method signature as detailed below, note that once Java finds a match the other phases are disguarded, think of it as precedence, phase one having the higher precedence

Phase One Performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity (var args) method invocation
Within Phase 1, if multiple methods meet this criteria the best method is selected by

  • Choose method with Exact Matches
  • Choose method with more specific matches
Primitives, subtypes are considered to climb up either of these branches

  • byte, short, int
  • char, int, long, float, double
If a primitive is passed and a method exists with a parameter that uses widening then this will be selected, a narrowing conversion won't be selected
Phase Two Allows boxing and unboxing, but ignores variable arity (var args) method invocation
Phase Three Allows overloading to be combined with variable arity (var args), boxing and unboxing

Note: var args methods will be last to match if better options exist

Here are some examples of the above

Examples of Java 3 phase method signature testing
public class Test {

    public static void main(String[] args) {

        int i = 1;
        Long l = 2L;
        char c = 3;

        T1 t1 = new T1(i);        // int test, will use int constructor
        T1 t2 = new T1(l);        // Long test, will use long constructor
        T1 t3 = new T1(c);        // char test, will use int constructor

        byte b = 4;
        long l2 = 5;
        short s = 6;
        float f = 5.6f;
        double d = 5.6;
        Character c2 = 7;

        T1 t4 = new T1(b);        // byte test, will use int constructor
        T1 t5 = new T1(l2);       // long test, will use long constructor
        T1 t6 = new T1(s);        // short test, will use int constructor
        // T1 t7 = new T1(f);     // No Constructor for float, compile error
        T1 t8 = new T1(d);        // double test, will use Double constructor
        T1 t9 = new T1(c2);       // Character test, will use char constructor
    }
}

class T1 {

    T1(int a){ System.out.println("int constructor"); }

    T1(long a){ System.out.println("long constructor"); }

    T1(char a){ System.out.println("char constructor"); }

    T1(Character a){ System.out.println("char constructor"); }

    T1(Double d) {  System.out.println("Double constructor"); }
}

Overridden Methods

Methods can be overridden and overloaded, constructors can only be overloaded. Overload methods and constructors let you use the same name but different argument lists, overriding lets you redefine a method in a subclass when you need new sub-class behavior unless the method is mark final. In the case of a abstract method you have no choice you must override it.

The rules for overriding are as follows

The key benefit of overriding a method is to define behavior that is specific to the class

Overriding example

public class Animal {                            // This will be the super class
   public void eat() {
      System.out.println("Generic Animal food");

   public void printAnimal {
      System.out.println("This is the Animal class");
   }
}

class Horse extends Animal {                     // Horse is a sub-class of Animal
   public void eat() {                           // override the Animal eat method
      System.out.println("I like oats, hay");

   public void printAnimal {
      super.printAnimal();                // Invoke the Animal super class code, then come back here
   }
}

class Lion extends Animal {                      // Lion is a sub-class of Animal
   public void eat() {                           // override the Animal eat method
      System.out.println("I like to eat Horses ");
}

Note: to call methods from the superclass use the super keyword.

Difference between Overloaded and Overriding

 
Overloaded
Overridden
Argument list
Must change Must not change
Return type
Can Change Must not change
Exceptions
Can Change Can reduce or eliminate, must not throw new or broader checked exception
Access
Can Change Must not make more restrictive (can be less restrictive)
Invocation
Reference type determines which overloaded version (based on declared argument types) is selected. Happens at compile time. The actual method that's invoked is still a virtual method invocation that happens at runtime, but the compiler will always know the signature of the method that is to be invoked. So at runtime, the argument match will have already been nailed down, just not the actual class in which the method lives Object type (in other words, the type of the actual instance on the heap) determines which method is selected Happens at runtime.

Command-line Arguments to Main

When passing command-line arguments to your Java program it creates a String array to hold the arguments, this array is just like any other array. Note that the array name args can be anything you want but it must be of type String.

Examples of a main method
// Mostly commonly used main signature
public static void main(String[] args)
				
// Other valid main method signatures
public static void main(String args[])
public static void main(String []args)
public static void main(String ... vargs)                          // a variable-length argument called vargs
public static void main(Boolean ... vargs)                         // you can overload main, overloading the above
public static void main(String...vargs)
public static void main(int i, String...vargs)                     // the vargs must be the last argument
public static void main(final String[] args)

public static void main(String[] hello)                            // you can use whatever name you like, here I use hello
public static void main(String[] vars)

public static void main(String[] args) throws Exception
public static void main(String[] args) throws RunTimeException
public static void main(String[] args) throws Throwable
public static void main(String[] args) throws Error
Using main method

public class commandLine {
   public static void main (String args[] ) {

     System.out.println("You passed " + args.length + " arguments");

     for(int i = 0; i < args.length; i++)
        System.out.println(args[i]);
   }
}

# Run the command line
c:\> java testCommandLine one two three

Note: arrays args is just like any other array.

Using variable-length arguments
public class VarArgs {

    public static void main(String ... vargs) {                 // notice the 3 periods, this means a varible-length argument is being passed
        for(String s : vargs){
            System.out.println(s);
        }
    }
}

Note: you can pass any number of string arguments, icluding passing no arguments

Encapsulation

The ability to make changes in your implementation code without breaking the code of others who use your code is a key benefit of encapsulation. You want to hide (mask) implementation details behind a public programming interface. By interface we mean the set of accessible methods your code makes available for other code to call, in other words, your codes API's. You can change the code without forcing a change in the code that calls your changed methods.

To provide maintainability, flexibility and extensibility you must encapsulate the code and to do this you

Set and Get methods

public class Football {
   private String team;                  // this is hidden to the outside world
   private int groundCapacity;           // this is hidden to the outside world

   public void setTeam(String t) { ... }
   public String getTeam() { ... }

   public void setGroundCapacity(int c) { ... }
   public int getGroundCapacity { ... }
}

Note: the only way to get to the instance variables is via the set and get methods


When you create a new class that does something you will document only what access methods (get and set) are available to the public and this will not change, whoever implements your class can get updates and improved versions knowing that implementing this newer version will not break his/her program as the return types and passing variables to methods will be the same.

I have already covered access modifiers in the previous section creating and using methods, so I will cover the princples here, as we learned in the previous section Java provides two ways to hide class memembers

The objectives to hiding members is to protect from unintended changes, and secondly to protect consumers of the class from unintended consequences causing a minimum of disruption should the class need to change and as mentioned above we can use a special type of class called a bean to archive this.

To sum up encapsulation we have the following:

Using the this Reference

The this keyword has the following meaning

access attributes from an Object
String getname() { return this.name; }
call the no args constructor
this();
use inside a constructor
class Test {
  Test() {}	           // no args constructor
  
  Test(String a) {
	this();		   // this() must be on the first line of the constructor
	// do something
  }
}
assign a variable to this
Object o = this;
return an Object
Object returnObject() { return this; }

When a method of a class references another member of that class for a specific object of that class, how does Java ensure that the proper object is referenced, the answer is that each object has access to a reference to itself called the this reference. The this reference is implicity used to refer to both instance variables and methods of an object.

this example public class thisTest {

   public static void main(String[] args) {

      myClass m = new myClass(17, 55, 59);
      mtClass m1;

      m.getTime();

      m1 = m.getObject;                  // return the m object using this
      m1.getTime();

      if ( m.equals(m1) )                // they should be the same object
         System.out.println("m and m1 are the same object");
   }
}

class myClass {

   private int hour, minute, second;     // access using this reference

   // Constructor
   myClass(int hour, int minute, int second) {
      this.hour = hour;                  // reference instance variable hour
      this.minute = minute;              // reference instance variable minute
      this.second = second;              // reference instance variable second
   }

   public void getTime() {
      System.out.println("Hour: " + hour + " Minute: " + minute + " Second: " + second);
   }

   public myClass getObject() {
      return this;                       // return this object
   }
}

Chained Methods

You can use a bit of Java tricky to chain methods (any methods)

General Form result = method1().method2().method3();
Example using String

String x = "hello have a nice day     ";

x = x.trim().concat(" and a jolly good evening").replace('hello'.'Hello');

Note: the statement is processed from left to right, so the trim method is called first, etc