Java Syntax; A Program's Lexical Structure

De la WikiLabs
Jump to navigationJump to search

A Java application is composed of two types of elements: classes and interfaces.

Rule: A Java file (.java) contains exactly one interface or regular class and zero or more inner classes.


Advice: Avoid using inner-classes. These can be implemented as regular classes with the appropriate access modifiers.

Before presenting the lexical structure of a class and interface, we need to list the data types.

Data Types

In Java, there are two types of data: primitives and references. The primitive types refer to those which have immediate values (numerical or logical), and references are "names" which identify objects.

Primitive Types

In Java, the primitive types are:

Name Category Bit count Possible values
byte integer 8 -128 : 127
short integer 16 -32768 : 32767
int integer 32 -2147483648 : 2147483647
long integer 64 -9223372036854775808 : 9223372036854775807
char character 16 Any Unicode character
boolean logic 8 true, false
float floating point 32 ±1.18 x 10-38 : ±3.4 x 1038
double floating point 64 ±2.23 x 10-308 : ±1.80 x 10308

Primitive type variables are declared just like in C. For example:

int someInteger;
int someOtherInteger = 10;
float someFloat = 10.5f;
double someDouble = 5.3 + someFloat;
boolean condition = true;
char oneChar = 'g';
char newLineChar = '\n';

Attention: In Java, there are no unsigned types.

Similar to C, there are cast operators for primitive types in Java as well. Some conversion operations are implicit (from integer to floating point), but some need to be explicitly specified (from floating point to integer, from char to integer), and others are not possible at all (from boolean to any other type and vice-versa).

In Java, primitive type variables are implicitly initialized with the value 0, and the logic ones with the value false.

Reference Types

Reference variables are objects identifiers. Two references can identify the same object but a reference can't identify two objects at the same time.

Rule: Objects, including arrays, are created with the new operator.

References are initialized implicitly with the null value.

Object References

Objects of any type (class), are created by using the new operator, just like in the following example:

Object obj;
new Object();
obj = new Object();

You can see line 2, which instantiate a new object, of class Object, but there is no reference to this newly created object. Thus, immediately after instantiation, the allocated memory is freed by the garbage collector, and it can no longer be accessed. Line 3 instantiate a new object and the obj variable will reference the newly created object. More about instantiation of objects and constructors in the following sections.

Program example Diagram
//...
Object _obj1 = new Object(); //we name this object ID1
Object _obj2 = new Object(); //we name this object ID2
Object _obj3 = _obj1;        //reference to the same object ID1
Object _obj4 = null;         //does not reffer any object
Object _obj5;                //initialized implicitly with null
//...
Reference type variables example

Character Strings in Java - Class String

In Java, character strings are implemented by class String:

String _name;
_name = "George";
//the following two lines are equivalent
String _firstName = "Vasile";
String _firstName2 = new String("Vasile");

The only overloaded operator in the Java language is the operator +, used for concatenating character strings. If operator + is used for concatenating a String with a primitive value, or reference, then the latter is first automatically converted to a String, by using String.valueOf() method.

String _name;
_name= "George";
String _firstName = "Vasile";
String _fullName= _name + " " + _firstName ;
String _title = "I am number " + 7;


Rule: Comparing objects of type String is done by using the method equals(). The comparison operator == compare references in order to check if reference the same object, not if the objects contain the same data.


String _firstName = "Vasile";
String _firstName2 = new String("Vasile");

if(_firstName2 == _firstName ){
    System.out.println("Equal references");
}else{
    System.out.println("Different references");
}

if(_firstName2 .equals(_firstName )){
    System.out.println("Equal strings");
}else{
    System.out.println("Different strings");
}

Displaying a String in the standard output stream, in a console, is done by using the methods "print" or "println" of the field out of type PrintSteam in the System class:

System.out.println("Hello world!");
String _someString = "Trust " + "no. " + 1;
System.out.println(_someString);

Multi-dimensional Arrays

Arrays are treated as objects in Java, as in there are reference type variables which identify the memory area allocated for the array.

Attention: In the case of reference array instantiation (where each location is a reference, and not a primitive type), the new operator only allocate memory for the array, not each of the array's locations.

Attention: Just like in C, the counting of the elements start from 0. Thus, for an array of 5 elements, the valid positions are 0, 1, 2, 3 and 4.

Program example Diagram
int[] _array1;
_array1 = new int[5];
_array1[3] = 10;
Object[] _array2 = new Object[10];
_array2[2] = new Object();
int[][] _array3 = new int[5][3];
_array3[1][1] = 5;
char[][][] _array4;
Example of reference type variables - arrays

All arrays have a field called length which holds the number of locations in the array. This field is read-only.

int[] _array1 = new int[5];
System.out.println(_array1.length); //"5" will be displayed
int[][] _array2 = new int[5][9];
System.out.println(_array2.length); //"5" will be displayed
System.out.println(_array2[0].length); //"9" will be displayed

The Java Class

The class is the base type in Java, as in any other object oriented language.

Rule:Nothing can exist outside a class or interface, except for package and import directives.


Rule: Any file can contain only one regular class.

The keyword that define a class is class, being always followed by the class name and its implementation, between curly brackets.


Convention: Any class or interface name starts with an uppercase letter.


class SomeClass{
    // here comes the implementation of the class
}

The implementation of a class is made of field (property) definitions and method definitions and implementations. The fields represent the data contained by the class and the methods represent its functionality, meaning the functions that modify the field values.

Rule: The operator used to access a class member (field or method) is ".".

Java Packets

Classes of a Java application are structured in a hierarchy similar to the file system: files and directories (folders), with the classes being similar to files and packages to directories. The separator used between package names and classes is ".".

Rule: A Java package can contain classes, interfaces and other packages.

The keyword specifying the package that contains the class/ interface is package. If this directive is missing, the class is placed implicitly in the root of the package hierarchy.

Rule: In order to use a class in an application, that class needs to be imported in any other class that uses it, with two exceptions: the classes in the package java.lang which are automatically imported, and the classes that are in the same package which are implicitly visible.


package myPackage;

import java.util.ArrayList;
import java.io.*;

class TestClass{

public static void main(String[] _args){
    //ArrayList class is in package java.util so it needs to be imported
    ArrayList _arrayList = new ArrayList(); 

    //both FileOutputStream and IOException are in package java.io so they
    //are both available.
    try{
        FileOutputStream _fileOutputStream = new FileOutputStream("test");
        _fileOutputStream.close();
    }catch(IOException _ioe){
        _ioe.printStackTrace();
    }

    //Class System is in package java.lang so it is automatically imported
    System.out.println("Done!");
}

}

Field Definition

Fields, like any other variable, may be of primitive or reference type. They are declared inside the class, but outside any method.

Convention: Field names start with a lowercase letter.


Convention: In order to distinguish easily between fields and local variables, the latter ones will have names starting with the underscore character ("_").


class SomeClass{
    
    //fields here
    int somePrimitiveField;
    boolean someOtherPrimitiveField;
    Object aReferenceField;
    int[][] anotherReferenceFieldToAnArray;

}

Attention: Fields have different values for each instance of the class, meaning each object.

Program example Diagram
class MainClass{

public static void main(String[] _args){
    SomeClass _obj1 = new SomeClass(); //we name this object ID1
    SomeClass _obj2 = new SomeClass(); //we name this object ID2
    _obj1.somePrimitiveField = 5;
    _obj2.somePrimitiveField = 10;
    System.out.println("Field somePrimitiveField from _obj1 is = " + 
        _obj1.somePrimitiveField + " and from _obj2 is = " +
        _obj2.somePrimitiveField);
    if(_obj1.somePrimitiveField == _obj2.somePrimitiveField){
        System.out.println("They are equal!");
    }else{
        System.out.println("They are NOT equal!");
    }
}

}
Fields in objects example

Method Definition

Methods can be defined and implemented exclusively inside a class. Methods change the state of the object by changing the values of the fields. Just like in C, methods are declared using the following template:

  • zero or more modifiers;
  • returned type (or void if the method does not return a value) - attention, constructors does not have a return type, not even void;
  • method name
  • between parentheses, the list of arguments, defined by type and name, separated by commas (this list can be empty)
  • between curly brackets, the method body (implementation).
Convention: With the exception of constructors, the method names begin with a lowercase letter.


class SomeClass{
    
    //fields here
    int somePrimitiveField;
    boolean someOtherPrimitiveField;
    Object aReferenceField;
    int[][] anotherReferenceFieldToAnArray;

//methods here
//---------------------------------------------------------------------
int getSomePrimitiveField(){
    return somePrimitiveField;
}

//---------------------------------------------------------------------
void setSomePrimitiveField(int _somePrimitiveField){
    somePrimitiveField = _somePrimitiveField;
}

//---------------------------------------------------------------------
boolean aMoreComplexMethod(Object _aReferenceField){
    if(_aReferenceField != null){
        aReference = _aReferenceField;
    }

    return aReference != null;
}

}

Primitive or reference type variables declared inside a method, or as an argument of a method, are called local variables and are not class fields.

Convention: In order to distinguish easily between fields and local variables, the latter ones will have names starting with the underscore character ("_").

Class Constructors

Constructors are special methods of a class that are called automatically when an object is instantiated. Constructors have two specific properties:

Rule: Constructors have the exact same name with the class they belong to.


Rule: Constructors don't have a return type (not even void).

Example of constructors for class SomeClass:

class SomeClass{
    
    //fields here
    int somePrimitiveField;
    boolean someOtherPrimitiveField;
    Object aReferenceField;
    int[][] anotherReferenceFieldToAnArray;

//constructors here
//---------------------------------------------------------------------
SomeClass(){                                    //this is a constructor
    somePrimitiveField = 0;
    someOtherPrimitiveField = false;
    aReferenceField = new Object();
    anotherReferenceFieldToAnArray = null;
}

//---------------------------------------------------------------------
SomeClass(int _somePrimitiveField){       //this is another constructor
    somePrimitiveField = _somePrimitiveField;
    someOtherPrimitiveField = false;
    aReferenceField = new Object();
    anotherReferenceFieldToAnArray = null;
}


//methods here
//---------------------------------------------------------------------
int getSomePrimitiveField(){
    return somePrimitiveField;
}

//---------------------------------------------------------------------
void setSomePrimitiveField(int _somePrimitiveField){
    somePrimitiveField = _somePrimitiveField;
}

//---------------------------------------------------------------------
boolean aMoreComplexMethod(Object _aReferenceField){
    if(_aReferenceField != null){
        aReference = _aReferenceField;
    }

    return aReference != null;
}

}


Rule: If a class does have a constructor defined, then the Java compiler will automatically define a default compiler, with no arguments. However, if there is even one constructor defined, the default constructor will not be automatically inserted anymore.


Rule: When instantiating an object (but not arrays), after the new operator, the next element is always a constructor call.

Example of constructor calling:

class MainClass{

public static void main(String[] _args){
    SomeClass _obj1 = new SomeClass();    //this calls the first constructor
    SomeClass _obj2 = new SomeClass(1234);  //this calls the second constructor

    System.out.println(_obj1.getSomePrimitiveField()); //this will print 0
    System.out.println(_obj2.getSomePrimitiveField()); //this will print 1234
}
    
}

this and super Keywords

Keyword this can be used in two ways:

  • as a call to a constructor from another constructor of the same class;
  • as a reference to the current object as an instance of the current class;

Example if using keyword this:

import java.util.ArrayList;

class SomeClass{
    
    //fields here
    int somePrimitiveField;
    boolean someOtherPrimitiveField;
    Object aReferenceField;
    int[][] anotherReferenceFieldToAnArray;

//constructors here
//---------------------------------------------------------------------
SomeClass(){                                    //this is a constructor
    this(10);    //this is a call to the other constructor
                 // (see method overloading)
}

//---------------------------------------------------------------------
SomeClass(int _somePrimitiveField){       //this is another constructor
    somePrimitiveField = _somePrimitiveField;
    someOtherPrimitiveField = false;
    aReferenceField = new Object();
    anotherReferenceFieldToAnArray = null;
}


//methods here
//---------------------------------------------------------------------
int getSomePrimitiveField(){
    return somePrimitiveField;
}

//---------------------------------------------------------------------
void setSomePrimitiveField(int somePrimitiveField){
    this.somePrimitiveField = somePrimitiveField;
    /*here, "this" is a reference to the 
     *current object. It is used to make the difference 
     *between the field somePrimitiveField and the local
     *variable with the same name. Writing "somePrimitiveField" 
     *with no object specifier ("this") will direct the compiler
     *to the closest defined variable with that name: the local
     *variable:
     */
     somePrimitiveField = 10; //this is the local variable
     this.somePrimitiveField = 10; // this is the field

     /*"this" can also be used for passing the current object
      *as argument to other methods:
      */
     ArrayList _list = new ArrayList();
     _list.add(this);
}

//---------------------------------------------------------------------
boolean aMoreComplexMethod(Object _aReferenceField){
    if(_aReferenceField != null){
        aReference = _aReferenceField;
    }

    return aReference != null;
}

}


Keyword super can be used in two ways (vezi class hierarchies)

  • as a call to a super-class constructor from a constructor in the current class;
  • as a reference to the current object, as an instance of the super-class.
import java.util.ArrayList;

class SomeClass{
    
    //fields here
    int somePrimitiveField;
    boolean someOtherPrimitiveField;
    Object aReferenceField;
    int[][] anotherReferenceFieldToAnArray;

//constructors here
//---------------------------------------------------------------------
SomeClass(){                                    //this is a constructor
    this(10);    //this is a call to the other constructor
                 // (see method overloading)
}

//---------------------------------------------------------------------
SomeClass(int _somePrimitiveField){       //this is another constructor
    super(); 

    /* Class SomeClass is extended from Object class so
     * the super() call is the call to the Object() 
     * constructor.
     */

    somePrimitiveField = _somePrimitiveField;
    someOtherPrimitiveField = false;
    aReferenceField = new Object();
    anotherReferenceFieldToAnArray = null;
}


//methods here
//---------------------------------------------------------------------
int getSomePrimitiveField(){
    return somePrimitiveField;
}

//---------------------------------------------------------------------
void setSomePrimitiveField(int somePrimitiveField){
    this.somePrimitiveField = somePrimitiveField;
    /*here, "this" is a reference to the 
     *current object. It is used to make the difference 
     *between the field somePrimitiveField and the local
     *variable with the same name. Writing "somePrimitiveField" 
     *with no object specifier ("this") will direct the compiler
     *to the closest defined variable with that name: the local
     *variable:
     */
     somePrimitiveField = 10; //this is the local variable
     this.somePrimitiveField = 10; // this is the field

     /*"this" can also be used for passing the current object
      *as argument to other methods:
      */
     ArrayList _list = new ArrayList();
     _list.add(this);
}

//---------------------------------------------------------------------
boolean aMoreComplexMethod(Object _aReferenceField){
    if(_aReferenceField != null){
        aReference = _aReferenceField;
    }

    return aReference != null;
}

//---------------------------------------------------------------------
String toString(){
    return "SomeClass " + super.toString();

    /* Here, the "super" keyword is used as a reference to the current
     * object but it is calling the method "toString" defined in the
     * superclass (see method overwriting). If the "super" was missing,
     * it would recursively call the same method, crashing the program.
     * Instead, it is now calling an entirely different method belonging
     * to the superclass.
     */
}

}


Rule: Any constructor must start with a call to another constructor (by using either super or this). If this is not explicitly specified, then a super() call is implicitly added by the compiler. In this case, compilation errors may occur, if the super-class does not have a constructor with no arguments defined. To avoid this, an explicit constructor call must be added.


Regulă: Any constructor call done by using super or this must be the first statement in a constructor.

Static Members

Static members of a class represent the members which do not change their value or behavior for each distinct object, meaning they are exist in the class context, not the object, or instance context.

class SomeClass{
 
    //fields here
    static int someStaticField;

    int somePrimitiveField;
    boolean someOtherPrimitiveField;
    Object aReferenceField;
    int[][] anotherReferenceFieldToAnArray;
 
}


Exemplu de program Schema bloc
class MainClass{

public static void main(String[] _args){
    SomeClass _obj1 = new SomeClass(); //notam acest obiect cu ID1
    SomeClass _obj2 = new SomeClass(); //notam acest obiect cu ID2
    _obj1.someStaticField = 5;
    _obj2.someStaticField = 10;
    System.out.println("Field someStaticField from _obj1 is = " + 
        _obj1.someStaticField + " and from _obj2 is = " +
        _obj2.someStaticField);
    if(_obj1.someStaticField == _obj2.someStaticField){
        System.out.println("They are equal!");
    }else{
        System.out.println("They are NOT equal!");
    }

    System.out.println("Proper way of accessing static members: " +
        SomeClass.someStaticField);
}

}
Example of static fields in objects


Advice: All methods that only access static fields of the class or no fields at all, should be also declared static.


Convention: Access to static members of a class is done by using the class name, not object references.


Rule: Only fields, methods and inner-classes can be declared static, and not regular classes.

Special Modifiers

Access Modifiers

Modifier Current Class Package Derived Class Outside Package
public Yes Yes Yes Yes
protected Yes Yes Yes No
default(no specifier) Yes Yes No No
private Yes No No No
import java.util.ArrayList;

public class SomeClass{
    
    //fields here
    private int somePrimitiveField;
    private boolean someOtherPrimitiveField;
    private Object aReferenceField;
    private int[][] anotherReferenceFieldToAnArray;

//constructors here
//---------------------------------------------------------------------
public SomeClass(){                             //this is a constructor
    this(10);    //this is a call to the other constructor
                 // (see method overloading)
}

//---------------------------------------------------------------------
public SomeClass(int _somePrimitiveField){//this is another constructor
    super(); 

    /* Class SomeClass is extended from Object class so
     * the super() call is the call to the Object() 
     * constructor.
     */

    somePrimitiveField = _somePrimitiveField;
    someOtherPrimitiveField = false;
    aReferenceField = new Object();
    anotherReferenceFieldToAnArray = null;
}


//methods here
//---------------------------------------------------------------------
public int getSomePrimitiveField(){
    return somePrimitiveField;
}

//---------------------------------------------------------------------
protected void setSomePrimitiveField(int somePrimitiveField){
    this.somePrimitiveField = somePrimitiveField;
    /*here, "this" is a reference to the 
     *current object. It is used to make the difference 
     *between the field somePrimitiveField and the local
     *variable with the same name. Writing "somePrimitiveField" 
     *with no object specifier ("this") will direct the compiler
     *to the closest defined variable with that name: the local
     *variable:
     */
     somePrimitiveField = 10; //this is the local variable
     this.somePrimitiveField = 10; // this is the field

     /*"this" can also be used for passing the current object
      *as argument to other methods:
      */
     ArrayList _list = new ArrayList();
     _list.add(this);
}

//---------------------------------------------------------------------
private boolean aMoreComplexMethod(Object _aReferenceField){
    if(_aReferenceField != null){
        aReference = _aReferenceField;
    }

    return aReference != null;
}

//---------------------------------------------------------------------
public String toString(){
    return "SomeClass " + super.toString();

    /* Here, the "super" keyword is used as a reference to the current
     * object but it is calling the method "toString" defined in the
     * superclass (see method overwriting). If the "super" was missing,
     * it would recursively call the same method, crashing the program.
     * Instead, it is now calling an entirely different method belonging
     * to the superclass.
     */
}

}


Advice: Usually, fields are declared private (see encapsulation), and getters and setters methods and constructors are declared public. There are, of course, exceptions.


public

A public class or member can be accessed from any other class.

protected

A protected field or method is accessible for the classes defined in the same package and for derived classes.

Rule: A regular class can't have the protected access modifier.
default(no specifier)

The lack of an access specifier means that the element is accessible from the current class or classes from the same package, but not other classes.

private

A private field or method can only be accessed from the defining class.

Rule: A regular class can't have the private access modifier.

final Modifier

The final modifier can be applied to:

  • a class - the class can't be extended;
  • an interface - the interface can't be extended;
  • a method - the method can't be overridden;
  • a field - the field can only take one value and it will remain constant throughout of the object's lifetime;

A field which is both static and final is called a class constant.

Modificatorul abstract

O metodă abstractă este o metodă care nu are implementare (doar prototipul).

Regulă: O clasă care conține cel puțin o metodă abstractă trebuie obligatoriu să fie declarată abstractă.


Regulă: O clasă abstractă nu poate fi instanțiată, ci doar extinsă. O clasă extinsă dintr-o clasă abstractă trebuie ori să implementeze toate metodele abstracte, ori să fie și ea declarată abstractă.

Exemplu:

abstract class Sorter{

    protected Object[] data;

public Sorter(Object[] _data){
    data = _data;
}

//this is an abstract method
protected abstract void sort();

public Object[] getResult(){
    sort();
    return data;
}

}


class BubbleSorter extends Sorter{

public BubbleSorter(Object[] _data){
    super(data);
}

//this is the implementation of the abstract method
protected void sort(){
    boolean _done;
    do{
        _done = true;
        for(int i=0; i<data.length - 1; i++){
            if(compare(data[i], data[i + 1]) < 0){
                Object _tmp = data[i];
                data[i] = data[i + 1];
                data[i + 1] = _tmp;
                _done = false;
            }
        }
    }while(!_done);
}

private int compare(Object _obj1, Object obj2){
    //...
} 

}

Modificatorul volatile

Un câmp este declarat volatil atunci când este scris de mai mult de un fir de execuție și în acest caz valoarea respectivă va fi încărcată de fiecare dată din memorie și nu se vor încărca în memoria cache.

Câmpurile volatile reprezintă o alternativă la mecanismul de sincronizare, dar poate fi folosit doar în anumite situații în care nu există race conditions. Vezi Programare concurentă - fire de execuție (Threads).

Modificatorul transient

Un câmp declarat transient nu va fi salvat împreună cu obiectul la serializarea acestuia (vezi serializarea obiectelor).

Modificatorul synchronized

O metodă sincronizată nu este accesibilă decât unui singur thread la un moment dat. Acest cuvânt cheie poate fi utilizat atât ca modificator pentru o metodă, cât și ca bloc de instrucțiuni în interiorul unei metode:

import java.util.ArrayList;

public class Queue extends ArrayList{

public Queue(){
    super();
}

public synchronized void push(Object _obj){
    add(_obj);
}

public Object pop(){
    if(size() != 0){
        synchronized(this){
            return remove(0);
        }
    }else{
        throw new RuntimeException("Queue empty!");
    }
}

}

Mai multe despre thread-uri în capitolul Programare concurentă - fire de execuție (Threads).

Modificatorul native

Există situații în care anumite funcții de sistem nu pot fi apelate în Java, sau alte cazuri în care din Java trebuie apelate funcții dintr-o bibliotecă (.dll sau .so, etc.). În acest caz, din aplicația Java trebuie apelate funcții compilate în limbajul nativ al mașinii, și nu în limbajul mașinii virtuale. Aceste funcții se numesc native, iar mecanismul prin care se pot apela ele din Java se numește JNI (Java Native Interface).

Mai multe despre cum se implementează metodele native pe site-ul Oracle.

Sfat: Evitați pe cât posibil utilizarea metodelor native deoarece acestea nu sunt portabile.

Class Constants

A field declared at the same time static and final is called a class constant.

Extinderea claselor

Limbajul Java suportă moștenirea simplă (vezi ierarhii de clase și polimorfism). Cuvântul cheie care permite extinderea unei clase este extends.

Regulă: O clasă Java extinde exact o singură altă clasă (dacă nu este specificată, aceasta este clasa Object).

Exemplu:

//---------------------------------------------------
//file Vehicle.java
public class Vehicle{          //class Vehicle extends Object

    protected int cylinders;
    protected float maxSpeed;
    protected static float maxAcceptedVehicleMass;
    protected float vehicleMass;

public void startEngine(){
    //...
}

}

//---------------------------------------------------
//file Truck.java
public class Truck extends Vehicle{  //class Truck extends Vehicle

    protected float maxCargoWeight;
    protected float currentCargoWeight;

public void loadCargo(float _cargo){
    // protected fields maxAcceptedVehicleMass and vehicleMass 
    // are inherited from Vehicle class
    if(currentCargoWeight + _cargo <= maxCargoWeight &&
        vehicleMass + currentCargoWeight + _cargo <= maxAcceptedVehicleMass){
        currentCargoWeight += _cargo;
    }
}

}

Șabloane (generics)

Există situații în care o anumită instanță a unei clase lucrează exclusiv cu un anumit tip de date. În acest caz, dacă clasa este definită ca folosind, de exemplu, date de tip Object, atunci trebuie făcuți pași suplimentari, consumatori de timp, pentru a face up-cast de la Object la tipul de date (clasa) cu care se lucrează. În plus, aceste operații de up-cast sunt verificate doar la runtime, crescând șansa să apară erori de programare ne-detectabile la compilare. Ca exemplu, vom descrie o clasă Stack care stochează referințe de tip Object:

public class Stack{

    private Object[] stackArray;
    private int stackTop;

public Stack(int _size){
    stackArray = new Object[_size];
    stackTop = 0;
}

public Object pop() throws Exception{
    if(stackTop > 0){
        return stackArray[--stackTop];
    }

    throw new Exception("Stack empty");
}

public void push(Object _obj) throws Exception{
    if(stackTop < stack.length){
        stackArray[stackTop++] = _obj;
        return;
    }

    throw new Exception("Stack full");
}

}

Și o clasă care folosește o stivă exclusiv pentru obiecte de tip String:

public class StringStacker{

public static void main(String[] _args){
    Stack _stack = new Stack(10);
    try{
        // String is automatically down-cast to Object
        // we could just as well push any kind of reference
        // on this stack, by mistake, and reading and trying
        // to up-cast it as String will give a runtime ClassCastException
        _stack.push("Hello world!");
        _stack.push("Byebye world");

        // Object must be manually up-casted to String
        // every time, even if it's obvious that all 
        // objects on the stack are Strings
        String _secondString = (String)_stack.pop();
        String _firstString = (String)_stack.pop();

        System.out.println(_secondString + ", " + _firstString);
    }catch(Exception _e){
        System.out.println("Stack error: " + _e.getMessage());
    }
}

}

Această problemă se poate rezolva folosind șabloane. Vom rescrie clasa Stack:

public class Stack <GenericType>{

    private GenericType[] stackArray;
    private int stackTop;

public Stack(int _size){
    stackArray = new GenericType[_size];
    stackTop = 0;
}

public GenericType pop() throws Exception{
    if(stackTop > 0){
        return stackArray[--stackTop];
    }

    throw new Exception("Stack empty");
}

public void push(GenericType _obj) throws Exception{
    if(stackTop < stack.length){
        stackArray[stackTop++] = _obj;
        return;
    }

    throw new Exception("Stack full");
}

}

Și clasa StringStacker:

public class StringStacker{

public static void main(String[] _args){
    // at this point, the _stack object is an instance
    // of the class Stack, where GenericType has been
    // replaced by String
    Stack<String> _stack = new Stack<String>(10);
    try{
        // now the push method only takes Strings as
        // an argument; trying to push an Object will 
        // fail with a compile time error
        _stack.push("Hello world!");
        _stack.push("Byebye world");

        // pop() will now return a String so casting
        // is no longer necessary
        String _secondString = _stack.pop();
        String _firstString = _stack.pop();

        System.out.println(_secondString + ", " + _firstString);
    }catch(Exception _e){
        System.out.println("Stack error: " + _e.getMessage());
    }
}

}

Mai multe despre șabloane în tutorial-ul Oracle.

Interfața Java

Interfața Java este o structură echivalentă cu o clasă abstractă în care toate metodele sunt abstracte. Spre deosebire de sistemul de moștenire (extindere), unde o clasă poate moșteni o singură altă clasă (moștenire simplă), o clasă poate implementa oricâte interfețe.

Sfat: Evitați declararea câmpurilor în interfețe. Pot apărea conflicte când există mai multe interfețe care conțin fiecare un câmp cu același nume.


//---------------------------------------------------
//file Closeable.java
public interface Closeable{
    public void close();
}

//---------------------------------------------------
//file Openable.java
public interface Openable{
    public void open();
}

//---------------------------------------------------
//file Stream.java
public class Stream implements Openable, Closeable{

public void close(){
    //.. something here
}
public void open(){
    //... something here
}

}
//---------------------------------------------------


Regulă: Dacă o clasă implementează o interfață, atunci obligatoriu ea trebuie să implementeze toate metodele declarate în interfață, sau clasa să fie abstractă și metodele respective să fie declarate abstracte.

Mai multe despre interfețe în capitolele ierarhii de clase și polimorfism.

Construcții repetitive

Expresia for

Expresia for are două forme posibile. Prima este identică cu cea din C:

for(expresie_initializare; condiție_continuare; expresie_bucla){
    //...
}
  • expresie_inițializare - reprezintă o expresie care se execută o singură dată la intrarea în buclă;
  • condiție_continuare - reprezintă o condiție care se evaluează la începutul fiecărei iterații; ciclul se continuă dacă condiția e adevărată sau dacă ea lipsește;
  • expresie buclă - reprezintă o expresie care se execută necondiționat la sfărșitul fiecărei iterații.

Exemplu pentru un array:

int[] someArray = getArray();

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

Exemplu pentru o listă:

Node firstNode = getList();

for(Node _eachNode = firstNode; _eachNode != null; _eachNode = _eachNode.getNextNode()){
    System.out.println(_eachNode);
}

Exemplu pentru o buclă infinită:

for(;;){
    System.out.println("Freedom!");
}

Al doilea tip de expresie for este utilizat pentru vectori în felul următor (identic ca funcționare cu primul exemplu):

int[] someArray = getArray();

for (int element : someArray) {
    System.out.println(element);
}

Expresia while

Expresia while este identică cu cea din C:

while(conditie_continuare){
    //... something
}
  • conditie_continuare - este o condiție care se evaluează la începutul fiecarei iterații și bucla se încheie dacă aceasta este falsă.
int a = 20;
int b = 3;
while(a != b){
    a++;
    b--;
    System.out.println(a * b);
}

Expresia do - while

Expresia do - while este identică cu cea din C:

do{
    //... something
}while(conditie_continuare);
  • conditie_continuare - este o condiție care se evaluează la sfârșitul fiecarei iterații și bucla se încheie dacă aceasta este falsă.
int a = 20;
int b = 3;
do{
    a++;
    b--;
    System.out.println(a * b);
}while(a != b);

Instrucțiunile break & continue

Instrucțiunile break și continue sunt folosite identic cu cele din C și anume pentru a întrerupe un ciclu repetitiv (break) sau a continua cu următoarea iterație (continue).

Spre exemplu, pentru a căuta primul număr par mai mare ca 100 într-un vector:

int search(int[] someArray){
    int result = 0;

    for(int element: someArray){
        if(element % 2 != 0){
            continue;
        }
        if(element > 100){
            result = element;
            break;
        }
    }

    return result;
}


Regulă: Break și continue acționează asupra celui mai apropiat (inner) for, while sau do-while din metoda curentă.

Construcții condiționale

Operatorul condițional (?)

Operator condițional este identic cu cel din C:

(expresie_de_evaluat ? expresie_adevarat : expresie_fals)
  • expresie_de_evaluat - reprezintă o expresie booleană (adevărat sau fals) de care depinde valoarea finală a expresiei condiționale;
  • expresie_adevarat - dacă expresie_de_evaluat este adevărată, atunci expresia condițională va lua valoarea expresie_adevarat;
  • expresie_fals - dacă expresie_de_evaluat este falsă, atunci expresia condițională va lua valoarea expresie_fals;
public class Utils{

//private constructor means the class can't be instantiated
private Utils(){ 
    //nothing here
}

public static float max(float a, float b){
    // if a is greater than b, the expression is
    // evaluated to a and the value of a is returned
    // else the expression is evaluated to b and the 
    // value of b is returned
    return a > b ? a : b;
}

}

Expresia if - else

Expresia if-else este expresia condițională standard și este și ea identică cu cea din C:

if(conditie){
    //instructiuni
}else{
    //instructiuni
}
  • conditie - este o expresie booleană care se evaluează și în funcție de care se execută primul bloc de instrucțiuni (dacă expresia este adevărată) sau al doilea bloc de instrucțiuni (cel de după else, dacă expresia este falsă).

Exemple de blocuri if:

public class Utils{

private Utils(){
    // nothing here
}

public static float clamp(float _value, float _lowestValue, float _highestValue){
    float _result = 0;
    if(_value < _lowestValue){
        _result = _lowestValue;
    }else if(_value > _highestValue){
        _result = _highestValue;
    }else{
        _result = _value;
    }

    return _result;
} 

public static float max(float a, float b){
    float _result = 0;
    if(a > b){
        result = a;
    }else{
        result = b;
    }
    return result;
} 

public static float abs(float a){
    if(a < 0){
        a = -a;
    }

    return a;
}

}

Expresia switch

Expresia switch este identică cu cea din C. Se folosește atunci când în funcție de valoarea unei expresii trebuie executate diferite instrucțiuni:

switch(expresie){
    case Valoare_1:
        // bloc_instructiuni_1
    break;
    case Valoare_2:
        // bloc_instructiuni_2
    break;
    case Valoare_3:
        // bloc_instructiuni_3
    break;
    default:
        // bloc_instructiuni_default
}
  • expresie - reprezintă expresia în funcție de valoarea căreia se sare la blocul de instrucțiuni corespunzător;
  • Valoare_i, bloc_instructiuni_i - dacă expresia expresie are valoarea Valoare_i, atunci se sare la bloc_instrucțiuni_i; Valoare_i trebuie să fie constantă.
  • default - este un cuvânt cheie care determină blocul la care se sare (bloc_instructiuni_default) în cazul în care valoarea expresiei nu corespunde nici unei valori specificate in blocul switch.
Atenție: Odată saltul făcut la unul din blocurile de instrucțiuni, acestea se vor executa în continuare până la întalnirea cuvântului cheie break (care face ca execuția programului să se reia de la prima instrucțiune după blocul switch) sau a cuvântului cheie return care încheie execuția metodei. Dacă se omite instrucțiunea break atunci programul se va executa în continuare, bloc după bloc, începând cu cel la care s-a sărit.


public String intToString(int _value){
    String _result = "";
    switch(_value){
        case 0: _result = "zero"; break;
        case 1: _result = "one"; break;
        case 2: _result = "two"; break;
        case 3: _result = "three"; break;
        case 4: _result = "four"; break;
        case 5: _result = "five"; break;
        case 6: _result = "six"; break;
        case 7: _result = "seven"; break;
        case 8: _result = "eight"; break;
        case 9: _result = "nine"; break;
        default: _result = "more than nine"; break;
    }

    return _result;
}

Cuvinte cheie pentru tratarea excepțiilor

O excepție se aruncă folosind cuvântul cheie throw. O metodă care nu administrează o excepție trebuie să o declare în prototip folosind cuvântul cheie throws. Un bloc de administrare a excepțiilor este de tip try-catch-finally sau, din Java 7, try-with-resources:

public class TestClass{

    private int value;

public static void main(String[] _args){
    try{
        TestClass _obj = new TestClass(10);
        _obj.someMethod();
    }catch(Exception _e){
        System.out.println("Exception caught: " + _e.getMessage());
    }

    try{
        TestClass _obj = new TestClass(-10);
        _obj.someMethod();
    }catch(Exception _e){
        System.out.println("Exception caught: " + _e.getMessage());
    }

}

public TestClass(int _value) throws Exception{
    if(_value < 0){
        throw new Exception("The value must not be negative: " + _value);
    }
    value = _value;
}

public void someMethod(){
    System.out.println("Running method for value " + value);
}

}

Mai multe despre excepții în capitolul tratarea excepțiilor.

Operatori

Aceasta este lista de operatori permiși în limbajul Java. Spre deosebire de C++, în Java nu este permisă supraîncarcarea operatorilor.

Precedență Operator Descriere Asociativitate
1 () Apel de metodă De la stânga la dreapta
[] Acces la elemente din vector
. Acces la membrul unei clase
2 ++ -- Incrementare și decrementare ca sufix
3 ++ -- Incrementare și decrementare ca prefix De la dreapta la stânga
+ - Plus și minus unar
! ~ NU logic și NU pe biți
(type) val Type cast
new Instanțiere de obiecte sau de vectori
4 * / % Înmuțire, împărțire și modulo (rest) De la stânga la dreapta
5 + - Adunare și scădere
+ Concatenare de stringuri
6 << >> >>> Shift pe biți la stânga, shift pe biți la dreapta cu semn și fără semn
7 < <= Operator de comparare "mai mic decât" și "mai mic sau egal"
> >= Operator de comparare "mai mare ca" și "mai mare sau egal"
instanceof Comparare de tip
8 == != Operator de comparare "egal cu" sau "diferit de"
9 & AND (și) pe biți
10 ^ XOR (sau exclusiv) pe biți
11 | OR (sau) pe biți
12 && AND (și) logic
13 || OR (sau) logic
14 c ? t : f Operator ternar condițional (vezi #Operatorul condițional (?)) De la dreapta la stânga
15 = Asignare simplă
+= -= Asignare cu sumă sau diferență
*= /= %= Asignare cu produs, cât sau rest
<<= >>= >>>= Asignare cu shift la stânga, la dreapta, sau la dreapta fără semn
&= ^= |= Asignare cu AND, XOR, sau OR pe biți

Resurse

  1. Specificația limbajului Java, v.7
  2. Specificația mașinii virtuale Java, v.7
  3. Sintaxa Java - Wikipedia