|
CS 596 Java Programming
Fall Semester, 1998
Classes (1): members, this, constructors, packages
|
|
|
To Lecture Notes Index
© 1998, All Rights Reserved, SDSU & Roger Whitney
San Diego State University -- This page last updated 19-Sep-98
|
|
Contents of Doc 4, Classes (1): members, this, constructors, packages
Reference
The
Java Programming Language, 2 ed., Arnold & Gosling, Chapter 2 & 10
Classes
We
can define classes and objects using a language approach or a conceptual approach
The
language approach deals with syntax
The
conceptual approach deals with purpose if classes and objects
Language
Definition
Conceptual
Definition
- Abstraction
- Encapsulation
- Information
Hiding
- Hierarchy
Basic
Terms
Java | field | method |
C++ | data member | member function |
Smalltalk | instance variable | method |
C | ??? | function |
Class
member refers to either a field or method
public class BankAccount
{
public float balance = 0.0F; // field
public void deposit( float amount ) // method
{
balance += amount ;
}
}
Comparisons
to C++
Similar
class syntax and structure
No
multiple inheritance, uses interfaces instead
Functions
are virtual by default
Constructors
are much simpler
Destructors
are not need
Packages
provide separate name spaces for classes
Fields
Each
object has its own copy of the fields defined in the class, that is richStudent
and poorInstructor have different values for the field balance. In this regard,
classes are like C/C++ structs.
The
fields in an object exist as long as the object exists
public class BankAccount
{
public float balance = 0.0F;
}
public class RunBank
{
public static void main( String args[] )
{
System.out.println( "Start main " );
BankAccount richStudent = new BankAccount( );
richStudent.balance = (float) 100000;
BankAccount poorInstructor = new BankAccount( );
poorInstructor.balance = 5.10F;
System.out.println( "Student Balance: " +
richStudent.balance );
System.out.println( "Prof Balance: " + poorInstructor.balance );
}
}
Output
Start main
Student Balance: 100000
Prof Balance: 5.1
Multi-File
Programs
Put
class BankAccount in file BankAccount.java
Put
class RunBank in file RunBank.java
Compile
top level class - javac will compile needed classes
Run
program with java.
Example
rohan 21-> ls
BankAccount.java RunBank.java
rohan 22-> javac RunBank.java
rohan 23-> ls
BankAccount.class RunBank.class
BankAccount.java RunBank.java
rohan 24-> java RunBank
Start main
Student Balance: 100000.0
Prof Balance: 5.1
Methods
public class BankAccount
{
public float balance = 0.0F;
public void deposit( float amount )
{
balance += amount ;
}
public String toString()
{
return "Account Balance: " + balance;
}
}
public class RunBank
{
public static void main( String args[] )
{
BankAccount richStudent = new BankAccount( );
BankAccount poorInstructor;
poorInstructor = new BankAccount( );
richStudent.deposit( 10000F);
poorInstructor.deposit( 5.10F );
System.out.println( "Student Balance: " +
richStudent.balance );
System.out.println( "Prof: " + poorInstructor.toString() );
}
}
Output
Student Balance: 10000
Prof: Account Balance: 5.1
About
The Previous Example
"balance"
is a field of the class BankAccount
deposit()
and toString() are methods of class BankAccount
A
method of a class can access the fields of a class
"richStudent"
and "poorInstructor" are references to objects
new
BankAccount() creates a new object from the class BankAccount and returns a
reference to the object
When
a method change the value of a field, the change remains in effect after the
method ends
The
toString() Standard
- When
required Java sends toString() message to objects to convert them to strings
- This
happens in println() and when adding to strings
- In
println( richStudent ) the toString() method is used to convert richStudent to
a string
public class RunBank
{
public static void main( String args[] )
{
BankAccount richStudent = new BankAccount( );
BankAccount poorInstructor = new BankAccount( );
richStudent.deposit( 10000F);
poorInstructor.deposit( 5.10F );
String profBalance = "Prof: " + poorInstructor;
System.out.println( profBalance );
System.out.println( "Prof: " + poorInstructor );
System.out.println( richStudent );
}
}
Output
Prof: Account Balance: 5.1
Prof: Account Balance: 5.1
Account Balance: 10000
Initializing
Fields
There
are three ways in Java to give a field an initial value:
- Direct
Assignment
- Instance
Initialization Block
- Constructors
Direct
Assignment
public class BankAccount
{
public float balance = 0.0F;
public void deposit( float amount )
{
balance += amount ;
}
public String toString()
{
return "Account Balance: " + balance;
}
}
Whenever
a BankAccount object is created from the above class, the balance field will be
set to 0.0F
Initializing
Fields
Instance
Initialization Blocks
New
to Java 1.1.x
Instance
initialization blocks are indicated by blocks of code inside the class, but
outside any method.
Whenever
an object is created from a class the code in each instance initialization
block is executed. If there is more than one instance initialization block they
are executed in order, from top to bottom of the class. Use initialization
blocks when the initialization cannot be done in a simple assignment and needs
no extra input parameters. Direct assignment and constructors are used far more
often than initialization blocks.
public class TaxAccount
{
public double balance;
{ //Instance Initialization block
float baseTaxRate = .33F;
float companySize = 1.24F;
balance = baseTaxRate * companySize -
Math.sin( baseTaxRate ) + .013f;
}
public static void main( String[] args ) //Test method
{
TaxAccount x = new TaxAccount();
System.out.println( x.balance);
}
}
Output
0.09815697215174707
Initializing
Fields - Constructors
Constructors
have the same name as their class
Constructors
have no return type
Constructors
have zero or more arguments
Constructors
are not methods! You can only call a constuctor using "new ClassName"
Constructors
are called when objects are created (using "new")
new
BankAccount( arg1, arg2 ) will call the BankAccount constructor with two
arguments
Like
methods, the argument list used (actual parameters) must match the constructors
definition (formal parameters)
Constructor
Example
public class BankAccount
{
public float balance;
public BankAccount( float initialBalance ) //Constructor
{
balance = initialBalance;
}
public void deposit( float amount )
{
balance += amount ;
}
public String toString()
{
return "Account Balance: " + balance;
}
}
public class RunBank
{
public static void main( String args[] )
{
BankAccount richStudent = new BankAccount( 10000F );
BankAccount poorInstructor = new BankAccount( 5.10F );
System.out.println( "Prof: " + poorInstructor );
System.out.println( "Student: " + richStudent );
}
}
Output
Prof: Account Balance: 5.1
Student: Account Balance: 10000.0
Multiple
Constructors
public class ConstructorExample {
public ConstructorExample( ) {
System.out.println( "In constructor - no argument" );
}; // the ; is legal but does nothing
public ConstructorExample( int size) {
System.out.println( "In constructor - one argument" );
}
public void ConstructorExample( ) {
System.out.println( "return type means it is ");
System.out.println( "not a constructor " );
}
}
public class TestConstructor {
public static void main( String args[] ) {
System.out.println( "Start main " );
ConstructorExample test = new ConstructorExample( );
ConstructorExample x = new ConstructorExample(5);
System.out.println( "Done with Constructors " );
test.ConstructorExample ();
}
}
Output
Start main
In constructor - no argument
In constructor - one argument
Done with Constructors
return type means it is
not a constructor
Implicit
Constructors
If
a class has no constructor the compiler generates an implicit constructor with
no arguments for the class. If a class has any constructor, the compiler will
not generate the implicit constructor. This can cause some confusion. Start
with a working class with no constructor. Now add a constructor to the class.
It compiles with no problem. But code that uses the class will no longer
compile since it used the implicit constructor, which is no longer provided. If
you add a constructor to an existing class, you may need to also add a
constructor with no arguments.
public class ImplicitConstructorOnly {
int size = 5;
}
public class OneConstructor {
OneConstructor( String message ) {
System.out.println( message );
}
}
public class TwoConstructors {
TwoConstructors ( String message ) {
System.out.println( message );
}
TwoConstructors ( ) {
System.out.println( "No argument Constructor" );
}
}
public class Constructors {
public static void main( String args[] ) {
ImplicitConstructorOnly ok = new ImplicitConstructorOnly();
TwoConstructors alsoOk = new TwoConstructors();
OneConstructor compileError = new OneConstructor();
}
}
Order
of Initialization
When
you create an object the direct assignment of fields and instance
initialization blocks are done in order from top to bottom of class, then the
constructor is executed
public class OrderExample
{
int aField = 1;
{
System.out.println( "First block: " + aField );
aField++;
}
public OrderExample()
{
System.out.println( "Start Constructor: " + aField );
aField++;
System.out.println( "End Constructor: " + aField );
}
{
System.out.println( "Second block: " + aField );
aField++;
}
public static void main( String args[] )
{
OrderExample test = new OrderExample();
}
}
Output
First block: 1
Second block: 2
Start Constructor: 3
End Constructor: 4
Order
of Class Elements
Layout
The
compiler will let you order the element of a class nearly any order!
Fields
must be declared before they are used in an initialization block
Human
readers require some consistency
Guidelines
suggest placing all field declaration in one location
- place
all fields at the beginning of the class
or
- place
all fields at the end of the class
Layout
Example
public class OrderExample
{
public OrderExample()
{
a = 8; // OK
}
{
a = 4; // Compile Error
}
int a = 1;
public int getC()
{
return c;
}
int c = 3;
{
a = 4;
}
}
Overloading Methods
The
signature of a method is its name with number, type and order of its parameters.
The
return type is not part of the signature of a method.
Two
methods in the same class can have the same name if their signatures are
different.
public class OverLoad
{
public void same() {
System.out.println( "No arguments" );
}
public void same( int firstArgument ) {
System.out.println( "One int arguments" );
}
public void same( char firstArgument ) {
System.out.println( "One char arguments" );
}
public int same( int firstArgument ) { // Compile Error
System.out.println( "One char arguments" );
return 5;
}
public void same( char firstArgument, int secondArgument) {
System.out.println( "char + int arguments" );
}
public void same( int firstArgument, char secondArgument ) {
System.out.println( "int + char arguments" );
}
}
this
(not that)
"this"
refers to the current object. It is normally used to return self from method
and pass the current object as a parameter from the current object. Java's
"this" differs from C++'s "this". The difference occurs with inheritance.
In
the example below there are two uses of "this". The first use of this
"this.balance = initialBalance" is not needed. "this.balance" refers to the
field named "balance". In the first constructor, replacing "this.balance" with
"balance" would not change the behavior of the code. The second use of this
"this.balance = balance" is required. Since the second constructor has a
parameter named "balance", in the second constructor the name "balance" refers
to the argument, not the field. So "balance = balance" in this constructor
would not set the value of the field. Since "this.balance" refers to the field,
the statement "this.balance = balance" does set the value of the field to the
value of the parameter.
public class BankAccount
{
public float balance;
public BankAccount( float initialBalance )
{
this.balance = initialBalance;
}
public BankAccount( int balance )
{
this.balance = balance;
}
public void deposit( float amount )
{
balance += amount ;
}
public String toString()
{
return "Account Balance: " + balance;
}
}
Returning
this
Since
the deposit method returns "this" which is the current object, we can nest
calls to deposit. richStudent.deposit( 100F ).deposit( 200F) is read from left
to right. First deposit( 100F ) is sent to the object referred to by
richStudent. This method returns "this", the richStudent object with the
increased balance. Then deposit( 200F ) is sent to the object returned by the
first deposit method, i.e. the richStudent object. This would not work if
deposit did not return "this".
public class BankAccount
{
public float balance;
public BankAccount( float initialBalance )
{
this.balance = initialBalance;
}
public BankAccount deposit( float amount )
{
balance += amount ;
return this;
}
}
public class RunBank
{
public static void main( String args[] )
{
BankAccount richStudent = new BankAccount( 10000F );
richStudent.deposit( 100F ).deposit( 200F ).deposit( 300F );
System.out.println( "Student: " + richStudent.balance );
}
}
Output
Student: 10600.0
this
as Parameter
A
Convoluted Contrived Example
This
example shows how a BankAccount object can sent itself as a parameter of a
method to another object. In this case the BankAccount object adds itself to a
list of customers with bad balances.
public class CustomerList {
public BankAccount[] list = new BankAccount[ 100 ];
public int nextFreeSlot = 0;
public void add( BankAccount newItem ){
list[ nextFreeSlot++ ] = newItem;
}
}
public class BankAccount {
public float balance;
public BankAccount( float initialBalance ) {
this.balance = initialBalance;
}
public void badBalanceCheck( CustomerList badAccounts ) {
if ( balance <= 0F ) badAccounts.add( this );
}
}
public class RunBank {
public static void main( String args[] ) {
BankAccount richStudent = new BankAccount( 10000F );
CustomerList customersToDrop = new CustomerList();
richStudent.badBalanceCheck( customersToDrop );
}
}
this
and Chaining Constructors
"this"
with an argument list as first line of a constructor will call another
constructor of the same class with the given parameters. The call to "this"
must be the first statement in the constructor. The example below has the first
two constructors calling another constructor. The changing done here (the first
constructor calling the second, which calls the third) is common. The last
constructor does all the work. The other constructors just determine default
values for some of the parameters. This keeps all the actual work in one place
and avoids cutting-and-pasting code between the constructors.
public class ThisAndConstructors
{
public ThisAndConstructors()
{
this( 5 );
System.out.println( "No argument");
}
public ThisAndConstructors( int a)
{
this( a, 10 );
System.out.println( "One argument");
}
public ThisAndConstructors( int a, int b)
{
System.out.println( "Two arguments");
}
public static void main( String args[] )
{
new ThisAndConstructors();
}
}
Output
Two arguments
One argument
No argument
Calling
Methods before calling a "this" Constructor
You
can call a static method before calling a constructor with "this". A bit
restrictive at times, but it is the rule of the language.
public class MethodCallInConstructor
{
private int value;
public MethodCallInConstructor( int first, int second, int third )
{
this( nonstaticAdd( first, second, third ) ); // compiler error
}
public MethodCallInConstructor( int first, int second )
{
this( add( first, second ) ); // OK, can call static method
System.out.println( "2 arguments: Value = " + value );
}
public MethodCallInConstructor( int first )
{
value = first;
System.out.println( "1 argument: Value = " + value );
}
public int nonstaticAdd( int a, int b, int c )
{
return a + b + c;
}
public static int add( int a, int b )
{
return a + b;
}
}
Finalize
- A Destructor of Sorts
Automatic
storage management handles reclaiming objects and arrays that are no longer
needed by a running program. When an object is determined to no longer be
needed it may be reclaimed, unless it has a finalize method. If a class has a
finalize method, the method is executed. The object is reclaimed the next time
it is determined the object is no longer needed. The finalize method is never
called more than once for an object.
In
Java 1.0.2 it was hard to insure that finalize would be called.
Java
1.1 introduced the method:
- System.runFinalizersOnExit(boolean)
- default
value is false - do not run finalize on exit
You
can now force finalize to run when your program exits
Java
1.2 introduces different levels of references, which gives more control over
garbage collection and finalization. This is an advanced topic, which would
cause much confusion at this point.
Finalize
is rarely used in practice. Either you don't care when the object is collected
by garbage or you need to insure that resources used by the object are released
as soon as possible. Finalize does not help in the latter case.
Finalize
Example
There
is no output since there was no need to run garbage collection in this program,
so finalize() is never called.
public class Death
{
int myId;
public Death ( int sequenceNumber)
{
myId = sequenceNumber;
}
public void finalize( )
{
System.out.println( myId );
}
}
public class Finalization
{
public static void main( String args[] )
{
Death sampleObject;
for ( int k = 0; k < 5; k++ )
sampleObject = new Death( k );
}
}
No
Output
Finalize Example - On Exit
This
example shows how to force finalization on exit.
public class FinalizationOnExit
{
public static void main( String args[] )
{
System.runFinalizersOnExit(true);
Death sampleObject;
for ( int k = 0; k < 5; k++ )
sampleObject = new Death( k );
}
}
Output
0
1
2
3
4
Finalize
Example - Forced
This
example shows how to force finalization. Why is finalize only called on the
first four objects and not the fifth object?
public class FinalizationForced
{
public static void main( String args[] )
{
Death sampleObject;
for ( int k = 0; k < 5; k++ )
sampleObject = new Death( k );
System.gc();
System.runFinalization();
}
}
Output
0
1
2
3
Access
Levels for Fields and Methods
public
- Members
declared public are accessible anywhere the class is accessible
protected
- Members
declared protected are directly accessible to any subclasses, and directly
accessible by code in the same package
- Different
from C++'s protected
private
- Members
declared private are accessible only in the class itself
package
- A
member is declared package level access by not explicitly declaring the member
any access level
- Members
with package access are directly accessible only to code in the same package
- Subclasses
in different packages can not directly access the member directly
Note
that until we cover packages access levels package, protected and public will
seem to be the same.
Public,
Protected, Private
public class AccessLevels
{
public int publicObjectVariable ;
protected float protectedObjectVariable = 10;
private int[] privateObjectVariable;
int packageAcceess = publicObjectVariable;
public AccessLevels ( int startValue )
{
System.out.println( " Start Constructor" );
privateObjectVariable = new int[ startValue ];
}
public void sampleMethod( int value )
{
System.out.println( " In method" );
privateObjectVariable[ 1 ] = value;
}
}
public class TestAccessLevels
{
public static void main( String args[] )
{
AccessLevels test = new AccessLevels ( 11 );
test.publicObjectVariable = 100; // Ok
test.protectedObjectVariable= 100; // Ok
test.privateObjectVariable[ 1 ] = 100; // Compile Error
test.packageAcceess = 100; // Ok
}
}
Look
Ma, Hidden Methods
This
example shows that one can have private methods
public class Hide
{
public void publicAccess()
{
System.out.println( "Start public access " );
internalWorker();
realPrivateWorker();
}
protected void internalWorker()
{
System.out.println( "In internal worker " );
}
private void realPrivateWorker()
{
System.out.println( "In Private worker " );
}
public static void main( String[] args )
{
Hide me = new Hide();
me.publicAccess();
}
}
Output
Start public access
In internal worker
In Private worker
Better
BankAccount
Making
balance private improves the BankAccount class.
We
can now restrict access to the balance.
public class BankAccount
{
private float balance;
public BankAccount( float initialBalance )
{
balance = initialBalance;
}
public void deposit( float amount )
{
balance += amount ;
}
public String toString()
{
return "Account Balance: " + balance;
}
}
Modular
Design Rules
Measure
Twice, Cut Once
Object-Oriented
programming requires more planning earlier then procedural programming
Supports:
Decomposability
Composability
Protection
|
Continuity
Understandability
|
Poker
Rule: Hide Your Cards
or
Information
Hiding
All
information about a module should be private unless it is specifically declared
public
Only
the methods in the definition of the abstraction should be declared public
Supports:
Decomposability
Composability
|
Continuity
Understandability
|
Two
Views on Hidden fields
- All
fields should be protected or private to outside access
- All
fields should be hidden from methods in same class
This
second view is the more extreme of the two. The example below shows how it is
done. Each field has methods that are used to access the field. If others need
to access the field, the method can be made non-private like getBalance(). The
claim is that now it is easy to change the representation of the fields of the
class. To change how balance is stored, we just need to change two methods.
However, unless great care and some skill is used to start with good
abstractions it can be much harder to change the representation of the field
than this example indicates.
public class BankAccount {
private float balance;
public float getBalance() {
return balance;
}
private void setbalance( float newBalance) {
balance = newBalance;
}
public BankAccount( float initialBalance ) {
setbalance( initialBalance );
}
public void deposit( float amount ) {
setbalance( getBalance() + amount );
}
public String toString() {
return "Account Balance: " + getBalance();
}
}
Class
Names, Packages, Import, CLASSPATH
Each
class belongs to a "package".
Packages
are used to group related classes. A package creates a name space. Classes in
different packages can have the same name.
To
put a class in a package, place the statement:
package packageName;
at
the beginning of the source file containing the class.
Example
package EDU.sdsu.roger;
public class Sample {
public void hello() {
System.out.println( "Hello for package sdsu.roger" );
}
}
If
a class has no declared package, it is in the unnamed package.
Package
names are separated into components, which are separated by "."
In
the above example the components are "EDU", "sdsu", and "roger"
Classpath
& Packages
For
the JVM to find a class in a package at runtime, the binaries of the class must
be in the correct spot and your CLASSPATH must be correctly set.
The
name of the package determines the directory structure that contains the
binaries of your classes in the package. Each component of the package name is
mapped to a directory of the same name.
The
next slide shows an example. In this example the source code and the binary
(.class file) are in the same directory. This is not required. The instructions
for the example are required for the .class file. The .java file can be placed
where you like. If you place the source files in a different location, you must
compile them before using them.
Warning
I
have double checked the instructions on the next slide. They do work.
Using
packages and classpaths for the first time can be very confusing and error
prone. Beginners often have trouble using packages for the first time. You may
be tempted to just avoid using packages. You
must
be able to create and use packages. If you do not master this and claim to know
Java, at best you will be laughed at by Java programmers.
Package
Example
package EDU.sdsu.roger;
public class Sample {
public void hello() {
System.out.println( "Hello for package sdsu.roger" );
}
}
The
class must be in a file named "Sample.java"
Place
file "Sample.java" in a directory called "roger"
Place
directory "roger" in a directory called "sdsu"
Place
directory "sdsu" in a directory "EDU"
The
directory "EDU" can be placed anywhere, for completeness of the example I place
it in the directory "/home/ma/whitney/java"
Make
sure that "/home/ma/whitney/java" in the CLASSPATH environment variable
setenv CLASSPATH '.:/home/ma/whitney/java'
Place
the following class anywhere you like and compile & run, the JVM will use
the CLASSPATH to find the class Sample. If you did not compile Sample.java, on
a UNIX machine Java will compile it for you when you compile TestPackage
import EDU.sdsu.roger.Sample;
public class TestPackage {
public static void main( String args[] ) {
Sample me = new Sample();
me.hello();
}
}
Import
Statement
The
import statement allows you to use short class names
Both
the examples below will compile and run
With
Import, Short Name
import EDU.sdsu.roger.Sample;
public class TestPackage {
public static void main( String args[] ) {
Sample me = new Sample();
me.hello();
}
}
Without
Import, Full Name
public class TestPackage {
public static void main( String args[] ) {
EDU.sdsu.roger.Sample me =
new EDU.sdsu.roger.Sample();
me.hello();
}
}
Implicit
Import
All
classes in the java.lang package are imported in all programs implicitly for you
Import
on Demand
You
can replace the class name in the import statement with an "*".
This
will import all classes in that package and is called import on demand
The
following import statement will import all classes in the EDU.sdsu.roger package
import EDU.sdsu.roger.*;
Recommended
Naming Convention
Sun
recommends that you use your domain name in your package names.
The
order of the domain is reversed & the top-level domain name is capitalized.
This
will eliminate name clashes when other people use your packages.
This
convention is being used more frequently now. Even Sun is starting to use the
convention.
Name
Collision
Packages
can be used to avoid name collisions of classes.
Two
use the two Leaf classes below in the same code we can:
- Use
the full name of each class
- Import
one class and use the full name of the other class
- Import
both packages on demand to import other classes in each package and use the
full name for the Leaf classes
Examples
on next slide
File
SearchTree/Leaf
package SearchTree;
public class Leaf
{
public Leaf()
{
System.out.println( "Leaf in a binary search tree" );
}
}
File
Botany/Leaf
package Botany;
public class Leaf
{
public Leaf()
{
System.out.println( "Leaf in a real tree" );
}
}
Use
Full Names
class Test
{
public static void main( String args[] )
{
Botany.Leaf green = new Botany.Leaf();
SearchTree.Leaf node = new SearchTree.Leaf();
}
}
Import
One Leaf
import SearchTree.Leaf;
class Test
{
public static void main( String args[] )
{
Botany.Leaf green = new Botany.Leaf();
Leaf node = new Leaf();
}
}
Importing
Both Leaf Classes - Error
import SearchTree.Leaf;
import Botany.Leaf; // Compile error
class Test
{
public static void main( String args[] )
{
Botany.Leaf green = new Botany.Leaf();
Leaf node = new Leaf();
}
}
What
should this do? And Why?
import SearchTree.Leaf;
import Botany.*;
class Test
{
public static void main( String args[] )
{
Botany.Leaf green = new Botany.Leaf();
Leaf node = new Leaf();
}
}
Class
Access Levels
Public
- Accessible
to code in and outside a package
Package
- Accessible
to code in package only
- No
subclasses outside package allowed
- Used
as helper classes in package
package Botany;
public class Leaf
{
public Leaf()
{
System.out.println( "Leaf in a real tree" );
}
}
package Botany;
class BotanyHelper
{
// Only code in package Botany can use this class
}
Copyright © 1998 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
All rights reserved.
visitors since 09-Sep-98