Before you can use the variable, the actual storage for the object must be allocated. This is done with the new operator. The syntax is new type( arg1, arg2, ... ).
class OPOdemos {
public static void main( String[] args ) {
String str;
// System.out.println( "str is --" + str + "--" );
// Error: Variable str may not have been initialized.
str = new String( "123 abc" );
System.out.println( "str is --" + str + "--" );
} }
// str is --123 abc--
If you want to set-up an array of objects, then 2 levels of allocation
are required. The first new is creating an array of object
references. The second new inside the loop is creating each
object and initializing each object reference.
class OPOdemos {
public static void main( String[] args ) {
String[] array = new String[3];
for (int i=0; i < array.length; i++)
array[i] = new String( "element " + i );
for (int i=0; i < array.length; i++)
System.out.println( array[i] );
} }
// element 0
// element 1
// element 2
Each class defines some number of "methods" that may be
used by clients of the class. These operations are accessed
just like members of an old-fashioned "struct" in C or Pascal. The
only difference is that they represent functions instead of data.
class OPOdemos {
public static void main( String[] args ) {
String str = new String( "123 xyz" );
System.out.println( "the length of str is " + str.length() );
System.out.println( "the first character is " + str.charAt( 0 ) );
System.out.println( "the last is " + str.charAt( str.length()-1 ) );
} }
// the length of str is 7
// the first character is 1
// the last is z
Most of the classes in Java's standard library require that a
"fully qualified class name" be specified in order for the compiler
to find the class.
class OPOdemos {
public static void main( String[] args ) {
// Date today = new Date();
// Error: class Date not found.
java.util.Date today = new java.util.Date();
System.out.println( "today is " + today );
} }
// today is Thu Jan 06 10:50:53 CST 2000
The class Date is encapsulated in (or assigned to) the
"package" called java.util. The "package" construct
allows classes to be partitioned hierarchically. This minimizes
the possibility of naming conflicts between subsystems, and,
provides a mechanism for configuration management.
The class String is encapsulated in the
"package" called java.lang. The package java.lang
never needs to be referenced explicitly. It is always "included"
by default.An alternative to providing a "fully qualified class name" whenever a package-encapsulated class is referenced is the import statement.
import java.util.Date;
class OPOdemos {
public static void main( String[] args ) {
Date today = new Date();
System.out.println( "today is " + today );
} }
// today is Thu Jan 06 11:20:24 CST 2000
Yet another short-hand alternative to the "fully qualified class name"
is the use of ".*". This is called "import on demand", and it makes
all members of the package visible with considerably less typing effort.
import java.util.*;
class OPOdemos {
public static void main( String[] args ) throws InterruptedException {
Date start = new Date();
Thread.sleep( 2000 );
Date finish = new Date();
System.out.println( "ellapsed time is "
+ (finish.getTime() - start.getTime()) + " milliseconds" );
System.out.println( "start < finish is " + start.before( finish ) );
System.out.println( "start > finish is " + start.after( finish ) );
} }
// ellapsed time is 2000 milliseconds
// start < finish is true
// start > finish is false
The class DateFormat allows for formatting (date => text),
parsing (text => date), and normalization. The date is represented
as a Date object or as the milliseconds since January 1, 1970,
00:00:00 GMT. SimpleDateFormat supports user-defined
patterns for date-time formatting.
import java.util.Date;
import java.text.*;
class OPOdemos {
public static void main( String[] args ) throws ParseException {
Date today = new Date();
System.out.println( "today is " + today );
DateFormat df = DateFormat.getTimeInstance();
System.out.println( "default time is " + df.format( today ) );
df = DateFormat.getDateInstance();
// df = new SimpleDateFormat( "MM/dd/yyyy HH:mm a" );
System.out.println( "default date is " + df.format( today ) );
String str;
System.out.print( "Enter date: " );
str = Read.aString();
while ( ! str.equals( "quit" )) {
System.out.println( " date object is " + df.parse( str ) );
System.out.print( "Enter date: " );
str = Read.aString();
} } }
// ///// DateFormat.getDateInstance() \\\\\
// today is Fri Jan 07 10:43:48 CST 2000
// default time is 10:43:48 AM
// default date is Jan 7, 2000
// Enter date: feb 29, 2000
// date object is Tue Feb 29 00:00:00 CST 2000
// Enter date: feb 30, 2000
// date object is Wed Mar 01 00:00:00 CST 2000
// Enter date: feb 30, 00
// date object is Mon Mar 01 00:00:00 CST 0001
// Enter date: 1/7/2000
// ParseException: Unparseable date: "1/7/2000"
// ///// new SimpleDateFormat( "MM/dd/yyyy HH:mm a" ) \\\\\
// today is Fri Jan 07 10:55:02 CST 2000
// default time is 10:55:02 AM
// default date is 01/07/2000 10:55 AM
// Enter date: 2/2/2000 11:11 AM
// date object is Wed Feb 02 11:11:00 CST 2000
// Enter date: 02/30/2000 12:34 am
// date object is Wed Mar 01 12:34:00 CST 2000
// Enter date: 02/30/2000 12:34
// ParseException: Unparseable date: "02/30/2000 12:34"
Java's standard library includes a boat-load of "collection" classes.
One of these is Stack. All collection classes in Java store and
return objects.
import java.util.Stack;
class OPOdemos {
public static void main( String[] args ) {
Stack s = new java.util.Stack();
for (int i=0; i < args.length; i++)
s.push( args[i] );
System.out.println( "size of stack is " + s.size() );
while ( ! s.empty())
System.out.println( s.pop() );
} }
// D:\Java> java OPOdemos one two three four
// size of stack is 4
// four
// three
// two
// one
Because all collection classes in Java store and return objects,
they do not support Java's primitive data types. For this reason,
each primitive data type has a corresponding "wrapper" class that
can be used to reshape a primitive type into an abstract type. All
of these wrapper classes: begin with a capital letter (to distinguish
their name from the matching primitive type's name), reside in the
java.lang package, and provide an excellent place to stage
utility functionality like parseInt() and toHexString().
import java.util.*;
class OPOdemos {
public static void main( String[] args ) {
Stack s = new Stack();
for (int i=1; i <= Integer.parseInt(args[0]); i++)
s.push( new Integer( i ) );
System.out.println( "int 2 is at position " + s.search( new Integer(2) ));
while ( ! s.empty())
System.out.print( s.pop() + " " );
} }
// D:\Java> java OPOdemos 6
// int 2 is at position 5
// 6 5 4 3 2 1
LinkedList provides uniformly named methods to get, remove
and insert an element at
the beginning and end of the list. These operations allow linked
lists to be used as a stack, queue, or double-ended queue (deque).
Note that LinkedList objects know how to intelligently interact
with println().
import java.util.*;
class OPOdemos {
public static void main( String[] args ) {
LinkedList list = new LinkedList();
for (int i=1; i <= Integer.parseInt(args[0]); i++)
list.add( new Integer( i ) );
for (int i=1; i <= Integer.parseInt(args[0]); i++)
if (i % 2 == 1)
list.addFirst( new Integer( i ) );
else
list.addLast( new Integer( i ) );
System.out.println( "list is " + list );
for (int i=0; i < list.size(); i++)
System.out.print( list.get( i ) + " " );
System.out.print( "\ncontains 2? " + list.contains( new Integer(2) ));
System.out.print( "\ncontains 22? " + list.contains( new Integer(22) ));
} }
// D:\Java> java OPOdemos 6
// list is [5, 3, 1, 1, 2, 3, 4, 5, 6, 2, 4, 6]
// 5 3 1 1 2 3 4 5 6 2 4 6
// contains 2? true
// contains 22? false
////////////////////////////// lab - LinkedList \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
The Vector class implements a growable array of objects
Like an array, it contains components
that can be accessed using an integer index. However,
the size of a Vector can grow or shrink as
needed to accommodate adding and removing items after
the Vector has been created.Each vector tries to optimize storage management by maintaining a capacity and a capacityIncrement. The capacity is always at least as large as the vector size; it is usually larger because as components are added to the vector, the vector's storage increases in chunks the size of capacityIncrement. An application can increase the capacity of a vector before inserting a large number of components; this reduces the amount of incremental reallocation.
import java.util.*;
class OPOdemos {
public static void main( String[] args ) {
Vector v = new Vector();
System.out.println( "capacity is " + v.capacity()
+ ", size is " + v.size() );
for (int i=1; i < 5; i++) {
v.addElement( new Integer(i) );
v.addElement( new Double(i) );
v.addElement( new String( "str" + i) );
}
System.out.println( "capacity is " + v.capacity()
+ ", size is " + v.size() );
int index = v.indexOf( new Integer( 3 ) );
v.setElementAt( new Integer( 6 ), index );
Object obj;
for (int i=0; i < v.size(); i++) {
obj = v.elementAt( i );
System.out.print( obj + "--" );
}
System.out.println();
Integer value = new Integer( 2 );
System.out.println( "contains 2? " + v.contains( value ) );
v.removeElement( value );
System.out.println( "contains 2? " + v.contains( value ) );
v.removeAllElements();
System.out.println( "capacity is " + v.capacity()
+ ", size is " + v.size() );
} }
// capacity is 10, size is 0
// capacity is 20, size is 12
// 1--1.0--str1--2--2.0--str2--6--3.0--str3--4--4.0--str4--
// contains 2? true
// contains 2? false
// capacity is 20, size is 0
////////////////////////////// lab - Vector \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
A very common programming problem is splitting a string
up into chunks (or tokens). Typically, white space (spaces,
tabs, newlines) are used to distinguish one token from the next; but
any character(s) could be used as "delimiters". The example below
employs the 1-argument constructor for class StringTokenizer.
This results in the default delimiters being used to tokenize the
String.
import java.util.StringTokenizer;
class OPOdemos {
public static void main( String[] args ) {
StringTokenizer st = new StringTokenizer( "this is\na\ttest" );
while (st.hasMoreTokens())
System.out.print( st.nextToken() + " -- " );
System.out.println();
} }
// this -- is -- a -- test --
The 2-argument constructor is demonstrated next. The second
argument is a list of characters to be used as token delimiters.
import java.util.*;
class OPOdemos {
public static void main( String[] args ) {
StringTokenizer st =
new StringTokenizer( "|abc|def ghi|||jkl, mno|pqr|", "|" );
System.out.println( "number of tokens is " + st.countTokens() );
while (st.hasMoreTokens())
System.out.println( "-" + st.nextToken() + "-" );
} }
// number of tokens is 4
// -abc-
// -def ghi-
// -jkl, mno-
// -pqr-
The previous example did not preserve "empty" tokens. If that
behavior is desirable then the 3-argument constructor can be
used to "roll your own" empty-token-detection capability.
import java.util.StringTokenizer;
class OPOdemos {
public static void main( String[] args ) {
StringTokenizer st =
new StringTokenizer( "123,,345,456, ,789" , " ," , true );
while (st.hasMoreTokens())
System.out.print( "-" + st.nextToken() + "-" );
System.out.println();
} }
// -123--,--,--345--,--456--,-- --,--789-
////////////////////////////// lab - JackAndJill \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
////////////////////////////// lab - SimpleCalculator \\\\\\\\\\\\\\\\\\\\\\\\\\
class OPOdemos {
public static void main( String[] args ) {
String first = new String( "hello" );
String second = " world";
String third = first + second;
System.out.println( "third is -" + third + "-" );
String alias = first;
first = "goodbye";
System.out.println( "alias is -" + alias + "-, first is -" + first + "-");
} }
// third is -hello world-
// alias is -hello-, first is -goodbye-
equals() compares the String being messaged
to the object being passed. The result is true if and only if
the argument is
not null and is a String object that represents the same
sequence of characters as the target String.
compareTo() returns: 0 if the argument String
is equal to the messaged String, less than 0 if the messaged
String is lexicographically less than the argument String,
and greater than 0 otherwise.
class OPOdemos {
public static void main( String[] args ) {
String one = new String( "abcd" );
String two;
two = "ab" + "cD";
System.out.println( "one is " + one + ", two is " + two );
System.out.println( "one.equals() is " + one.equals( two ) );
System.out.println( "one.compareTo() is " + one.compareTo( two ) );
System.out.println( "two.compareTo() is " + two.compareTo( one ) );
} }
// one is abcd, two is abcD
// one.equals() is false
// one.compareTo() is 32
// two.compareTo() is -32
"In general, using "==" to compare Strings will give you the wrong
results." [Gosling, p165] This is because "==" compares one
object reference to another object reference (i.e.
comparing one address or another); it does not compare the
contents of objects. But, if String literals are involved
directly in
the equality operation, or indirectly (a String object was initialized
with a String literal), then the caching (and reuse) of these literals
internal to the JVM will probably cause the comparison to work
correctly.The example below has the object references s and t referring to the same cached String literal, while u is an object reference to an entirely new object with the same value. Intuition would suggest that the third comparison should succeed. But because s and u represent two entirely different objects, the comparison fails.
class OPOdemos { // String equality
public static void main( String[] args ) {
String s = new String( "abc" );
String t = s;
String u = new String( s );
if (s == t) System.out.println( "s == t" );
if (s.equals(t)) System.out.println( "s.equals(t)" );
if (s == u) System.out.println( "s == u" );
if (s.equals(u)) System.out.println( "s.equals(u)" );
} }
// s == t
// s.equals(t)
// s.equals(u)
Strings are immutable in Java - you cannot change the contents
of a String. You can however change which String
instance a String object reference is referring to. Consider
the code below -
String message = "file not found"; message = message + "!!";The second line is not changing the original String object. It is taking the original, creating a new String object, and causing the "message" object reference to to refer to this new String. The Java compiler actually rewrites the second line as -
message = new StringBuffer(message).append("!!").toString();
Some of the methods in class String seem to suggest
that it is possible to change a String, but what is really
happening is: the String is cloned as a StringBuffer,
the StringBuffer is changed, the StringBuffer is
converted back to a String, and the new String is
returned.
class OPOdemos {
public static void main( String[] args ) {
String first = new String( "the quick brown fox" );
String second = first.replace( ' ', '-' );
System.out.println( "first is -" + first + "-" );
System.out.println( "second is -" + second + "-" );
first = first.replace( ' ', '-' );
System.out.println( "first2 is -" + first + "-" );
} }
// first is -the quick brown fox-
// second is -the-quick-brown-fox-
// first2 is -the-quick-brown-fox-
////////////////////////////// lab - String compare \\\\\\\\\\\\\\\\\\\\\\\\\\\\
Every StringBuffer has an internal capacity. As long as the length of the character sequence contained in the instance does not exceed this capacity, it is not adjusted. If the internal buffer overflows, it is automatically made larger.
Note that append() and insert() not only modify the object they are called on, but they also return that object to the println() call.
class OPOdemos {
public static void main( String[] args ) {
StringBuffer sb = new StringBuffer( "start" );
System.out.println( "append le --- " + sb.append( "le" ) );
sb.setLength( sb.length() - 2 );
System.out.println( "truncate le - " + sb );
System.out.println( "insert le --- " + sb.insert( 4, "le" ) );
for (int i=0; i < sb.length(); i++)
sb.setCharAt( i, (char) (sb.charAt(i)-32) );
System.out.println( "upper case -- " + sb );
} }
// append le --- startle
// truncate le - start
// insert le --- starlet
// upper case -- STARLET
The following example adds a ternary operator expression to skip over
space characters in the convert-to-upper-case loop. Also notice that
explicitly calling toString() is optional.
class OPOdemos {
public static void main( String[] args ) {
StringBuffer sb = new StringBuffer( "java" );
System.out.println( sb );
sb.append( " is real" );
System.out.println( sb.toString() );
System.out.println( sb.insert( 8, "sur" ) );
char ch;
for (int i=0; i < sb.length(); i++)
sb.setCharAt( i, (ch = sb.charAt(i)) == ' ' ? ch : (char)(ch - 32) );
System.out.println( sb );
} }
// java
// java is real
// java is surreal
// JAVA IS SURREAL
////////////////////////////// lab - StringBuffer cycle \\\\\\\\\\\\\\\\\\\\\\\\
////////////////////////////// lab - StringBuffer reverse \\\\\\\\\\\\\\\\\\\\\\
The class Math in package java.lang contains
methods for performing basic numeric operations such as the elementary
exponential, logarithm, square root, and trigonometric functions. Notice
that all the methods demonstrated here are class methods.
class OPOdemos {
public static void main( String[] args ) {
System.out.println( "PI is " + Math.PI );
System.out.println( "2 sqrt is " + Math.sqrt( 2. ) );
System.out.println( "2 raised to .5 is " + Math.pow( 2., .5 ) );
System.out.println( "4 raised to 3 is " + Math.pow( 4., 3. ) );
System.out.println( "2 sqrt-squared is " + Math.pow( Math.sqrt(2.), 2. ));
System.out.println( "-42 absolute value is " + Math.abs( -42 ) );
System.out.println( "4.4 floor is " + Math.floor( 4.4 ) );
System.out.println( "4.4 ceil is " + Math.ceil( 4.4 ) );
System.out.println( "4.4 rounded is " + Math.round( 4.4 ) );
System.out.println( "4.5 rounded is " + Math.round( 4.5 ) );
System.out.println( "42 tens is " + (42 / 10) );
System.out.println( "42 ones is " + (42 % 10) );
} }
// PI is 3.141592653589793
// 2 sqrt is 1.4142135623730951
// 2 raised to .5 is 1.4142135623730951
// 4 raised to 3 is 64.0
// 2 sqrt-squared is 2.0000000000000004
// -42 absolute value is 42
// 4.4 floor is 4.0
// 4.4 ceil is 5.0
// 4.4 rounded is 4
// 4.5 rounded is 5
// 42 tens is 4
// 42 ones is 2
The class method random() in class Math in package
java.lang returns a double value between
0.0 (inclusive) and 1.0 (exclusive). The class Random in package
java.util can be instantiated as an object.
The method nextInt(int) returns an
int value between 0 (inclusive)
and the specified argument (exclusive).
If two instances of Random are created with the same seed,
and the same sequence of method
calls is made for each, they will generate and return identical
sequences of numbers.
class OPOdemos {
public static void main( String[] args ) {
int max = Integer.parseInt( args[0] );
for (int i=0; i < 35; i++)
System.out.print( (int)(java.lang.Math.random() * max) + " " );
System.out.println();
for (int i=0; i < 35; i++)
System.out.print( (int)(Math.random() * 1000) % max + " " );
System.out.println();
java.util.Random obj = new java.util.Random();
for (int i=0; i < 35; i++)
System.out.print( obj.nextInt( max ) + " " );
System.out.println();
} }
// D:\Java> java OPOdemos 10
// 1 4 0 3 1 6 8 7 9 2 4 0 8 4 2 3 9 1 4 6 1 2 1 1 5 1 1 7 2 4 1 4 1 7 8
// 6 9 4 9 7 8 5 4 1 9 1 4 3 5 2 3 2 3 3 5 1 4 6 6 2 7 4 2 3 7 7 3 1 4 9
// 9 6 8 2 4 3 5 7 5 8 6 4 5 5 8 9 3 0 4 4 8 5 9 8 8 1 6 6 2 5 0 1 3 9 2
////////////////////////////// lab - Math \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
The System class contains several useful class attributes and
methods. It cannot be instantiated. Some of the facilities it provides
are: standard input, standard output, and standard error output streams;
access to externally defined "properties"; and a means of loading files
and libraries.
import java.util.Properties;
import java.util.Date;
class OPOdemos {
public static void main( String[] args ) {
Properties array = System.getProperties();
array.list( System.out );
System.out.println( "dir - " + System.getProperty( "user.dir" ));
System.out.println( "sep - " + System.getProperty( "file.separator" ));
System.out.println( "now - " + new Date( System.currentTimeMillis() ));
while (true)
System.exit( 1 );
} }
// -- listing properties --
// java.version=1.2.1
// java.home=d:\jdk1.2.1\jre
// java.io.tmpdir=D:\TEMP\
// os.name=Windows NT
// user.name=vhuston
// sun.boot.library.path=d:\jdk1.2.1\jre\bin
// java.vm.version=1.2.1
// path.separator=;
// file.separator=\
// user.dir=D:\Java
// dir - D:\Java
// sep - \
// now - Thu Jan 06 15:25:17 CST 2000
Working with Java's AWT (Abstract Window Toolkit) is very
straight-forward. The standard Frame class is usable,
but incomplete. The FrameClose class has been supplied
for you, and extends the functionality of the standard Frame
class.
import java.awt.*;
public class OPOdemos {
public static void main( String[] args ) {
FrameClose f = new FrameClose();
f.setTitle( "Title bar string" );
f.setSize( 200, 100 );
f.setLocation( 200, 200 );
f.setVisible( true );
} }
Here, the Frame object is being created, and, having its title
bar initialized at the same time.
import java.awt.*;
public class OPOdemos {
public static void main( String[] args ) {
FrameClose f = new FrameClose( "Title bar string" );
f.setSize( 200, 100 );
f.setLocation( 200, 200 );
f.setVisible( true );
} }
A Label object has now been added to the Frame object.
By default, Label objects are left justified.
import java.awt.*;
public class OPOdemos {
public static void main( String[] args ) {
FrameClose f = new FrameClose( "Label window" );
Label lab = new Label( "Here is a label" );
// Label lab = new Label( "Here is a label", Label.CENTER );
f.add( lab );
f.setSize( 200, 100 );
f.setLocation( 200, 200 );
f.setVisible( true );
} }
Button objects are capable of generating events in response
to user interaction; but, they are not capable of responding to
(or handling) these events. A separate class must be architected
to provide this functionality; then, an instance of that class
is instantiated and registered with each Button object.
The EventListener class has been written for you. A
Button object is the "source" of events, and a
EventListener object is the "destination" of events.TextField objects are capable of receiving keyboard input and then generating an event when the user presses the "Enter" key. The EventListener class is capable of handling both Button and TextField events.
Instead of assigning a fixed width and height to the Frame object, pack() tells the Frame to negotiate a "preferred" size for all its children.
import java.awt.*;
public class OPOdemos {
public static void main( String[] args ) {
FrameClose f = new FrameClose( "Button window" );
Button component = new Button( "press me" );
component.addActionListener( new EventListener() );
// TextField component = new TextField( "initial text", 20 );
// component.addActionListener( new EventListener() );
f.add( component );
// f.setSize( 200, 100 );
f.pack();
f.setLocation( 200, 200 );
f.setVisible( true );
} }
// press me
// press me
// window disposing and exiting
// ///// initial text plus more
// ///// brand new string
// ///// window disposing and exiting
////////////////////////////// lab - FrameClose and Label \\\\\\\\\\\\\\\\\\\\\\
////////////////////////////// lab - FrameClose and Button \\\\\\\\\\\\\\\\\\\\\
When a Frame object manages more than one child, it needs
a "layout manager" object to be specified. The FlowLayout
class causes the children to be layed-out from left to right.
The GridLayout class causes the children to be layed-out
in a regular grid of rows and columns. The two layout managers
behave differently to resize requests. Notice that a single
Button object reference is being reused to create three
distinct Button objects, and, that a single
EventListener object is servicing all three Button
objects.
import java.awt.*;
public class OPOdemos {
public static void main( String[] args ) {
FrameClose f = new FrameClose( "3 Button window" );
f.setLayout( new FlowLayout() );
// f.setLayout( new GridLayout(1,3) );
// f.setLayout( new GridLayout(3,1) );
EventListener bl = new EventListener();
Button btn = new Button( "first" );
btn.addActionListener( bl );
f.add( btn );
btn = new Button( "second" );
btn.addActionListener( bl );
f.add( btn );
btn = new Button( "third" );
btn.addActionListener( bl );
f.add( btn );
f.pack();
f.setVisible( true );
} }
// label is first, data is first
// label is second, data is second
// label is third, data is third
// window disposing and exiting
////////////////////////////// lab - Flow, Layout \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
import java.util.Stack;
public class ClassDemos {
public static void foo( Stack st ) { // modify the object
for (int i=11; i < 15; i++) st.push( new Integer(i) );
}
public static void bar( Stack st ) { // modify the obj reference
st = new Stack();
for (int i=21; i < 25; i++) st.push( new Integer(i) );
System.out.println( "stack in bar: " + st );
}
public static void main( String[] args ) {
Stack s = new Stack();
for (int i=0; i < 5; i++) s.push( new Integer(i) );
while ( ! s.empty()) System.out.print( s.pop() + " " );
System.out.println();
System.out.println( "stack before foo: " + s );
foo( s );
System.out.println( "stack after foo: " + s );
bar( s );
System.out.println( "stack after bar: " + s );
} }
// 4 3 2 1 0
// stack before foo: []
// stack after foo: [11, 12, 13, 14]
// stack in bar: [21, 22, 23, 24]
// stack after bar: [11, 12, 13, 14]