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.
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.
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.
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
| Operator | Function |
|---|---|
+ | Adds |
- | Subtracts |
/ | Divides |
* | Multiplies |
% | Modulus, remainder |
Division truncates (gets rid of) the decimal if both operands are integers
+ - concatenates two strings
&& - 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
| Operator | Function |
|---|---|
= | 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 |
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
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.
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
| character | function |
|---|---|
\n | new line |
\t | tab |
\\ | \ |
\" | double quotes " |
\' | single quote ' |
| symbol | meaning |
|---|---|
> | greater than |
< | less than |
>= | greater than or equal to |
<= | less than or equal to |
== | equal to |
!= | not equal to |
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.
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.
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.
pretest means the condition is checked before any iteration is done
posttest means the condition is checked after any iteration is done
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.
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 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);
}
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.
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.
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();
| method | returns | purpose |
|---|---|---|
String.toLowerCase() | String | the all lower case version of the String it is called upon |
String.toUpperCase() | String | the all upper case version of the String it is called upon |
String.length() | int | how many characters (including spaces) are in the String |
String.charAt(int index) | char | gets the character at the specified index in the String, 0-based indexing. |
String.subString(int start) | String | when it is just 1 int, it returns from that index start (inclusive) until the end of the String |
String.subString(int start, int end) | String | similar to one-parameter subString() but the char at index end is not included |
String.isEmpty() | boolean | returns true if String.length() is 0 |
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.
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
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
}
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 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.
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.
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.
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
}
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.
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-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.
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 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.
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 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);
}
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.
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"}}
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).
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.
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.
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,.))
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
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
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"
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
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.
| method | return value | function |
|---|---|---|
add() | boolean | adds 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() | boolean | returns true if empty, false if not |
indexOf(Object o) | int | returns 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() | int | the number of elements in the ArrayList |
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
*/
}
}
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 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
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.
When you replace some inherited method with your own, more specialized version of it.
.iterator() method on classes which implement Iterable, it should return an Iterator objectIterator object allows us to go through the list..next() - gives you the next element, and advances the iterator, throws an exception if there is no next element..hasNext() - true if there is, false if notex:
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
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(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 is when the derived or child class extends from parent, base, or super class (these all mean the same thing )
public class B extends A{...}super() to call the parent/super class's constructorsuper() constructorsuper to call methods from the base class, such as super.toString()Person p = new Student() but don't do Student p = new Person()
Object type, so we could compare it to any Object.Object parameter is powerful as any class can be input there.@Override tells the compiler that it should expect to be overriding a method from some parent classObject.getClass() is a final method, so we are not allowed to overwrite it for any classlets 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.
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
Refers to "this", the object which is calling a non-static method.
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.
An instance is derived from a Class, it's not a type or anything, it's vocabulary for saying like Object from a class.
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.
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.
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.