A Closer look at Methods and Classes
A Closer look at Methods and Classes
}
This program generates the following output:
No parameters
a and b: 10 20
Inside test(double) a: 88
Inside test(double) a: 123.2
This version of OverloadDemo does not define test(int). Therefore, when test( ) is called
with an integer argument inside Overload, no matching method is found. However, Java
can automatically convert an integer into a double and this conversion can be used to
resolve the call. Therefore, after test(int) is not found, Java elevates i to double and then
calls test(double). Java will employ its automatic type conversions only if no exact
match is found.
Method overloading supports polymorphism because it is one way that Java
implements the “one interface, multiple methods” paradigm.
In languages that do not support method overloading, each method must be given a
unique name. Consider the absolute value function. In languages that do not support
overloading, there are usually three or more versions of this function, each with a
slightly different name. For instance, in C, the function abs( ) returns the absolute
value of an integer, labs( ) returns the absolute value of a long integer, and fabs( )
returns the absolute value of a floating-point value. Since C does not support
overloading, each function has its own name, even though all three functions do
essentially the same thing. This makes the situation more complex. Although the
underlying concept of each function is the same, we still have three names to
remember. This situation does not occur in Java, because each absolute value method
can use the same name. Java determines which version of abs( ) to call based upon the
type of argument.
The value of overloading is that it allows related methods to be accessed by use
of a common name. Through the application of polymorphism, several names can
be reduced to one.
But we cannot use the same name to overload unrelated methods. For example,
we could use the name sqr to create methods that return the square of an integer
and the square root of a floating-point value. But these two operations are
fundamentally different. Applying method overloading in this manner defeats its
original purpose.
Overloading Constructors
The output produced by this program is shown here:
Volume of mybox1 is 3000.0
Volume of mybox2 is -1.0
Volume of mycube is 343.0
Here the proper overloaded constructor is called based upon the parameters
specified when new is executed.
Using Objects as Parameters
It is both correct and common to pass objects to methods. For example, consider the
following short program:
This program generates the following output:
ob1 == ob2: true
ob1 == ob3: false
The equalTo( ) method inside Test compares two objects for equality and returns the
result. That is, it compares the invoking object with the one that it is passed. If they
contain the same values, then the method returns true. Otherwise, it returns false.
One of the most common uses of object parameters involves constructors. Frequently, we will
want to construct a new object so that it is initially the same as some existing object. To do
this, we must define a constructor that takes an object of its class as a parameter. For example,
the following version of Box allows one object to initialize another:
The output of this program is
Volume of mybox1 is 3000.0
Volume of mybox2 is -1.0
Volume of cube is 343.0
Volume of clone is 3000.0
A Closer Look at Argument Passing
In general, there are two ways we can pass an argument to a subroutine.
● The first way is call-by-value. This approach copies the value of an argument
into the formal parameter of the subroutine. Therefore, changes made to the
parameter of the subroutine have no effect on the argument.
● The second way an argument can be passed is call-by-reference. In this
approach, a reference to an argument (not the value of the argument) is passed to
the parameter. Inside the subroutine, this reference is used to access the actual
argument specified in the call. This means that changes made to the parameter
will affect the argument used to call the subroutine.
Although Java uses call-by-value to pass all arguments, the precise effect differs
between whether a primitive type or a reference type is passed.
When we pass a primitive type to a method, it is passed by value. Thus, a copy of the
argument is made and what occurs to the parameter that receives the argument has no
effect outside the method. For example, consider the following program:
The output from this program is shown here:
a and b before call: 15 20
a and b after call: 15 20
The operations that occur inside meth( ) have no effect on the values of a and b used
in the call; their values here did not change to 30 and 10.
When we pass an object to a method, the situation changes dramatically, because objects are
passed by what is effectively call-by-reference. When we create a variable of a class type, we
are only creating a reference to an object. Thus, when we pass this reference to a method, the
parameter that receives it will refer to the same object as that referred to by the argument. This
effectively means that objects act as if they are passed to methods by use of call-by-reference.
Changes to the object inside the method do affect the object used as an argument. For example,
consider the following program:
This program generates the following output:
ob.a and ob.b before call: 15 20
ob.a and ob.b after call: 30 10
The actions inside meth( ) have affected the object used as an argument.
Introducing Access Control
● Encapsulation links data with the code that manipulates it.
● However, encapsulation provides another important attribute: access control.
● Through encapsulation, we can control what parts of a program can access the
members of a class.
● By controlling access, we can prevent misuse.
● For example, allowing access to data only through a well-defined set of methods,
we can prevent the misuse of that data.
● Thus, when correctly implemented, a class creates a “black box” which may be
used, but the inner workings of which are not open to tampering.
● How a member can be accessed is determined by the access modifier attached to
its declaration.
● Java supplies a rich set of access modifiers.
● Java’s access modifiers are public, private, and protected.
● Java also defines a default access level.
● protected applies only when inheritance is involved.
● When a member of a class is modified by public, then that member can be
accessed by any other code.
● When a member of a class is specified as private, then that member can only be
accessed by other members of its class.
● So main( ) is always preceded by the public modifier. It is called by code that is
outside the program—that is, by the Java run-time system.
● When no access modifier is used, then by default the member of a class is public
within its own package, but cannot be accessed outside of its package.
● Usually, we will want to restrict access to the data members of a class—allowing
access only through methods.
● Also, there will be times when we will want to define methods that are private to
a class.
● An access modifier precedes the rest of a member’s type specification. That is, it
must begin a member’s declaration statement. Here is an example:
public int i;
private double j;
private int myMethod(int a, char b) { //…
In this program, inside the Test class, a uses default access, which is same as
specifying public. b is explicitly specified as public. Member c is given private
access. This means that it cannot be accessed by code outside of its class. So, inside
the AccessTest class, c cannot be used directly. It must be accessed through its public
methods: setc( ) and getc( ).
We would not be able to compile this program because of the access violation.
To see how access control can be applied to a more practical example, consider the
following improved version of the Stack class
Now both stck, which holds the stack and tos, which is the index of the top of the
stack, are specified as private. This means that they cannot be accessed or altered
except through push( ) and pop( ). Making tos private, for example, prevents other
parts of program from inadvertently setting it to a value that is beyond the end of the
stck array.
Understanding static
● There will be times when we will want to define a class member that will be used
independently of any object of that class.
● Normally, a class member must be accessed only in conjunction with an object of
its class.
● However, it is possible to create a member that can be used by itself, without
reference to a specific instance.
● To create such a member, precede its declaration with the keyword static.
● When a member is declared static, it can be accessed before any objects of its
class are created and without reference to any object.
● We can declare both methods and variables to be static.
● The most common example of a static member is main( ).
● main( ) is declared as static because it must be called before any objects exist.
● Instance variables declared as static are, essentially, global variables.
● When objects of its class are declared, no copy of a static variable is made.
Instead, all instances of the class share the same static variable.
Here is an example. Inside main( ), the static method callme( ) and the static variable b
are accessed through their class name StaticDemo.
Here is the output of this program:
a = 42
b = 99
Introducing final
● A field can be declared as final. Doing so prevents its contents from being
modified, making it, essentially, a constant.
● We can do this in one of two ways:
❖ First, we can give it a value when it is declared.
❖ Second, we can assign it a value within a constructor.
● The first approach is the most common.
Here is an example:
final int FILE_NEW = 1;
final int FILE_OPEN = 2;
final int FILE_SAVE = 3;
final int FILE_SAVEAS = 4;
final int FILE_QUIT = 5;
● Subsequent parts of the program can now use FILE_OPEN, etc., as if they were
constants, without fear that a value has been changed.
Here the size of each array is displayed. The value of length has nothing to do with
the number of elements that are actually in use. It only reflects the number of
elements that the array is designed to hold.
Exploring the String Class
● String is the most commonly used class in Java’s class library.
● The first thing to understand about strings is that every string we create is
actually an object of type String.
● Even string constants are actually String objects.
For example, in the statement
System.out.println("This is a String, too");
the string "This is a String, too" is a String object.
● The second thing to understand about strings is that objects of type String are
immutable; once a String object is created, its contents cannot be altered. While
this may seem like a serious restriction, it is not, for two reasons:
★ If we need to change a string, we can always create a new one that contains
the modifications.
★ Java defines peer classes of String, called StringBuffer and StringBuilder,
which allow strings to be altered, so all of the normal string manipulations
are still available in Java.
Note: The StringBuffer and StringBuilder class provides an alternative to String Class,
as it creates a mutable sequence of characters.
● Strings can be constructed in a variety of ways. The easiest is to use a statement
like this:
String myString = "this is a test";
● Once we have created a String object, we can use it anywhere that a string is
allowed.
For example, this statement displays myString:
System.out.println(myString);
● Java defines one operator for String objects: +. It is used to concatenate two
strings. For example, this statement
String myString = "I" + " like " + "Java.";
results in myString containing "I like Java."
The output produced by this program is shown here:
First String
Second String
First String and Second String
The String class contains several methods that we can use.
● We can test two strings for equality by using equals( ).
● We can obtain the length of a string by calling the length( ) method.
● We can obtain the character at a specified index within a string by calling
charAt().
● Java has a feature that simplifies the creation of methods that need to take a
variable number of arguments. This feature is called varargs and it is short for
variable-length arguments.
● A method that takes a variable number of arguments is called a variable-arity
method, or simply a varargs method.
● Situations that require that a variable number of arguments be passed to a method
are not unusual. For example, a method that opens an Internet connection might
take a username, password, filename, protocol and so on, but supply defaults if
some of this information is not provided. In this situation, it would be convenient
to pass only the arguments to which the defaults did not apply.
● In varargs feature,a variable-length argument is specified by three periods (...).
This syntax tells the compiler that vaTest( ) can be called with zero or more arguments.
As a result, v is implicitly declared as an array of type int[ ]. Thus, inside vaTest( ), v is
accessed using the normal array syntax.
The output from the program is shown here:
Number of args: 1 Contents: 10
Number of args: 3 Contents: 1 2 3
Number of args: 0 Contents:
● Remember, the varargs parameter must be last. For example, the following
declaration is incorrect:
int doIt(int a, int b, double c, int ... vals, boolean stopFlag) { // Error!
Here, there is an attempt to declare a regular parameter after the varargs
parameter, which is illegal.
● There is one more restriction to be aware of: there must be only one varargs
parameter. For example, this declaration is also invalid:
int doIt(int a, int b, double c, int ... vals, double ... morevals) { // Error!
The attempt to declare the second varargs parameter is illegal.
Overloading Vararg Methods
We can overload a method that takes a variable-length argument. For example, the
following program overloads vaTest( ) three times:
The output produced by this program is shown here:
vaTest(int ...): Number of args: 3 Contents: 1 2 3
vaTest(String, int ...): Testing: 2 Contents: 10 20
vaTest(boolean ...) Number of args: 3 Contents: true false false
This program illustrates both ways that a varargs method can be overloaded.
● First, the types of its vararg parameter can differ. This is the case for vaTest(int ...)
and vaTest(boolean ...). Remember, the ... causes the parameter to be treated as an
array of the specified type.In this case, Java uses the type difference to determine
which overloaded method to call.
● The second way to overload a varargs method is to add one or more normal
parameters. This is what was done with vaTest(String, int ...). In this case, Java uses
both the number of arguments and the type of the arguments to determine which
method to call.
Varargs and Ambiguity
Somewhat unexpected errors can result when overloading a method that takes a
variable-length argument. These errors involve ambiguity because it is possible to create
an ambiguous call to an overloaded varargs method.
For example, consider the following program:
In this program, the overloading of vaTest( ) is perfectly correct. However, this
program will not compile because of the following call:
Because the vararg parameter can be empty, this call could be translated into a call to
vaTest(int ...) or vaTest(boolean ...). Both are equally valid. Thus, the call is
inherently ambiguous.
Here is another example of ambiguity. The following overloaded versions of vaTest( )
are inherently ambiguous even though one takes a normal parameter:
Although the parameter lists of vaTest( ) differ, there is no way for the compiler to
resolve the following call:
vaTest(1)
Does this translate into a call to vaTest(int ...), with one varargs argument, or into a
call to vaTest(int, int ...) with no varargs arguments? There is no way for the compiler
to answer this question. Thus, the situation is ambiguous.