CMSC131 Final Exam Study Guide

pretext:

this is meant to serve as an outline to guide your studying for the final of this class, if you have any questions, corrections, etc,. you can email me: david0 [at] umd [dot] edu. I wouldn't recommend only using this, here are claire's exam 2 and 3 notes. Some (like two or three) of the topics are omitted b/c of their simplicity (expressions, string operator). Official exam coverage page from UMD here.

by: david and claire 👍

Object Oriented Terminology

See Abstraction, Polymorphism, Encapsulation, and Inheritance

Psuedocode

code written in a non-programming language which will describe the algorithm of the code and the "thinking" process of the code, like what it will look for, what it will store, the point of it is that it can be implemented across different languages.

Java Variables and Types

Primitive Types:

  1. byte
  2. short
  3. int
  4. long
  5. float
  6. double
  7. boolean
  8. char

What does it mean to be primitive?

If you were to write code like int num = 1, your num variable would be assigned a specific size (32 bits) and can only hold numbers which fit in that size, secondly the data inside of the num variable in memory would represent 1, there is no pointer or place in memory where it points to.

Reference Types (Non-Primitive):

there is no fixed number of reference types, you can have an infinite amount so these are some examples of some we've encountered in this class

What's the difference between Reference vs Primitive Types?

Assume we have some Class Student which represents a Student. If I were to say Student david = new Student(); that tells Java "I want to allocate space in memory for david, and I want it to point to a new Student Object." Reference types are simply variables which hold a reference to something, the type is specified so I can't make my Student david point to a new String since that would be a type mismatch.

However, you can point a reference to something which inherits the type on the left, if I had a Class UndergradStudent which inherits/extends Student I will be able to write code such that Student david = new UndergradStudent();. The reason why is because every undergrad is a student, but not every student is an undergrad.

If the above explanation confuses you, see Inheritance

Java Numeric/String/Logical Operators

Numeric Operators

OperatorFunction
+Adds
-Subtracts
/Divides
*Multiplies
%Modulus, remainder

Division truncates (gets rid of) the decimal if both operands are integers

String Operators

+ - concatenates two strings

Logical Operators

&& - AND, both left and right must be true to return true

|| - OR, either left or right can be true to return true

! - NOT, flips true to false, and false to true

Assignment Operators

OperatorFunction
=Sets the value of something
+=Adds the value to the current value
-=Subtracts the value from the current value
*=Multiplies the value from the current value
/=Divides the value from the current value
%=Finds the modulus of the current value

Short Circuiting

The idea that Java doesn't need to check something has already been fulfilled.

true || false stops checking after the first true, because with the OR operator, you know that if the first operand is true, you don't even need to check the second one to know if the overall will be true

This works with AND too but like this, false && true will not check the right hand operand since it already knows after the false that no matter what is in the right operand is, the AND will evaluate to false.

This is why if you have code such that:

int x = 2;
if(true || x++ > 5){
    ...
}  
System.out.println(x);

outputs 2, and never 3, even if the x++ is a ++x

Scanner Class

Import it using import java.util.Scanner

This is how we get user input through the console

ex:

//assume main method and imported

Scanner scan = new Scanner();

scan.nextInt();
scan.next();

nextInt() - returns the next integer inputted from the user

next() - returns the next "String" separated by space or new line characters.

System.out.println() / System.out.print()

Standard print to console methods, we don't need to concern ourselves with how it works.

println() - prints and then adds a new line to the end

print() - simply prints, no new line

Escape Sequence

used in Strings to represent certain special characters

characterfunction
\nnew line
\ttab
\\\
\"double quotes "
\'single quote '

Conditional Statements

logical conditions

symbolmeaning
>greater than
<less than
>=greater than or equal to
<=less than or equal to
==equal to
!=not equal to

Blocks and Scoping

Blocks

A block is simply code written inbetween {} (a set of curly braces).

ex:

public static void main(String[] args) {
        
        int x = 10;
        
        {
            int hidden = 20;
        }
        
        int division = hidden / x;
    }

In the example above, hidden is inside of a block, which is inside of the main (method) block.

Scoping

Variables outside of your blocks cannot be seen or accessed. This is the reason why when you write a Class if you have private instance fields/variables you need getters and setters to access them. In the previous example, hidden is hidden from the rest of main since it's in a block.

Constants

Something is considered a constant if it has the final keyword in front of it. It means you cannot edit that variable. Methods, and Classes can also be final, meaning that they are unable to be modified, or overwritten. Sounds abstract but classes like String are Final, which makes them immutable.

Naming Conventions

Loops (whiles, do whiles, for loops, for-each (enhanced) loops)

Pretest vs Post-test:

pretest means the condition is checked before any iteration is done

posttest means the condition is checked after any iteration is done

while()

while the condition inside is true, the loop will run, it is pre-test

int i = 0;
while(i <= 10){
 System.out.println(i++);
}  

this will count to 10.

do while()

similar to while however it is posttest, so the do body/block will run at least once

int i = 0;
do{
  System.out.println(i);
}while(i > 1);

the code will print out 0 and then realize that i > 1 (0 > 1) is false and stop there.

for()

for loops are pretest and are used when you want to run it for some number of times. (when the number of times you want it to run can be represented as some number).

ex:

for(int i = 0; i < 5; i++){
  ...
}

int i = 0 is only executed once, when the for loop is called, and that's it

i < 5 is the condition for the for loop, so when i == 5 the loop will stop

i++ runs every iteration of the loop, and determines how the variables will be changed.

None of these "sections" of the for loop header have to be one thing, so we can have a more sophisticated for loop like this:

for(int i = 0, j = 5; i < 5 || j > 0; i++, j--) {
            System.out.println(i + " " + j);
        }

for each

you can only use for each loops if the Object's Class implements Iterable, meaning it has an Iterator Object which can be retrieved.

For this class, we only touch on ArrayLists, which implement Iterable

ex:

public static void main(String[] args) {
        
        ArrayList<Integer> numList = new ArrayList<>();
        
        numList.add(1);numList.add(2);numList.add(3);
        numList.add(4);numList.add(5);numList.add(6);
        numList.add(7);numList.add(8);numList.add(9);
        
        for(Integer num : numList) {
            System.out.println(num);
        }
        
        Iterator<Integer> itr = numList.iterator();
        while(itr.hasNext()) {
            System.out.println(itr.next());
        }
    }

Both the enhanced for loop and the while loop accomplish the same thing, however the for each loop is a lot easier to read and write.

explanation

Integer num represents the element we are "retrieving", so if I had an ArrayList of String or Student it would be either one of those instead of Integer num.

numList is where the elements are being retrieved from.

String Class

ex:

String name = "claire";
String otherName = "claire";

java sees that the "claire" string already exists in the heap, and since String objects are immutable, there is no issue with having name and otherName point to the same address in memory.

If you want to guarantee that a new address in memory is allocated, use new String();

String Class Methods: toLowerCase, toUpperCase, length, charAt, substring, isEmpty

methodreturnspurpose
String.toLowerCase()Stringthe all lower case version of the String it is called upon
String.toUpperCase()Stringthe all upper case version of the String it is called upon
String.length()inthow many characters (including spaces) are in the String
String.charAt(int index)chargets the character at the specified index in the String, 0-based indexing.
String.subString(int start)Stringwhen it is just 1 int, it returns from that index start (inclusive) until the end of the String
String.subString(int start, int end)Stringsimilar to one-parameter subString() but the char at index end is not included
String.isEmpty()booleanreturns true if String.length() is 0

Static and Non-Static Methods

Static Methods

Non-Static Methods

Constructors

what is it?

it's how an instance of a Class is constructed (no way), the new keyword is what tells Java to allocate memory for a new Object, and the constructor defines how the object should be defined.

Default constructor

This simply refers to having a no argument constructor, but also refers to the implicit constructor that is written when there is no constructors defined in the class. The implicit constructor will make all reference types null and pritive types their default "empty" values.

Note: the default constructor that our compiler provides goes away when you write ANY constructor

Using "this" for classes

You can use this. prefix to refer to the current object which will call the method, you can use this in non-static methods in order to access instance variables, however it is not required since methods of the Class can already access private fields, it's useful for when you have some method where the parameters have the same name as the instance fields.

ex:

//assume String name instance variable
public void setName(String name){
  this.name = name;
  //current Object's name field = name parameter
}

Instance variables

These are variables which belong to a specific instance of the class, meaning each instance has their own unique version which belongs to them. These are also called instance fields.

Use this when you want all instances to have distinct variables.

Static variables

Static variables are shared all throughout the instances of the class, so each instance has access to a static variable. It is not unique to instances, but it is unique to the class itself.

Use this when you want all instances to have some shared variable.

Get/Set methods

These are methods which either get (return) a field or set (mutate/modify) a field. This is useful when we have private instance variables or we don't want classes outside of our class to directly access instance fields. It is important to note that "getting" a reference type field will return the reference to the field, you need to worry about this if your field is mutable, because then you can change it outside of the class.

How to override the toString() method

@Override
public String toString(){
 return ... 
}

the @Override annotation is not necessary. toString() comes from the Object class, by default it will return a String representing its' address in memory.

How to override the equals(Object obj) method

@Override
public boolean equals(Object obj){
  //if the parameter has the same address in memory it has to be the same as "this", notice that its == not .equals()
 if(obj == this)
    return true;
  //if obj is null, it can't be this because this isn't null
  if(obj == null)
    return false;
  //getClass() calls this.getClass() implicitly, if this and obj have different classes, then they cannot be compared
  if(getClass() != obj.getClass())
    return false;
  
  //at this point we can safely cast the Object into the class of this
  SomeClass someVarName = (SomeClass) obj;
  // compare based on what you want to compare them on
}

Switch statement

A switch statement is when you want a number of options depending on the input, this has the advantage of being easier to read and write than a ton of chained if-else statements. Only a select amount of types are able to be used in switch statements:

and their respective wrapper classes

The exception is made for the String class, which can be used in the switch statement.

ex:

Scanner scan = new Scanner(System.in);
int x = scan.nextInt();

switch (x) {
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        System.out.println("Too low!");
        break;
    case 6:
        System.out.println("Correct number");
        break;
    case 7:
    case 8:
    case 9:
    case 10:
        System.out.println("Too high!");
        break;
    default:
        System.out.println("out of range!");
}

In the code above, if the user enters a number from 1 - 5 it will output that it is too low. If they put 6, it will print out correct number, if they put 7 - 10 it will be too high, if they guess anything else it will be considered out of the range.

Notice you can prevent code duplication by allowing the cases to "fall through" if they behave the same. Also, each case should have a break inside of it, otherwise it will attempt to run the cases under it.

StringBuffer

The StringBuffer class is a version of Strings which is mutable instead of immutable, this means that you are able to alter the value in memory and anything referencing it will see the changes.

One and Two Dimensional Arrays (of primitives and references)

One-dimensional arrays allow you to store elements in one group. You can initialize one by following this form,

type[] arrayName = new type[size];

where type can be both primitives and reference types. The [] represents index, or subscript, so arrayName[0] would be the first element in such array.

Ternary Operator

You want to use this on expressions which evaluate as true or false, it's essentially asking a question, if the expression evaluates to true, it will do the left hand of the operator, false will do the right.

ex:

Scanner scan = new Scanner(System.in);
int x = scan.nextInt();

System.out.print(x == 10 ? "correct number" : "wrong number");

If the user inputs anything besides 10, it is considered a wrong number.

Ternary operators can be nested but you should avoid doing this as it reduces readability. They follow this format: boolean ? if true : if false.

Exceptions

Exceptions are when you want to account for something not happening the way it was expected. A simple example of an exception is with the Scanner class, if our Scanner is looking for nextInt() and the user inputs a String, it will throw an exception java.util.InputMismatchException.

try-catch-finally

a try will isolate code where an exception can be thrown in order to catch it and handle the exception. Sounds very vague, however we can adapt this to the example from above.

ex:

try {
            Scanner scan = new Scanner(System.in);
            int x = scan.nextInt();
            System.out.println(x == 10 ? "correct number" : "wrong number");
}catch(InputMismatchException e) {
            System.out.println(e.getMessage());
}finally {
            System.out.println("This code will always run!");
}

if the code in they try block were to throw an InputMismatchException we are able to handle it with the catch block. In the catch block above, we simply print out the message associated with the error, which is defined by java.lang and not us, so we don't need to worry about that. The code in the finally block will always run no matter what, even if an exception is thrown or not.

Privacy Leaks

Privacy leaks usually occur when you return a reference to something which is mutable, which allows someone or some class to have access to a reference in memory which they shouldn't. This can be avoided by returning a deep copy, where what you are returning is guaranteed to have its' own address in memory.

ex:

public static void main(String[] args) {
        StringBuffer strBffr = exampleMethod();
        System.out.println(strBffr.append("\nAdded in the main method!"));
    }
    
public static StringBuffer exampleMethod() {
        StringBuffer test = new StringBuffer("Out of Scope!");
        return test;
    }

In the code above, you can see that the test StringBuffer is made inside of the method, so it should not be able to be accessed outside of that block, however we return the reference in memory from test to strBffr, which means if test was some instance variable it could be affected from outside the class in an undesirable manner.

In order to prevent this you should do this with StringBuffer.

public static StringBuffer exampleMethod() {
        StringBuffer test = new StringBuffer("Out of Scope!");
        return new StringBuffer(test);
    }

Copying Objects: Reference, Shallow, Deep Copying

Reference Copying

When the copy is just of the reference itself, meaning it's the same Object address. ex:

int[] arr1 = new int[] {1,2,3,4,5};
int[] arr2 = arr1;

They both reference the same address in memory, so it's more of an aliasing situation, and the changes will be seen by both variables.

Shallow Copying

This is just when you only have seperation on the first layer, this works fine when you have an Object with immutables, or an array of primitives.

ex: (this example is with 2d arrays, and is to demonstrate the shortcomings of shallow copies)

String[][] arr1 = new String[][] {{"1","2"},{"3","4"}}

Deep Copying

Abstraction

⚠️ 1 of the 4 pillars of Object Oriented Programming

Abstraction is the idea that we do not need to understand how something works or that it is implemented, just that our input generates some output which we want.

Sounds abstract here's an example: System.out.println();

We don't know how this method works in its' entirety but we do know that it does something we want it to (output to console).

Encapsulation

⚠️ 1 of the 4 pillars of Object Oriented Programming

Very related to abstraction, however the intent is different, with Encapsulation, the programmer doesn't want the user to know how the method/function works in order to protect the security of it. This is why we need getters and setters.

Read more here, courtesy of Matthew.

Interfaces

It is a type of abstract class where you have no definition of methods. You cannot instantiate an Interface, it is meant to "group" classes together where they all share a type. If you implement an interface into a class, you MUST satisfy the method headers.

public interface Animals{
  public String getName();
  public String getAnimalSpecies(); 
}

Any class which implements Animals is required to have getName() and getAnimalSpecies() however the class gets to choose how they implement it.

Interfaces CANT have instance variables.

break and continue

these are control statements (things that alter the flow of logic) which can be used inside of any kind of loop (for, while, do while, etc,.))

break

this will "break" out of the closest for loop, so if you have a nested for loop, it will only break out of the innermost for loop.

int x = -1;
for(int i = 0; i <= 10; i++){
  if(i > 5 && (i % 2) == 1){
    x = i;
    break;
  }
}
System.out.println(x);

as soon as i is greater than 5, and odd it breaks out of the loop, stopping iteration

continue

this will skip the current iteration, at the point in the code where the continue is

for(int i = 0; i <= 10; i++){
 if(i % 2 == 0){
   continue;
 }
  System.out.println(i);
}

this code will print out all of the odd numbers between 0 and 10, notice how we skip the evens using continue

for while loops it's a little different

        int i = 0;
        while (i <= 10) {
            if (i % 2 == 0) {
                i++;
                continue;
            }
            System.out.println(i++);
        }

since while loops do not iterate through i automatically, we need to do it before we actually reach the continue line

Wrappers

Some things in Java require reference types, this is where the Wrapper classes for primitive types become useful.

We are not allowed to have an ArrayList<int> but we can do ArrayList<Integer> and it will interact the same way that primitive ints would. Even when an int is expected like in a parameter, it is automatically boxed and unboxed depending on the context.

Wrapper classes also have their own methods which can be useful even outside of the wrapper classes themselves, there's a lot of them so if you are curious just google "javadoc {wrapper class name} class"

Stacks

Abstract data structure where things are stored "on top" of eachother.

push() - add to top of stack

pop() - remove from the top of stack and return

peek() - see what's at the top without removing it

isEmpty() - returns true if empty, false if not

ArrayList

Abstract Data Structure which is very similar to an array, except that it is dynamically sizeable. We don't need to worry about its length.

ArrayList Class Methods: add, get, isEmpty, indexOf, remove, size

methodreturn valuefunction
add()booleanadds the parameter into the ArrayList, true if successful, false if fails.
get(int index)E (the type of the ArrayList)retrieve something, doesn't remove it from the list
isEmpty()booleanreturns true if empty, false if not
indexOf(Object o)intreturns the index when the Object is first found in the list
remove(int index)E (the type of the ArrayList)removes the Object at the index, and returns it.
size()intthe number of elements in the ArrayList

Comparable Interface (compareTo method)

Comarable is an interface which makes you implement compareTo(), this is utilized by a lot of the built in sorting libraries in Java. Such as Collections.sort().

How to implement Comparable:

//whatever is in the angled brackets (<>) is what the parameter for compareTo is
public class Foo implements Comparable<Foo>{
  
 public int compareTo(Foo bar){
   /*
    * code here decides how these are compared, and returns an int 
    * representing the value of the comparison
    */
 }
}

Sorting Data Using Collections.sort()

If you have a collection (array, ArrayList, etc,.) of Objects which implement Comparable, you can sort them. The class/structure it is stored in does not have to implement Comparable, but the objects inside do, in order to be compared to one another.

If it does not implement Comparable, you cannot use Collections.sort

Polymorphism

⚠️ 1 of the 4 pillars of Object Oriented Programming

What is it?

Polymorphism is the idea that something can have many forms, it may sound abstract, and that's because it is, it can be applied to a lot of things in Java. See late binding's example to view how code can produce and call different methods depending on the input, because of inheritance and late binding.

Also, very related to the idea of inheritance or implementing, if you have some class Person where Worker, Student, Mother all extend Person. Any code which works for Person, also works for things which extend Person. And even things which extend classes like Worker, Student or Mother

Method Overloading/Overriding

Overloading

the idea that Java can differentiate between methods with the same name, but different parameters.

ex:

public int someMethod(int x){ 
  ...
} 

public String someMethod(int x, String someString){
  ...
}

Java knows which of the two the user is expecting to use based on the arguments they pass in when calling the method, the return type is not relevant for overloading. This is usually done within the same class.

Overriding

When you replace some inherited method with your own, more specialized version of it.

Iterators

  1. .next() - gives you the next element, and advances the iterator, throws an exception if there is no next element.
  2. .hasNext() - true if there is, false if not

ex:

ArrayList<String> names = new ArrayList<>();

names.add("John");
names.add("Mary");
names.add("Rose");

Iterator<String> iter = names.iterator();

while(iter.hasNext()){
   String value = iter.next();
   System.out.println(value);
}

the programmer doesnt have to worry about iterating through the list

getClass(), instanceof

getClass()

instanceof

Recursion

The idea that a piece of code can call itself and run on itself.

ex:

public static int factorial(int num) {
        if(num == 0) {
            return 1;
        }else {
            return num * factorial(num - 1);
        }
    }

the if(num -- 0) is the base case, or when your code is supposed to stop.

the else block is when the call is made to the function, passing in the value below the current integer, this is important because otherwise it would be an infinite loop of calling the method.

if you want more coverage on this, check the final exam info page, schedule for recursion practices, or this piazza post.

Integer.parseInt(), Double.parseDouble()

Integer.parseInt(String s) - takes some string s and parses it into an integer. Double.parseDouble(String s) - takes some string s and parses (processes) it into a double.

Inheritance (extending other classes using "extends")

⚠️ 1 of the 4 pillars of Object Oriented Programming

inheritance is when the derived or child class extends from parent, base, or super class (these all mean the same thing )

Person p = new Student() but don't do Student p = new Person()

class hierarchy and Object class

Early and late binding

lets say that you have this code

Faculty carol = new Faculty("Carol Huffteacher", 
                           "999-99-9999", 1995);
Person p = carol;
System.out.println(carol.toString());

//which toString will be called?

the answer depends on the language you are writing code on, obviously in this class we are dealing with Java

Early (Static) binding - p is type Person, therefore it should call the Person's class toString()

Late (Dynamic) binding - p refers to an Object of type Faculty so it should call the toString() of that class.

why do the creators of these languages choose between these two?

java uses late binding by default but it can be changed.

other languages like C++ use early binding (by default), for our example that means the Faculty.toString() is what is called.

polymorphism as it pertains to binding

ex:


Person[] list = new Person[3];
list[0] = new Person("Col. Mustard", "000-00-0000");
list[1] = new Student("Ms. Scarlet", "111-11-1111", 1998, 3.2);
list[2] = new Faculty("Prof. Plum", "222-22-2222", 1981);

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

/*the toString() that gets printed is from that Object's class
*so the student will have their gpa printed out
*/

polymorphism (here) is the idea that the output of the code has many forms as in, it will output different things depending on the input. if it was early binding, you wouldn't print out the gpa or birthyear/admission for Student and Faculty

Terms You Need to Know

Current Object

Refers to "this", the object which is calling a non-static method.

Index and Indices

Index refers to the way elements in an array or other structure are numbered. 0-based indexing is usually the assumed default, meaning counting starts at 0 and not 1. Indicies refers to multiple index.

Instance of a Class

An instance is derived from a Class, it's not a type or anything, it's vocabulary for saying like Object from a class.

Instance Variables

These are the same as fields, instance fields, these all refer to variables that belong to an instance/object of a class. They are unique to each instance of a class and do not conflict.

Auxiliary / Helper Method

Auxiliary Method

this is very commonly used when you want to expand the parameters in a recursive function. Imagine you are writing a method to reverse a String recursively, and you have a method header like public static String reverseString(String s), in order for this to work recursively, you CAN have an auxiliary method which keeps track of the index. (For this problem you can just use String.subString(), auxiliary methods aren't required). The method header would look something like this private static String reverseString(String s, int index), having it private is important because we don't want users calling it, only the public method.

Helper Method

This is when you want to put some part of the work in a different method, either to avoid code duplication accross methods or to have a cleaner method body.