In: Computer Science
The first part of this lab is making your stack generic. Take the code from your working StringStack class and paste it into the GenericStack class. Change the name of the constructors to match the new name of the class (this has been done to this point), then modify the whole class so it uses generics, and can store any type that a programmer asks for.
Until you successfully complete this, the main method will give you nasty compiler errors. Once they stop, you should be good to move on!
Numbers and Readers
To take advantage of polymorphism, Java has a lot of inheritance hierarchies built-in. You may not have known that simple numeric classes, like Integer and Float, are actually part of one! Java contains a generic number class creatively called Number, and pretty much any class in Java whose purpose is to store numbers inherits from this class. (Note that this does not include the primitive int, float, and so on, since there are not classes)
Deep within Java's I/O packages, there's another interesting hierarchy of classes; the Reader and its many children. There are many types of Readers that can extract characters from different things, such as Strings, Files, and arrays. A function that takes in a Reader as a parameter, for example,
Scrolls of Numbers
A few years back, I bought a box of random numbers from an intrepid salesman who insisted they were special. I'd like to find out if that's really the the case. Unfortunately, I've stored the numbers in a bunch of different places - some of them are in Strings, others are in files, it's a bit of a mess.
So I created a generic calculator that, using Readers, can read in a list of numbers from any source, put them into a stack, and then add them all up. Since I'm a good programmer, I put each of these steps in their own functions... but it looks like I accidentally erased some of them. Your next task in the lab is to repair my generic stack calculator by fixing the functions.
Wrapping up
You'll know when you've successfully completed this lab once you can compile and run the code without errors, and you get output that looks somewhat like this (exact numbers may vary due to floating point rounding):
76.4 76.39999961853027 4584425.0 15.324 -------------------------------------------
package src;
import java.util.*;
public class GenericStack<T> {
/* YOUR CODE HERE
* Just like in the ArrayList lab, copy your StringStack code, excluding the
* main method, here.
* Then, modify it so it's generic!
*/
private String[] stack;
public int size;
public int top;
/* Puts the stack into a valid state, ready for us to call methods on.
* The size parameter tell the... well, size of the stack.
*/
public GenericStack(int size) {
this.top = -1;
this.size=size;
this.stack = new String[size];
}
/* If someone calls the constructor with no argument, they should get a
* stack with a default size of 10.
*/
public GenericStack() {
this.size=10;
this.stack = new String[this.size];
this.top = -1;
}
/* Return true if the stack has no elements, and false otherwise.
*/
public boolean empty() {
return this.top == -1;
}
/* Return the object at the top of the stack, WITHOUT removing it.
* If there are no elements to peek, throw a NoSuchElementException.
*/
public String peek() {
if(this.top == -1) {
throw new NoSuchElementException("Empty stack!");
}
return this.stack[this.top];
}
/* Return the object at the top of the stack, AND ALSO remove it.
* If there are no elements to pop, throw a NoSuchElementException.
*/
public String pop() {
if(this.top == -1) {
throw new NoSuchElementException("Empty stack!");
}
return this.stack[this.top--];
}
/* Add a new object to the top of the stack.
* If there is no room in the stack, throw a IllegalStateException.
*/
public void push(String s) {
if(this.top == this.size-1) {
throw new IllegalStateException("Stack is full!");
}
this.stack[++this.top] = s;
}
/* Return the position of an object on the stack. The position of an object
* is just its distance from the top of the stack. So, the topmost item is
* distance 0, the one below the topmost item is at distance 1, etc.
*/
public int search(String s) {
int distance = 0;
int top = this.top;
while (top >= 0) {
if(this.stack[top] == s) {
return distance;
}
distance++;
--top;
}
return -1;
}
public static void main(String[] args) {
// If any of these lines cause a compilation error, your stack hasn't
// been properly made generic.
GenericStack<Integer> intStack = new GenericStack<>();
GenericStack<String> stringStack = new GenericStack<>();
GenericStack<ArrayList<String>> listStack = new GenericStack<>();
}
}
---------------------------
package src;
import java.util.*;
import java.io.*;
import java.math.*;
public class Calculator {
public static void main(String[] args) throws FileNotFoundException, IOException {
String numbers = "56 3 7 2.0 8.4";
char[] moreNumbers = "1.0 2 3 4 5 0.324".toCharArray();
GenericStack<Number> stack1 = makeStack(new
StringReader(numbers));
System.out.println(evaluate(stack1));
GenericStack<Float> stack2 = new
GenericStack<>();
for (String token : numbers.split(" ")) {
stack2.push(Float.parseFloat(token));
}
System.out.println(evaluate(stack2));
GenericStack<Number> stack3 = makeStack(new
FileReader("numbers.txt"));
System.out.println(evaluate(stack3));
GenericStack<Number> stack4 = makeStack(new
CharArrayReader(moreNumbers));
System.out.println(evaluate(stack4));
}
/* This function is meant to take in a Reader called "reader" and
return a
* stack of Numbers.
*
* Remember: don't change anything that's already there. Just add
your new
* code where the comment is to fix the function's signature.
*/
public static /* ??? */ throws IOException {
GenericStack<Number> stack = new
GenericStack<>(64);
char[] data = new char[64];
reader.read(data);
String tokens = new String(data);
for (String token : tokens.split(" ")) {
stack.push(parse(token));
}
return stack;
}
/* This function is meant to take in a stack of ANY KIND of
Number
* called "stack", and return the double you get if you add all of
the
* numbers together.
* The function must be able to accept a stack of ANY KIND of
Number!
*
* Hint: use wildcard generics!
*/
public static /* ??? */ {
/* implement me! */
return 0.0;
}
/* This function is missing a return type.
* Examine it, and see if you can tell what type it should
return.
* (Spoiler: it's probably what you think it is.)
*/
public static /* ??? */ parse(String s) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) { }
try {
return Double.parseDouble(s);
} catch (NumberFormatException e) { }
return new BigDecimal(s);
}
}
// GenericStack.java
// Make the below changes to GenericStack methods to make it generic
package src;
import java.util.*;
public class GenericStack<T> {
/*
* Just like in the ArrayList lab, copy your
StringStack code, excluding the
* main method, here.
* Then, modify it so it's generic!
*/
// convert the array to generic type T
private T [] stack;
public int size, top;
/* Puts the stack into a valid state, ready for us
to call methods on.
* The size parameter tell the... well, size of the
stack.
*/
public GenericStack(int size) {
this.top=-1;
this.size=size;
stack= (T[])new Object[size]; // create an array of
size of generic type T
}
/* If someone calls the constructor with no argument,
they should get a
* stack with a default size of 10.
*/
public GenericStack() {
this.size=10;
this.top=-1;
stack= (T[])new Object[this.size]; // create an array
of size 10 of generic type T
}
/* Return true if the stack has no elements, and false
otherwise.
*/
public boolean empty() {
return this.top == -1;
}
/* Return the object at the top of the stack, WITHOUT
removing it.
* If there are no elements to peek, throw a
NoSuchElementException.
*/
// change the return type to generic type T
public T peek() {
if (this.top==-1) {
throw new NoSuchElementException("Empty
stack!");
}
else {
return this.stack[this.top];
}
}
/* Return the object at the top of the stack, AND ALSO
remove it.
* If there are no elements to pop, throw a
NoSuchElementException.
*/
// change the return type to generic type T
public T pop() {
if (this.top==-1) {
throw new
NoSuchElementException("Empty stack!");
}
else {
return
this.stack[this.top--];
}
}
/* Add a new object to the top of the stack.
* If there is no room in the stack, throw a
IllegalStateException.
*/
// input argument will be of generic type T
public void push(T s) {
if (this.top>=this.size-1)
{
throw new
IllegalStateException("Stack is full!");
}
this.stack[++this.top]= s;
}
/* Return the position of an object on the stack. The
position of an object
* is just its distance from the top of the stack. So,
the topmost item is
* distance 0, the one below the topmost item is at
distance 1, etc.
*/
public int search(String s) {
int dist = 0;
int top = this.top;
while(top>=0) {
if(this.stack[top]==s) {
return dist;
}
dist++;
top--;
}
return -1;
}
public static void main(String[] args) {
// If any of these lines cause a
compilation error, your stack hasn't
// been properly made
generic.
GenericStack<Integer>
intStack = new GenericStack<>();
GenericStack<String>
stringStack = new GenericStack<>();
GenericStack<ArrayList<String>> listStack = new
GenericStack<>();
}
}
// end of GenericStack.java
// Calculator.java
package src;
import java.util.*;
import java.io.*;
import java.math.*;
public class Calculator{
public static void main(String[] args) throws
FileNotFoundException, IOException {
String numbers = "56 3 7 2.0
8.4";
char[] moreNumbers = "1.0 2 3 4 5
0.324".toCharArray();
GenericStack<Number>
stack1 = makeStack(new StringReader(numbers));
System.out.println(evaluate(stack1));
GenericStack<Float> stack2
= new GenericStack<>();
for (String token : numbers.split("
")) {
stack2.push(Float.parseFloat(token));
}
System.out.println(evaluate(stack2));
// Uncomment the below lines so
that it works for the file
/*
GenericStack<Number> stack3 =
makeStack(new FileReader("numbers.txt"));
System.out.println(evaluate(stack3));
*/
GenericStack<Number>
stack4 = makeStack(new CharArrayReader(moreNumbers));
System.out.println(evaluate(stack4));
}
/* This function is meant to take in a Reader called
"reader" and return a
* stack of Numbers.
*
* Remember: don't change anything that's already
there. Just add your new
* code where the comment is to fix the function's
signature.
*/
public static GenericStack<Number>
makeStack(Reader reader) throws IOException {
GenericStack<Number> stack =
new GenericStack<>(64);
char[] data = new char[64];
reader.read(data);
String tokens = new
String(data);
for (String token : tokens.split("
")) {
stack.push(parse(token));
}
return stack;
}
/* This function is meant to take in a stack of ANY
KIND of Number
* called "stack", and return the double you get if you
add all of the
* numbers together.
* The function must be able to accept a stack of ANY
KIND of Number!
*
* Hint: use wildcard generics!
*/
// all the subclasses of Number can be used as the
parameter of GenericStack
public static double evaluate(GenericStack<?
extends Number> stack) {
double sum = 0; // set sum to
0
// loop over the stack, adding the
numbers together
while(!stack.empty())
{
sum +=
((Number)stack.pop()).doubleValue(); // remove and return the top
element of the stack, convert it to double and add it to sum
}
return sum;
}
/* This function is missing a return type.
* Examine it, and see if you can tell what type it
should return.
* (Spoiler: it's probably what you think it is.)
*/
public static Number parse(String s) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
}
try {
return Double.parseDouble(s);
} catch (NumberFormatException e) {
}
return new BigDecimal(s);
}
}
//end of Calculator.java
Output: