Creating and Using Methods

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 with 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 sub-class or any other subclass that has extend it.
Transient This modifier can only be applied to variables, you are telling 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

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 a 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.

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 sub-class 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.