COMP 215 Test 2

Lakukan tugas rumah & ujian kamu dengan baik sekarang menggunakan Quizwiz!

6.M: I/O To ensure that any file I/O code that you write will work on any operating system, what character should you use to separate directories in a path?

'/', not '\'. You should use the forward slash, '/', to separate directories in a path, because although Windows uses the backslash '\', it will automatically convert all forward slashes to backslashes, while unix-based operating systems like Mac and Linux will not compile with backslashes. Forward slashes are always safe to use to separate directories. Additionally, File.separator and File.separatorchar will work on any OS.

5.M: Type Inference + Type Erasure & Invariance + Generics III What limitations does type erasure impose?

()As a result of type erasure, type parameters are not known at runtime! This uncertainty limits: 1. What we can do when defining generic types & methods 2. What we can do when using parameterized types Limitations on defining generic types & methods: - Cannot use a type parameter T to construct an object of type T (ex: public static <T> T constructObject() { return new T();} disallowed) - Cannot use a type parameter T to construct an array of type T[] (ex: public static <T> T[] constructArray() { return new T[10];} disallowed) - Cannot declare static fields using type parameters (ex: public class GenericClass<T> { private static T genericField; ...} disallowed) -- since static fields are shared across all instances of the class, but different instances may have different values of T, so it's unclear what genericField's type should be here Limitations on using parameterized types: - Cannot use instanceof to check parameterized types (ex: cannot say List<Integer> myList = new ArrayList<Integer>();boolean isIntList = myList instanceof List<Integer>;, can say List<Integer> myList = new ArrayList<Integer>();boolean isList = myList instanceof List;) - One more limitation: invariance (see question on invariance)

5.M: Type Inference + Type Erasure & Invariance + Generics III How do we improve LVTI readability?

- Use descriptive comments - Choose good variable and method names, ex: var numStudents = getNumStudents(); - Use type designators on numeric types (even when optional), ex: var num = 5D;

5.W: The Call Stack + Recursion + Errors What happens behind the scenes of a function call within another function?

1. Caller's context (value of variables + call site) gets saved 2. Callee's parameter values get initialized 3. Control flow is transferred to the callee 4. Callee's body executes 5. Caller's context gets restored 6. Caller's return value gets assigned (as appropriate) 7. Control flow is transferred to the caller Each function's context get stored in a frame on the call stack When a function gets called, the caller's context gets stored & control flow is transferred to the callee When a function returns, its context is popped off the stack and control flow is transferred to the caller

5.F: Exceptions What are the two ways in which exceptions can be handled in Java? Which keywords correspond to each approach?

1. Catch: - A function can catch an exception and handle it appropriately 2. Throw: - A function can throw an exception to its caller Either throwing or catching counts as handling the exception. Try & catch keywords: - Used to catch an exception and handle it Throw & throws keywords: - Used to throw an exception

5.W: The Call Stack + Recursion + Errors What are the three types of errors in Java, and what is an example of each?

1. Compile-time errors = errors that are detected by the compiler - Compilers detect violations of the grammar & type system rules - Error messages include line no. + column no. + description - Ex: - int x = 5 (no semicolon) - y = 5; (no type declared) 2. Runtime errors = errors that are detected by the JVM - Errors that prohibit the program's execution from proceeding, but cannot be detected until runtime - This includes errors that depend on external state - - e.g.: Error only occurs for certain inputs to the program - Program reads file; error only occurs if the file is missing or malformed - Error messages include error type + inputs + stack trace - Ex: passing a String argument to parseInt(), which only works with numeric inputs - Outputs: - Exception in thread "main" Error: java.lang.NumberFormatException: For input string: "hello" Stack trace: at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.base/java.lang.Integer.parseInt(Integer.java:652) at java.base/java.lang.Integer.parseInt(Integer.java:770) at main.rice.Helper.doSomething(Helper.java:7) at main.rice.Main.main(Main.java:33) 3. Logical errors = not detected by the compiler or the JVM, but cause the program to do the wrong thing - Ex: using the wrong conditional statements such that the it still compiles and outputs a value but it's not the value you expected

6.M: I/O What are the three key players of I/O in Java, and what are the roles of each?

1. Data source/destination: - Where data is read from/written 2. Stream: - A sequence of data that flows between the program & the data source/destination 3. Reader/Writer: - Optional abstractions that wrap around streams - Readers wrap around input streams - Writers wrap around output streams

4.F: Maps + Sets + Constructing Containers + Generics II What are the three primary means in Java of constructing containers?

1. Default constructors (zero args) -- create an empty container: List<Integer> myList = new ArrayList<Integer(); Set<Integer> mySet = new HashSet<Integer>(); Map<Integer, String> myMap = new HashMap<Integer, String>(); 2. Copy constructors: create a shallow copy of a given container List<List<Integer>> myList = new ArrayList<List<Integer>>(); List<Integer> innerList = new ArrayList<Integer>(); myList.add(innerList); List<List<Integer>> myList2 = new ArrayList<List<Integer>>(myList); myList2.get(0).add(1); Copy constructors can also be used to convert from one subclass of Collection (e.g. List or Set, but not Map) to another: List<Integer> myList = new ArrayList<Integer>(); myList.add(1); myList.add(-7); Set<Integer> mySet = new HashSet<Integer>(myList); When converting from List → Set, duplicates are removedwhen converting from Set → List, order is undefined 3. Static "factory" methods - quick means of building a new non-empty container: List<Integer> myList = List.of(1, 3, 5, 7, 9); Map<Integer, String> myMap = Map.of(1, "a", 2, "b", 3, "c"); Set<Boolean> mySet = Set.of(True, False);

6.W: The Command Line + Processes Consider this program: public class Greeter { public static void main(String[] args) { System.out.println("hi " + args[0]); } } 1. How do we compile and directly run this program within IntelliJ? 2. How do we compile and directly run this program from the command line? 3. How do we compile the program (either through IntelliJ or the command line) and use processes to run it indirectly?

1. How do we compile and directly run this program within IntelliJ? - Use IntelliJ's "Edit Configuration" 2. How do we compile and directly run this program from the command line? - javac Greeter.java --> compiles using Java compiler - java Greeter friend! --> runs on JVM; runs Greeter.main() with args = ["friend"], but the array containing both ["Greeter", "friend"] are passed to the JVM itself, but when the JVM executes, it peels off arguments after the class name to pass to Greeter's main method 3. How do we compile the program (either through IntelliJ or the command line) and use processes to run it indirectly? i. Define a separate class, Main ii. Create an instance of the ProcessBuilder class within the main method in Main iii. Create a command that will execute the Greeter program, which is an array of strings in which the first element is the name of the class we want to execute, and the remaining elements are the arguments we want to pass to the program. iv. Call command method on the ProcessBuilder object, passing in the String[] command. v. Start the process by calling the start method on the ProcessBuilder object. public class Main { public static void main(String[] args) { ProcessBuilder pb = new ProcessBuilder(); String[] cmd = new String[]{"java", "Greeter", "friend!"} pb.command(cmd); Process proc = pb.start(); } } Equivalent to running command "java Greeter friend" at the command line. This assumes that the Greeter class has already been compiled, and that the resultant file can be found within the JVM's class path.

6.F: Debugging + The IntelliJ Debugger Sample problem: 1 public class Main { 2 3 public static void main(String[] args) { 4 int mySum = computeSum(args); 5 System.out.println(mySum); 6 } 7 8 public static int computeSum(String[] myArray) { 9 int mySum = 0; 10 int[] intArray = createIntArray(myArray); 11 for (int i: intArray) { 12 mySum += i; 13 } 14 return mySum; 15 } 1617 public static int[] createIntArray(String[] myArray) {18 int[] intArray = new int[myArray.length]; 19 for (int i = 0; i < myArray.length; i++) {20 intArray[i] = Integer.parseInt(myArray[i]); 21 } 22 return intArray; 23 } 24} 1. If you press "step out" while stopped at line 10, to what line would execution proceed next? 2. If you instead press "step into" while stopped at line 10, to what line would execution proceed next? 3. If you instead press "step over" while stopped at line 10, to what line would execution proceed next?

1. If you press "step out" while stopped at line 10, to what line would execution proceed next? - Line 4, since that is the callsite of function computeSum, which will execute in its entirely after "step out" is pressed. 2. If you instead press "step into" while stopped at line 10, to what line would execution proceed next? - Line 17, since it's the first line of the function called in line 10. 3. If you instead press "step over" while stopped at line 10, to what line would execution proceed next? - Line 11.

5.W: The Call Stack + Recursion + Errors What are the rules of recursion?

1. Must have cases covering all valid inputs - Common pitfall: base case is too large → smaller inputs inadvertently fall into recursive case → crash or infinite recursion 2. Must have one or more base cases - Common pitfall: supposed "base case" makes recursive call → infinite recursion 3. Input to each recursive call must be smaller - Common pitfall: input is not smaller → infinite recursion

4.F: Maps + Sets + Constructing Containers + Generics II What are the three limitations of using static factory methods to build non-empty containers?

1. Resultant containers are immutable; calling add(), put(), etc. will result in an UnsupportedOperationException 2. Cannot have null elements 3. Map.of only supports up to ten (key, value) pairs

6.M: I/O How are File objects (which include both files and directories) constructed in Java, and what operations can be performed on them?

A File object can be constructed with a String path: - e.g.: File f = new File("/home/rebecca/comp215/IO_lecture.pdf"); Can check to see if the file/directory exists: - boolean exists() -> returns true if there's a file/dir at the path If no file/directory exists, can create one: - boolean createNewFile() -> creates file; parent dirs must exist - boolean mkdir() -> creates directory; parent dirs must exist - boolean mkdirs() -> creates dir. + any parent dirs that don't exist Can delete the File, or effectively move it by renaming it: - boolean delete() - deletes the file; if a directory, must be empty - boolean renameTo(File dest) - renames the file; may fail if there's already a file at dest's path Can check if the File is a normal file or a directory - boolean isFile() - boolean isDirectory() If the File is a directory, can get an array of all files inside of it: - String[] list() -> returns an array of the names of all files and subdirectories within the directory - File[] listFiles() -> returns an array of File objects representing all files and subdirectories within the directories Both return null if the File's path does not point to a directory

6.W: The Command Line + Processes What are the Process and ProcessBuilder classes in java.lang?

A Java program executes in the context of a process A Java program can also ask the OS to create a new process and run a different program within that process Java provides support for this in the java.lang package - java.lang.Process: representation of a single process - java.lang.ProcessBuilder: helper class for creating processes Process and ProcessBuilder classes can: - Start a process, wait for it to finish, and capture its output - Run any program, not just another Java program (i.e. Python programs, such as in FEAT)

6.F: Debugging + The IntelliJ Debugger What is the purpose of setting a breakpoint in your code? How do you set a breakpoint in your code?

A breakpoint allows you to see the intermediate program state at a point that you set, as you can inspect with the debugger and program execution is paused. You can set a breakpoint by clicking on the left margin next to the desired line.

6.M: I/O What is the role of a device driver? How does it fit within the context of the hardware + software stack?

A device driver is a kind of software for each I/O device that communicates with I/O devices, typically through raw bytes. Hardware + software stack: From top to bottom: - Application - Operating system (at the bottom is device driver) - I/O Devices The application interacts with high-level abstractions for I/O provided by OS + PL libraries. The OS uses device drivers to communicate w/ I/O devices. PL libraries enable applications to ask the OS to communicate with I/O devices (through device drivers), and help with converting to/from raw bytes. Programming languages such as Java include libraries for interfacing with the file system at a high level of abstraction Applications don't need to worry about: - How the file system is represented - How the hardware works (whether it's an HDD or SSD, details about sectors/tracks or blocks/cells, etc.)

6.M: I/O How are I/O exceptions dealt with in Java? What can we assume for this class?

A large number of the I/O-related methods that we're about to discuss are declared to throw some kind of exception: - Typically an IOException (including subclasses such as FileNotFoundException) or SecurityException If you call a function that throws a checked exception, IntelliJ will remind you to handle it - For this class, you can treat all I/O-related exceptions as unrecoverable → no need to catch, just declare as thrown

6.W: The Command Line + Processes What is the relationship between a program and a process? What are the three things that every process "believes" about itself?

A process is an encapsulation of a program that is running. Every process believes that it is the sole process, it has full access to memory and CPU resources, and all of its operations happen one after another. However, a program is an application that sends a request to the operating system to start the application. A program is mostly a set of instructions on a file, while a process actually runs and requires resources. A program is executed within the context of a process. Three things every process believes about itself: 1. It's the only process 2. It has exclusive access to resources such as memory and CPU 3. All of its operations get executed back-to-back

6.M: I/O What is the difference between an absolute path and a relative path?

Absolute path: - Full list of directories that make up the path - Relative to the file system's root Relative path: - Part of the list of directories that make up the path - Relative to its current working directory instead of system's root

4.F: Maps + Sets + Constructing Containers + Generics II What are the advantages of generic classes as compared to generic methods? Describe one situation in which a generic classes would be the better design.

Advantages of generic classes compared to generic methods: 1. Better consistency & type safety -- we can use these same type parameters throughout the class in its fields and methods. Static type-checking helps in code debugging, since it checks that the correct argument types are passed into corresponding parameter types, and checks that return value is being assigned to the correct type. If the types are not the same, the code won't compile, allowing us to find type errors much more easily. Ex: public class Map<K, V> { private List<K> keys = new ArrayList<K>(); private List<V> values = new ArrayList<V>(); ... public void put(K key, V value) { this.keys.add(key); this.values.add(value); } public V get(K key) { int index = this.keys.indexOf(key); return (index == -1) ? (null) : (this.values.get(index)); } } Map<Integer, String> myMap = new Map<Integer, String>(); myMap.put(1, "a"); arg types match parameter types, so this will compile! (Integer key, String value) myMap.put("b", 2); arg types don't match parameter types; this won't compile! String str1 = myMap.get(1); arg type matches parameter type (Integer key) and return value is assigned to the correct type String str2 = myMap.get(true); arg type doesn't match parameter type; this won't compile! Integer int1 = myMap.get(1); return value is assigned to the wrong type; this won't compile! If this had been a non-generic class: public class Map { private List<Object> keys = new ArrayList<Object>(); private List<Object> values = new ArrayList<Object>(); ... public <K, V> void put(K key, V value) { this.keys.add(key); this.values.add(value); } public <K, V> V get(K key) { int index = this.keys.indexOf(key); return (index == -1) ? (null) : ((V) this.values.get(index)); }} We can't know what types of objects will be stored as keys and values. No guarantee that the K, V in one method are the same as the K, V in another method. W would need to cast the values that we get out of this.values in get(). Ex: Map myMap = new Map(); myMap.put(1, "a"); myMap.put("b", 2); String str1 = myMap.get(1); String str2 = myMap.get("b"); Integer int1 = myMap.get(1); String str3 = myMap.get(true); The compiler won't catch that the value associated with the key "b" is an integer, not a String, so there will be a runtime ClassCastException. 2. Generic types are a good fit for when you want a consistent type across multiple methods that share state

4.F: Maps + Sets + Constructing Containers + Generics II What are the advantages of generic methods as compared to generic classes? Describe one situation in which a generic method would be the better design.

Advantages of generic methods compared to generic classes: 1. Enhanced flexibility -- in the example, Copier was a non-generic class, but copyList was a generic method. This means we only have to construct one Copier object, and use the same object to copy both an ArrayList<Integer> and an ArrayList<String>. If the Copier class itself was generic, we would have to instantiate a different Copier object for each kind of list we wanted to copy (since type has to remain the same throughout the class). 2. Good for pure functions -- generic methods are good fit for pure functions (functions that don't rely on external state). This implies that generic methods typically can & should be declared static. It's useful for when we only want part of it to be generic, not caring as much about type consistency across the class.

6.F: Debugging + The IntelliJ Debugger Explain what a divide-and-conquer approach to debugging would look like.

An initial method is divided into smaller segments, which are themselves divided into smaller segments until the bug is found. You should pick a point in the middle of the code and assess whether the bug is likely to be in the code segment above it. Once you narrow down which area the bug is likely to be in, pick another point between point 1 and the end of the code, and continue the process. Once we identify which segment of code is producing an unexpected output, we will look there first for the bug.

4.W: Type Parameterization + Generics I + Lists What are the three main implementations of the List interface?

ArrayList - underlying representation is an array. LinkedList - underlying representation is a doubly-linked list. Vector - underlying representation is also an array; has special protections that make it well-suited to parallel programming.

5.M: Type Inference + Type Erasure & Invariance + Generics III What is a bounded type parameter? What is an upper-bounded type parameter?

Bounded type parameter: Limit the values (types) that can be passed to a given type parameter Upper-bounded type parameter: Defines an upper bound on the type that can be passed to a given type parameter (upper describes that that type is the most generic type that can be defined, and any type passed must be of that type or a subclass of it) Ex: public class SummableList<T extends Number> extends ArrayList<T> { public double computeSum() { double sum = 0.0; for (T elem : this) { sum += elem.doubleValue(); } return sum; } } Number is the upper bound on T; T must be some kind of Number. This passes compile-timetype checking, since T is guaranteed to be some kind of Number

6.M: I/O What is the difference between buffered and unbuffered I/O? What is the primary advantage of buffered I/O?

BufferedReader and BufferedWriter are of type Reader and Writer, respectively. They are wrappers around other Readers/Writers, which must be passed at construction; provide additional operations such as readLine(). In unbuffered I/O: - Each read/write goes to the OS, triggering a potentially slow hardware access In buffered I/O: - A buffer (such as an array) serves as an intermediary between the OS and the Reader/Writer Primary advantage of buffered I/O: - Allowing multiple read/writes to be combined into one hardware access

6.M: I/O How can we read and write to a File object in Java?

Cannot directly read from or write to a File object! For reading, two options: 1. Read the entire contents into a single String: - String contents = Files.readString(Paths.get(path)); 2. Use a Reader to perform more granular operations For writing: Use a Writer!

5.F: Exceptions In what order should the catch blocks of a series of try-catch blocks appear in?

Catch blocks should be ordered from more to less specific, since only the first catch block whose type matches the thrown type gets executed. We want the most exact match as possible.

6.W: The Command Line + Processes How do you run your program from the command line?

Command: Path to program + arguments (a series of Strings) Ex: Class Main.java: public class Main { public static void main (String[] args) { System.out.println(args[0]); } } Commands to run program: javac Main.java <-- compiles Main.java, generating Main.class java Main hello <-- runs Main.main() with args = ["hello"], printing "hello"

5.M: Type Inference + Type Erasure & Invariance + Generics III Define each of the following terms: - Covariance - Contravariance - Invariance

Covariance: possible to use a type more specific than specified Contravariance: possible to use a type less specific than specified Invariance: have to use the exact type specified Ex: Sub is a subtype Supr - "Normal" objects are covariant, i.e. can do Supr x = new Sub(); - Arrays are covariant, i.e. can do Supr[] x = new Sub[1]; - Type parameters, by default*, are invariant! Super/subtype relationships of the "outer" generic type, assuming identical type parameters, exhibit covariance, e.g.: ArrayList<E> implements List<E>, so an ArrayList<Integer> can be assigned to a variable of type List<Integer> In the typical case*, generic classes do not preserve covariant super/subtype relationships of the type parameter: Imagine that Sub is a subtype of Supr: An ArrayList<Sub> cannot be assigned to a variable of type of ArrayList<Supr>

5.W: The Call Stack + Recursion + Errors What is the difference between the callee and the caller of a function?

Every function call involves two entities: Callee: - The function being called Caller: - The function calling the callee Ex: public class SampleClass { public int f(int x) { int y = 10; 4 int z = this.g(x + y); ... } private int g(int x) { ... } } f is the caller, g is the callee.

5.W: The Call Stack + Recursion + Errors What is an example of recursion being more elegant than iteration?

Ex: Fibonacci numbers Iteration: public static Integer fibonacci(int n) { if (n < 0) { return null; } else if (n < 2) { return n; } int minus2 = 0; int minus1 = 1; int curr = 1; for (int i = 2; i <= n; i++) { curr = minus2 + minus1; minus2 = minus1; minus1 = curr; } return curr;} Recursion: public static Integer fibonacciRec(int n) { if (n < 0) { return null; } else if (n < 2) { return n; } return fibonacciRec(n - 1) + fibonacciRec(n - 2); }

6.W: The Command Line + Processes How do we capture a process's output in Java?

Ex: public static void main(String[] args) throws IOException, InterruptedException { // start the process as before ProcessBuilder pb = new ProcessBuilder(); String[] cmd = new String[]{"python3", "/home/rebecca/greeter.py", "friend!"} pb.command(cmd); Process proc = pb.start(); // create a reader to read process's output -- need to wrap new InputStreamReader around a BufferedReader, and argument to InputStreamReader is proc's input stream, so proc.getInputStream // proc.getInputStream allows you to read bytes from proc's stdout -- is input because we're reading the input and treating it as our output // InputStreamReader() converts stream of bytes to characters // BufferedReader gives ability to read line by line BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream())); // wait for process to run to completion proc.waitFor(); // read proc's output // create StringBuilder object String line; StringBuilder sb = new StringBuilder(); // while the line being read exists, append the line and create a new line while ((line = reader.readLine()) != null) { sb.append(line).append("\n") } // close the reader reader.close(); ... } Can also do: BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getErrorStream())); - To read from the stderr stream instead of stdout

4.F: Maps + Sets + Constructing Containers + Generics II How are generic methods created?

Example with one type parameter: public class Copier { public <T> List<T> copyList(List<T> listToCopy) { List<T> newList = new ArrayList<T>(); for (T elem : listToCopy) { newList.add(elem); } return newList; } } Type parameters go in angle brackets directly before the return type. Can use T throughout the method, e.g. as the return type, parameter types, local variable types. Example with two type parameters: public class Copier { public <K, V> Map<K, V> copyMap(Map<K, V> mapToCopy) { Map<K, V> newMap = new HashMap<K, V>(); for (Map.Entry<K, V> entry : mapToCopy.entrySet()) { newMap.put(entry.getKey(), entry.getValue()); } return newMap; } } K represents key type, V represents value type

6.M: I/O What is the role of a file system?

File systems are provided by the OS as an abstraction, imposing structure on the data in storage by: - Dividing the space on the HDD/SSD into blocks - Maintaining an indexing system for organizing files From the user's perspective, each file has a unique path with a hierarchical structure

6.M: I/O What are unbuffered file readers and writers in Java, and how are they used?

FileReader is a type of InputStreamReader, which is a type of Reader FileWriter is a type of OutputStreamWriter, which is a type of Writer Both are specifically designed for interfacing with Files. FileReader constructors include: - FileReader(File file) - FileReader(String path) - These fail if the file does not exist FileWriter constructors include: - FileWriter(File file) - FileWriter(String path) - Create a new file at the path; obliterate any existing contents - FileWriter(File file, boolean append) - FileWriter(String path, boolean append) - If append=true, open the existing file in append mode (fail if it does not exist)

5.W: The Call Stack + Recursion + Errors What are the frame, call stack, and call trees of function call(s)?

Frame: - A structure of the call stack which stores a function's context - One frame corresponds to a single in-progress function call Call trees: - Depict the complete history of all function calls Call Stack: - A structure that stores the state of each function call being executed at a moment in time, in a Last-In, First-Out order (is a true stack) Ex: public class SampleClass { public static void main(String[] args) { int x = Integer.parseInt(args[0]); int y = f(x) + 1; } private static int f(int x) { int y = Math.abs(x); return g(y + 3); } 1private static int g(int x) { 1return (x * 2); } } Initial stack: Func name: mainCall site: <none> Local variables: args → ["-4"] Then stack becomes: Func name: Integer.parseIntCall site: line 3 in SampleClass Local variables: ... Func name: mainCall site: <none> Local variables: args → ["-4"] Then parseIntCall() context popped off, caller's context restored, stack becomes: Func name: mainCall site: <none> Local variables: args → ["-4"] Caller's return value assigned: Func name: mainCall site: <none> Local variables: args → ["-4"] x → -4 And so on w/ the other function calls (look at call stack slides for more details)

4.W: Type Parameterization + Generics I + Lists Scope of type parameters

Generic classes / interfaces -- defined in class / interface header, scope is the entire class / interface and all its fields and methods. Generic methods -- generic types defined in method header, type parameters only have scope within that method.

4.F: Maps + Sets + Constructing Containers + Generics II How are generic methods called? What is a type witness?

Generic methods can be called with an explicit type witness in <>: Ex: Calling the copyList method: Copier c = new Copier(); List<Integer> myList = List.of(1, 2, 3); List<Integer> myList2 = c.<Integer>copyList(myList); Integer is the type witness in this case. However, most of the time, the compiler can infer the type being passed to the type parameters from context: Ex: Copier c = new Copier(); List<Integer> myList = List.of(1, 2, 3); List<Integer> myList2 = c.copyList(myList); The compiler knows that myList is of type List<Integer>. Since there is only one copyList method in Copier, and it takes a List<T>, the compiler infers that T's value is Integer for this invocation of copyList.

5.M: Type Inference + Type Erasure & Invariance + Generics III What is generic type inference? What are its pros and cons?

Generic type inference is used for variables whose types are parameterized, e.g. List<Integer>. - Declaring & defining generic types typically has redundancy: List<String> myList = new ArrayList<String>(); List<List<String>> myList2 = new ArrayList<List<String>>(); - Compiler can infer the righthand side from the lefthand side! List<String> myList = new ArrayList<>(); List<List<String>> myList2 = new ArrayList<>(); The <> is referred to as the diamond operator. Pros of GTI: - Code is faster to write - Code is more adaptable to changes, in interfaces and locally - Code is arguably more readable (shorter, but without any loss of information) if declaration & construction are in the same line Cons of GTI: - Code is less readable if declaration & construction statements are not located near each other Use whenever declaration & construction are near each other.

4.W: Type Parameterization + Generics I + Lists Generic types vs. type parameters vs. parameterized types

Generic type: A type that can take in another type as its parameter, used to define generic classes and methods; ex: List<E>. Type parameter: A variable that has a type as its value, making a generic class / interface / method more specific -- can only take in object types; ex: E in List<E>. Parameterized type: A generic type that's given a specific type for its type parameter, used to declare variables, define non-generic types, construct objects; ex: List<String>

4.F: Maps + Sets + Constructing Containers + Generics II What are the two main implementations of the Map interface?

HashMap - underlying implementation is a hash table TreeMap - underlying implementation is a red-black tree

6.W: The Command Line + Processes How is a compiled program made standalone executable?

If you want your compiled program to be a standalone executable, you must define a function with this exact header to serve as the "entry point": public class Main { public static void main (String[] args) { System.out.println("hello world!"); } }

6.M: I/O What is the difference between input and output? Give one example of each of the following: - An I/O device that's used for input only - An I/O device that's used for output only - An I/O device that's used for both input and output

Input: Computer receives (reads) data from outside world Output: Computer transmits (writes) data to outside world An I/O device that's used for input only: - Microphone An I/O device that's used for output only: - Screen An I/O device that's used for both input and output: - - Hard disk

6.M: I/O Label each of the following streams with whether it's used for reading or writing: - InputStream - OutputStream What are the three built-in streams for interacting with the console in Java?

InputStream: - Reading data (only flows in one direction) - int read() OutputStream: - Writing data (only flows in one direction, void write(int b)) - void write(intb) Both of these are Streams of bytes. Three built-in streams for console interaction: - System.in = standard input (an InputStream) - System.out = standard output (a PrintStream, which is a subtype of OutputStream) - System.err = standard error (also a PrintStream)

6.W: The Command Line + Processes Under what circumstances might it be necessary to run a program using Java's ProcessBuilder class rather than simply making a function call?

Java's ProcessBuilder class is necessary when running a non-Java process from within Java, such as running a Python process from within Java or any program that you don't have the source code for..

5.M: Type Inference + Type Erasure & Invariance + Generics III How can generic type inference be combined with LVTI? What are potential problems with this?

LVTI and generic type inference can be combined, like so: var myList = new ArrayList<> Potential problems: - var infers the type from the righthand side; finds ArrayList, but no type parameter - <> infers the type from the lefthand side; no type is found Result: myList is of type ArrayList<Object>. While these two can be combined, but they typically shouldn't.

4.W: Type Parameterization + Generics I + Lists What is a List, and what are the main operations that can be performed on a List?

List: An ordered collection of elements. List<E>: An ordered collection of elements of type E. Operations: add - adds the given element to the List set - places the given element at the given position in the List get - returns the element at a given index indexOf - returns the index of the given element contains - returns true if the given element is in the List size - returns the size (number of elements) of the List remove - deletes a given element or the element at a given index Operations on List<E>: boolean add(E elem) - adds elem to the end of the List void get(int idx) - returns the element at index idx in the List int indexOf(E elem) - returns the index of elem in the List int size() - returns the size of the List boolean remove(Object obj) - removes obj from the List

5.M: Type Inference + Type Erasure & Invariance + Generics III What is accomplished by using bounded wildcards? What is the difference between upper-bounded and lower-bounded wildcards?

List<?> tells the compiler that there are constraints on the type(s) it contains, but those constraints are unknown. We can clarify those constraints by using bounded wildcards. Upper-bounded wildcard: - Upper bound: <? extends Type> - e.g. <? extends Number> can take Number, Integer, Double, ... - Anything that is of type Type, or a subclass of Type is acceptable (lower than Type in the hierarchy) Lower-bounded wildcard: - Lower bound: <? super Type> - e.g. <? super Integer> can take Integer, Number, or Object - Anything that is of type Type, or the superclass of Type, or the superclass of its superclass, etc. (higher than Type in the hierarchy)

5.M: Type Inference + Type Erasure & Invariance + Generics III What are the key differences between declaring a List<?> vs. a List<Object>, both conceptually and in terms of how you can operate on the List? What are the new opportunities and constraints presented by a wildcard?

List<Object>: - Informs the compiler that this list's elements can be any type of objects - Invariant List<?>: - Informs the compiler that this list's elements are of an unknown type, but there are still constraints on the types it can contain, like homogeneity, though these constraints are unknown - Covariant Opportunities of wildcard: - Covariance - Can assign List<Integer>, List<String>, ... to List<?>, e.g.:List<?> myList = new ArrayList<Integer>(); Constraints of wildcard (to preserve type safety): 1. Can't but anything besides null into a Collection<?>. 2. Can't assume anything about the types you take out of a Collection<?> -- can't use wildcards in a return type. 3. Can't directly instantiate an object with a wildcard.

4.F: Maps + Sets + Constructing Containers + Generics II What is a Map<K, V> and what operations can be performed on them?

Map<K, V>: A collection of key, value pairs where keys are of type K and values are of type V. Operations on Map<K, V>: V put(K key, V value) - adds the (key, value) pair to the Map V get(Object key) - returns the value associated with key in Map int size() - returns the size of the Map V remove(Object key) - removes the key (and its corresponding value) from the Map boolean containsKey(Object key) - returns true if key is a key in the Map; false otherwise boolean containsValue(Object value) - returns true if value is a value in the Map; false otherwise

5.W: The Call Stack + Recursion + Errors What is memoization, and under what circumstances is it a good design decision?

Memoization is the caching of the results of function calls that require doing the same computations over and over in overlapping subproblems, which can blow up exponentially, so if the function is called again with the same input, it returns the stored result and doesn't re-execute the function to get its value.

6.M: I/O What are the key differences between memory and storage?

Memory: Memory is short term, and contains the data in-use while programs are running. These contents are not retrievable whenever power is lost. Storage: However, storage is long-term, and contains the data stored in file organizations (which are themselves an abstraction). The contents are retrievable even if power is lost.

6.W: The Command Line + Processes How is the command line used to run programs?

Modern operating systems typically provide a graphical user interface (GUI) so that you can run programs with a single click This GUI is an abstraction; when you click on an icon to run a program, a command is sent to the OS telling it: - The path (location) of the program to be run - Which arguments to use as inputs to that program Alternative: using command line (shell): - Can directly specify the command to run using the command line (AKA shell) - A textual interface to the OS

4.W: Type Parameterization + Generics I + Lists Can non-generic types, generic types extend one another?

Non-generic types: Can extend both generic types and non-generic types. Generic types: Can extend both generic types and non-generic types.

5.M: Type Inference + Type Erasure & Invariance + Generics III What are the safety constraints of upper- and lower-bounded wildcards? How is this summarized in PECS?

PECS: Producer Extends, Consumer Super Producer: A collection that we're taking elements out of - Use upper bounds (extends) when you want a producer Consumer: A collection that we're putting elements into - Use lower bounds (super) when you want a consumer If you want both a consumer and a producer, you have to use a specific (non-wildcard) type. Constraints on upper-bounded wildcards: 1. Can't put anything besides null in a Collection<? extends Type> - e.g. List<? extends Number> could be a List<Integer>, List<Double>, etc. If it's a List<Integer>, we can't add Doubles; if it's a List<Double>, we can't add Integers; etc.We have no idea which it is, so it isn't safe to add an Integer or a Double or anything else besides null. We can't add supertypes either (e.g. Number, Object);imagine it's a List<Integer>; while an Integer is-a Number, a Number is-not-an Integer! 2. Can assume that the types of the objects you take out of a Collection<? extends Type> are of type Type (not more specific) - e.g. List<? extends Number> could be a List<Integer>, List<Double>, etc. We have no idea which it is, so we can't assign any elements we take out ofit to a variable of type Integer, Double, or other specific subclass of Number.But, we do know that all elements will either be Numbers or belong to some subclass ofthe upper-bound type (Number), so we can assign elements to variables of type Number. 3. Can't directly instantiate an object with <? extends Type> Constraints on lower-bounded wildcards: 1. Can put objects of type Type in a Collection<? super Type> - e.g. List<? super Integer> could be a List<Integer>, List<Number>, or List<Object> If it's a List<Integer>, we can't add Numbers or Objects, but we can add Integers;if it's a List<Number>, we can't add Objects, but we can add Numbers or Integers;if it's a List<Object> we can add Objects, Numbers, or Integers.In all possible cases it's safe to add objects of the lower-bound type (Integers)! 2. Can't assume anything about the types of the objects you take out of a Collection<? super Type> - e.g. List<? super Integer> could be a List<Integer>, List<Number>, or List<Object> We have no idea which of these it is; since in the "worst" case the elements could simply be Objects, we can't assume they're anything more specific than that. 3. Can't directly instantiate an object with <? super Type>

5.M: Type Inference + Type Erasure & Invariance + Generics III What is the optimal remedy to the second problem with the basic form of generics we've discussed thus far: 2. In constructing and using parameterized types: are generics not flexible enough? Why are the other options unreasonable?

Problem: - Traditional generics may not be flexible enough -- type parameters are invariant by default, can make it hard to write sufficiently flexible code - What if we won't know what type goes in our List until runtime? - What if we don't care what type goes in our List, but we want homogeneity (all elements of same type)? - Neither List<Integer> nor List<String> can be assigned to List<Object> due to invariance! Options: 1. Instantiating a new T every time: - The problem? Type erasure! T's value isn't known at runtime, so we can't directly call new T(), nor can we have, e.g., a switch statement based on T's value 2. Making a switch statement based on T's value - Imagine that the type of Object to be generated is an input to the program → won't know the type until runtime! 3. Change every List to a List<Object> - Works, but less type safe than it could be: List<Object> objs in main() can hold mixed types when it doesn't need to - Less self-documenting & less type safe: genInts() and genStrs() don't use as specific types as they could Solution: use a wildcard = a type-safe way of designating that a type parameter's value is unknown Ex: List<?> is a list of unknown type

5.M: Type Inference + Type Erasure & Invariance + Generics III What is the optimal remedy to the first problem with the basic form of generics we've discussed thus far: 1. In defining generic types & methods: are generics too flexible? Why are the other options unreasonable?

Problem: - Traditional generics too flexible: what if we want to use operations that are only available to a subset of the Object types? Options: - Three options: tough luck, give up type safety, or limit the values (types) that can be passed to a given type parameter - We can't just not have a solution (no to tough luck, ex: using T to return a double, but doubleValue() not defined for all object types, get runtime error "Cannot resolve method doubleValue() in T") - We can't give up type safety (ex: if we try to cast this element to a Number type that does have doubleValue() defined for it, it won't work because not all objects can be cast to Number type, will get error ClassCastException at runtime: Bunny can't be cast to Number if we try to cast a Bunny object to a Number type) - Therefore, we choose to limit the values / types that can be passed to a given type parameter Solution: use a bounded type parameter: Limit the values (types) that can be passed to a given type parameter

4.F: Maps + Sets + Constructing Containers + Generics II Sample problem: Consider the following piece of code, which has been annotated with line numbers for convenience. 1 List<Integer> list1 = List.of(1, 2, 1, 2, 1); 2 Set<Integer> set1 = new HashSet<Integer>(list1); 3 List<Set<Integer>> list2 = new ArrayList<Set<Integer>>(); 4 list2.add(set1); 5 List<Set<Integer>> list3 = new ArrayList<Set<Integer>>(list2); 6 list3.get(0).add(3); 7 list1.add(4); Will the above code execute without error? If there will be an error, indicate which line(s) is/are problematic and why. What will be the contents of each of the following containers after the above code has executed?

Problematic line(s): Line 7 will cause an error because list1 is constructed using a static factory method, so it is immutable. It will result in an UnsupportedOperationException when add() is called on it. Contents of containers: list1: [1, 2, 1, 2, 1] set1: {1, 2, 3} list2: [{1, 2, 3}] list3:[{1, 2, 3}]

5.M: Type Inference + Type Erasure & Invariance + Generics III What are the pros and cons of covariance vs. invariance?

Pros of covariance: - More flexible Cons of covariance: - Less safe (more prone to failing at runtime) Pros of invariance: - More safe (less prone to failing at runtime) Cons of invariance: - Less flexible

5.M: Type Inference + Type Erasure & Invariance + Generics III What are the pros and cons of using explicit declaration vs. LVTI?

Pros of explicit declaration: - Code is more "self-documenting" - Can be used in all cases - Have full control over the choice of numeric type (e.g. int vs. short) Cons of explicit declaration: - Code might take slightly longer to write - Less adaptable to interface changes Pros of LVTI: - Code might be slightly faster to write - More adaptable to interface changes Cons of LVTI: - Code is less self-documenting - Can only be used in select cases - Can't indicate that you want a byte or short (will always get an int)

6.F: Debugging + The IntelliJ Debugger The key to debugging is understanding the program state. The two most common techniques for this are print statements and debuggers. What are the pros and cons of each?

Pros of print statements: - Straightforward, simple - Only see what's requested - Can see multiple states at points simultaneously, how a value changes over time - Can keep print statements for debugging in the future Cons of print statements: - It can become tedious - Clutters the code - Might need to add more print statements later if you want to see things you didn't think about before Pros of debuggers: - Can look at whole program state in real-time, and you don't need to predict what you cannot see - Can go through program step by step - Doesn't clutter the code Cons of debuggers: - You can only see the state of one point at a time, not multiple - Have to learn how to use a debugger

6.W: The Command Line + Processes How do we start a process to run a Python program in Java?

Python code is organized into modules - One file = one module - Each module has a name that it's known by Can access a module's name via the global variable __name__ - If the module was imported, __name__ is its filename, e.g. math.py: __name__ = "math" - If the module was run from the command line or an interactive prompt, __name__ = "__main__" Ex: greeter.py: import sys if __name__ == "__main__": args = sys.argv[1:] print ("hi " + args[0]) - Only try to access the command-line args if this module was executed from the command-line Command line command to run this program: - python3 /home/rebecca/greeter.py friend! - Version of python, path to .py file, any other command line args - The sys module defines a variable, argv, that gets populated with the filename + command-line args (excludes python3) - Runs greeter.py with sys.argv = ["/home/rebecca/greeter.py", "friend!"] Starting the process: public class Main { public static void main(String[] args) { ProcessBuilder pb = new ProcessBuilder(); String[] cmd = new String[]{"python3", "/home/rebecca/greeter.py", "friend!"} pb.command(cmd); Process proc = pb.start(); } }

4.W: Type Parameterization + Generics I + Lists Raw types, when raw types are good design

Raw type: A generic type that expects a type parameter but hasn't been given an explicit value for its type parameter, cannot be declared or instantiated (try to avoid this); ex: List. When it may be good design to use a raw type: a. When you are trying to access a generic class's static fields or methods. b. When you use instanceof to perform type-checking, i.e. can just call instanceof List to check if it is of type List, no parameters.

6.M: I/O What are example of reader and writer operations?

Reader/Writer: Optional higher level abstractions that wrap around streams (typically operate on characters) Reader operations include: - int read(char[] cbuf) -> reads characters into an array - void close() -> closes the underlying stream; releases resources Writer operations include: - void write(String str) -> writes a String - void close() -> closes the underlying stream; releases resources - void flush() -> ensures that all data from previous write() calls get written to the destination

5.W: The Call Stack + Recursion + Errors What is recursion?

Recursion: When something is defined in terms of smaller versions of itself Technique for both structuring data and solving problems Structuring data: Define a data type that is composed of smaller instances of the same data type Solving problems: Reduce a problem to smaller instances of the same problem A recursive function: A function that calls itself A recursive function must define two types of cases: 1. Base case = case in which the answer can be computed directly 2. Recursive case = case in which the answer can be expressed in terms of the answer to a smaller instance of the problem Can have multiple base cases and/or multiple recursive cases Each case is comprised of a "when" and a "what" When: under what conditions is this case executed? What: what steps does this case take?

6.F: Debugging + The IntelliJ Debugger What are the purpose and use of: - Rerun program - Resume execution - Stop execution - Step over - Step into - Step out - Run to cursor in the IntelliJ debugger?

Rerun program causes execution to restart from the beginning of the program - Runs until the first breakpoint or program termination (whichever comes first) Resume execution causes execution to continue from the current point (here, main:11) - Runs until the next breakpoint or program termination (whichever comes first) Pause execution pauses execution at the current point - Useful for long-running programs, when a lot of time passes between breakpoints Stop execution terminates program & exits debug mode - Clears all program state - Have to rerun the program from the beginning Step over causes control flow to move to the next line in the current function (main:12) - Can result in multiple lines executing: any function calls in the current line (main:11) will execute in their entirety Step into causes control flow to move to the next line in the overall program, wherever that may be - If there is one function call in the current line, control flow moves to the first line of that function - If there are multiple calls in the current line, you must select which function to enter - If there are no calls in the current line, step over and step into are equivalent Force step into causes control flow to move to the next line to be executed even if it's a built-in library function - By default, the debugger steps over library code (even if you select step into) unless you select force step into Step out executes the remainder of the current function, causing control flow to return to the call site in the caller - Stepping out of the last frame is equivalent to exiting the program - Step into + step out is not quite equivalent to step over - After step out, you'll still be on the line that made the call(rather than the next line) Run to cursor causes execution to continue from the current point (main:11) - Runs until the first breakpoint, program termination, or the line in which the cursor is located(whichever comes first) - Can also "force run to cursor" (Ctrl + Alt + F9), skippingpast all breakpoints

4.F: Maps + Sets + Constructing Containers + Generics II What is a Set<E>, what are its two main implementations, what operations can be performed on it, and how do we iterate over it?

Set<E>: An unordered collection of unique elements of type E. Two implementations: HashSet - underlying implementation is a HashMap (hash table) TreeSet - underlying implementation is a TreeMap (red-black tree) Operations on Set<E>: boolean add(E elem) - adds elem to the Set boolean contains(Object obj) - returns true if obj is in the Set; false otherwise int size() - returns the size of the Set boolean remove(Object obj) - removes obj from the Set boolean addAll(Collection<? extends E> c) - adds all of the elements of c to this Set boolean retainAll(Collection<?> c) - updates this Set to only contain elements that overlap with c boolean removeAll(Collection<?> c) - removes all of the elements of c from this Set Iteration: Since a set is unordered, iterate using a for each loop: Set<String> strSet = new HashSet<String>();... // populate strSet for (String str : strSet) { // do something }

5.M: Type Inference + Type Erasure & Invariance + Generics III What are the situations in which LVTI cannot be used? What are the situations in which LVTI should be used?

Situations in which LVTI cannot be used: - Declaration without definition (ex: var myString;) - Eventual definition (ex: declaraing var myString;, then defining it later on after a conditional) - Null definition (ex: var myString = null;) - Fields, parameters, & return types (anything that isn't a local variable, ex: public void f(var x) { ... }) Situations in which LVTI should be used: - In general: readability > writeability! - Each block of code should be readable in isolation (e.g. if a function is called, you shouldn't need to go look at that function's definition to figure out what it returns; don't do: var result = object.method();) - Readability should not depend on IDE-specific code analysis (don't assume everyone uses the same IDE as you)

5.F: Exceptions When and how can we define custom Exception types (i.e. subclasses of Exception)?

Subclasses of Exception are basically just wrappers around a stack trace + (optionally) error message To define a new subclass of Exception, you: - Don't need to define any operations - Do need to override one or more constructor(s), e.g.... public Exception() public Exception(String message) Ex: public class CustomException extends Exception { public CustomException(String message) { super(message); } } - Subclasses must extend Exception - Typically want to override the constructor that takes a message, so custom exception can provide info about what went wrong - Super delegates to the superclass constructor

5.F: Exceptions When you override a superclass method that's declared to throw an exception, what must be true about the subclass version? What if the superclass method does not throw any exceptions?

Superclass method with exception: - Subclass must be declared to throw at least the same type of exception - Can be declared to throw a more specific type - Cannot be declared to throw a more general type (or none at all) Ex: public class TypeA { public void f() throws IOException { ... } } This is correct: public class TypeB extends TypeA { public void f() throws IOException { ... } } This is incorrect: public class TypeB extends TypeA { public void f() throws Exception { ... } } - Subclass cannot throw a more general type Superclass method without exception: - Cannot be declared to throw any exceptions

5.F: Exceptions How is an exception caught in Java?

The Try & catch can be used to catch an exception and handle it Ex: try { // operation(s) that may fail ... } catch (<ThrowableClassName1> <varname>) { // error handling code ... } catch (<ThrowableClassName2> <varname>) { // error handling code ... } finally { // do something ... } - A try-catch begins with exactly one try block, containing code that may throw an exception - Each catch block should be designed to handle one type of exception that can be thrown by the code in the try block - The catch block header includes: <ThrowableClassName1>, the type of exception being handled and <varname>, the variable name used to refer to the exception object within the catch block - A catch block's body only executes if an exception of the stated type (or a subclass of the stated type) gets thrown by the code in the try block - Only the first catch block whose stated type encompasses the thrown type gets executed → compiler enforces that you order catch blocks from more to less specific - Can have zero or one finally blocks after all catch statements - The finally block's body always executes, regardless of whether the code in the try block succeeded or failed & what happened in the try/catch blocks - Typically used for preventing resource leaks; e.g., close all opened files Exceptions propagate through the call stack until an appropriate catch block is found or the end of the call stack is reached.

5.F: Exceptions Under what circumstances will the body of a finally block execute?

The body of the finally block will always execute whether the code in the try block succeeded or not, and is unaffected by the other try/catch blocks. Usually, the finally block is used for cleanup and preventing security leaks, like closing any opened files. It is discouraged to put return statements in a finally block, since putting the main logic of the code in the finally block (which includes return statements) may mess with what was originally intended to be returned by a prior block. Also, if there is an exception thrown in a prior block, control flow will proceed to the finally block, and the return statement in the finally block will take precedence over the thrown exception, and the exception will never be thrown.

4.F: Maps + Sets + Constructing Containers + Generics II How can iteration be performed over maps?

The key, value, and entry sets are unordered, so you need to use a foreach loop to iterate over them. Map<Integer, String> myMap = new HashMap<Integer, String>();... // populate myMap Three ways to iterate: Iterate through keys only: for (Integer key : myMap.keySet()) { // do something } Iterate through values only: for (Integer key : myMap.keySet()) { // do something } Iterate through both keys and values: for (Map.Entry<Integer, String> entry : myMap.entrySet()) { // do something }

5.F: Exceptions What are the three throwable types in Java? Which exception types are unchecked, and which are checked? Answer the following about each throwable type: 1. Typical point of origin? 2. Typically caused by programmer error? 3. Typically recoverable? 4. Need to be handled?

The three throwable types in Java are errors, runtime exceptions, and all other exceptions (both runtime and other exceptions are of type exception, which is a throwable type). Errors: 1. Typical point of origin? - JVM 2. Typically caused by programmer error? - No 3. Typically recoverable? - No 4. Need to be handled? - No Runtime exceptions: - Unchecked; (no handling required); often the result of programmer error 1. Typical point of origin? - JVM 2. Typically caused by programmer error? - Yes 3. Typically recoverable? - No 4. Need to be handled? - No Other exceptions: - All other Exceptions are checked (must be handled); typically not the result of programmer error - Ex: IO Exception, Timeout Exception 1. Typical point of origin? - Code 2. Typically caused by programmer error? - No 3. Typically recoverable? - Maybe 4. Need to be handled? - Yes

5.F: Exceptions How is an exception thrown in Java?

Throw & throws can be used to throw an exception The Exception class defines several constructors, including: - public Exception() - public Exception(String message) The Exception class inherits several methods from Throwable, including: - public String getMessage() - public void printStackTrace() If there is any chance that a given method could result in an exception occurring, its header must include a throws declaration. This includes a case where one or more of its callees could potentially throw an exception that it does not catch. Ex: public class SampleClass { public void f(double x) throws Exception { System.out.println("first print in f()"); double y = sqrt(x); System.out.println("second print in f()"); } public double sqrt(double x) throws Exception { System.out.println("first print in sqrt()"); if (x < 0) { throw new Exception("sqrt() unsupported " + "for negative numbers"); } System.out.println("second print in sqrt()"); ... } } After the if statement --> creates new Exception object, throws it to sqrt()'s caller (f()). Header "public double sqrt(double x) throws Exception" indicates that sqrt() may throw an exception. Input: -5 Outputs: first print in f() first print in sqrt() The Exception will get thrown back to the caller, & will be propagated through the call stack until it's caught or the end of the call stack is reached The exception isn't caught (no try-catch block in f()) --> program terminates & stack trace gets printed Stack trace: java.lang.Exception: sqrt() unsupported for negative numbers at SampleClass.sqrt(SampleClass.java:11) at SampleClass.f(SampleClass.java:4) at Main.main(Main.java:32)

5.F: Exceptions What is the difference between a checked and unchecked exception in Java?

Two types of exceptions in Java: Checked: - Required to be handled Unchecked: - Not required to be handled

6.M: I/O What are the two types of long-term storage devices, and how are they different?

Two types: hard disk drives (HDDs), solid state drives (SSDs) Hard disk drives: - A circular disk + a fixed arm that reads from the disk; disk spins to the correct location for the arm to read - Has no notion of files Solid state drives: - Has non-volatile memory: sequence of fixed-size storage cells like "normal" memory (DRAM), but persistent - Also has no notion of files

6.W: The Command Line + Processes Each process believes that it is the only process running, but in reality many processes exist simultaneously and share system resources. Describe two ways in which the operating system protects each process from potentially malicious processes.

Two ways to protect against malicious processes: 1. Processes cannot run w/ full privileges 2. Each process is provided with a private virtual address space 1. The OS disallow processes to have full privileges during execution - While the OS is in kernel mode and has full control, processes can only run in user mode, and the hardware limits the operations that can be performed - Since the process needs to go to the OS during operations that are normally limited, the OS can protect against potentially malicious behavior 2. The OS delegates a private virtual address space to each process - Within each process, a fake virtual address is used (as opposed to a physical, hardware memory address) - Whenever a process wants to access memory, the OS uses a predetermined page table mapping to convert virtual to physical addresses - Two advantages: i. Security - OS is part of address translation, so the process can't get access to memory that it shouldn't have access to ii. Convenience - OS can show the process as a contiguous virtual address space even if physical spaces are not contiguous

5.M: Type Inference + Type Erasure & Invariance + Generics III What is type erasure and why does it exist?

Type erasure: Compiler replaced parameterized types with raw types, inserts any necessary casts. Ex: List<String> myList = new ArrayList<String>(); myList.add("hello world"); String myStr = myList.get(0); Gets converted at compile time to: List myList = new ArrayList(); myList.add("hello world"); String myStr = (String) myList.get(0); Return type is no longer known (since myList is raw), so a cast is needed Type erasure allows for compatibility between raw types and parameterized types. The compiler checks the parameterized types before performing type erasure, so the transformed code is still type safe.

5.M: Type Inference + Type Erasure & Invariance + Generics III What is type inference? When is type inference performed , and does type inference cause a time overhead at runtime? What is Local Variable Type Inference (LVTI), and what keyword does it use?

Type inference: The ability of the compiler to infer the type of a variable without the programmer explicitly declaring it Because type inference is performed at compile-time, there is no time overhead at runtime Local Variable Type Inference (LVTI): - Programmer uses a special keyword, var, on the lefthand side of the declaration instead of the type - Compiler infers the lefthand side from the righthand side In explicit declaration, ex: int x = 5; the compiler knows that x is an int because you said so. Using LVTI, ex: var x = 5; Compiler infers that x is an int because its value is 5.

5.F: Exceptions Under what circumstances is it a good design to use exceptional control flow (try-catch blocks + the throw statement)? What are its pros and cons? When should you not use exceptional control flow?

Use exceptional control flow for checked exceptions that you know how to handle. Pros: - Forces you to think about what errors may occur - Forces you to handle errors in a structured way - Seamless integration with the JVM Cons: - Code is slightly bulkier, but no worse than checking return values - Slight time overhead - Can be abused out of laziness When not to use (bad design): - Don't use to handle unchecked exceptions caused by lazy programming (ex: IndexOutOfBoundsException)

4.W: Type Parameterization + Generics I + Lists From a design perspective, what is the problem with defining a List type (class or interface) whose get() method returns an Object?

We would forfeit the safety of type-checking that happened at compile-time, since there is a need to cast before actually using it when taking it out of the list, and the possibility of error of wrong type casting. Ex: trying to get an object from the list of objects, and trying to assign it to a more specific type would require a cast. If it's cast to the wrong type, the compiler will still allow this, so the error would only be discovered at runtime (a lot worse) This is flexible, but not type-safe, and it's inefficient to keep casting to different types.

5.F: Exceptions Sample problem: Consider the following class: public class SampleClass { public static void f1() { String s1 = "Y"; String s2 = "X"; String s3 = "Z"; try { s1 = f2(7); s2 = "A"; s3 = f3(7); } catch (Exception e) { s1 = e.getMessage(); s3 = "B"; } finally { s3 = f3(5); } System.out.println(s3 + s2 + s1); } public static String f2(int x) throws Exception { if (x > 2) { throw new Exception("C"); } return "D"; } public static String f3(int x) { try { f4(x); } catch (Exception e) { return "E"; } return "G"; } public static String f4(int x) throws IOException { if (x != 7) { throw new IOException("H"); } return "I"; } } What will be printed when SampleClass.f1() is invoked?

What will be printed when SampleClass.f1() is invoked? s1 - "C" s2 - "X" s3 - "E" "CXE" will be printed, since s1, s2, s3 are concatenated.

5.W: The Call Stack + Recursion + Errors Sample problem: Consider the following recursive function: public static int f(int x) { if (x <= 2) { return x; } else { return f(x/2) * f(x/4); } } Draw the call tree corresponding to f(32). What will be returned by f(32)? Imagine that we memoized this function by: 1. Placing each new product that is computed within the recursive case in the cache (associated with the current value of x) immediately before returning it, and 2. Checking for the existence of a cached result immediately after the base case and before the recursive case (i.e. in an additional else if block between the existing if and else blocks). Draw an updated call tree for f(32) based on this memoized version.

What will be returned by f(32)? 32 See worksheet 5.W for images.

5.M: Type Inference + Type Erasure & Invariance + Generics III Sample problem: Which of the following statements are valid (i.e., will compile without error)? Select all that apply. a. var myInt; b. var myInt = 5; c. var myInt = 5.5; d. var myObj = null;

Will compile without error: b. var myInt = 5; c. var myInt = 5.5; Will not compile: a. var myInt; --> need to declare and define at the same time. d. var myObj = null; --> cannot define as null

4.W: Type Parameterization + Generics I + Lists From a design perspective, what is the problem with defining a List type (class or interface) whose get() method returns a specific (non-generic) type?

You need to define several List interfaces, one corresponding to each type. There is a lack of reusability, and you would need duplicate code of a new List interface for each type.

4.W: Type Parameterization + Generics I + Lists Sample problem: List<Integer> myList = new ArrayList<Integer>(); for (int i = 10; i > 0; i -= 2) { myList.add(i); } myList.set(0, 5); myList.remove(2); What will be the contents of myList after the above code has executed?

[5, 8, 4, 2]

5.M: Type Inference + Type Erasure & Invariance + Generics III Imagine that you're trying to define a method that operates on an input object of generic type (such as a List). Describe the circumstances under which each of the following options would be the best design choice: a. Define a generic method with an unbounded type parameter when... b. Define a generic method with a bounded type parameter when... c. Define a method whose parameter uses an unbounded wildcard when... d. Define a method whose parameter uses a bounded wildcard when...

a. Define a generic method with an unbounded type parameter when... - You want to have a consistent type across multiple objects, and you don't want to limit the kinds of values, or types, that can potentially be passed to a type parameter. b. Define a generic method with a bounded type parameter when... - You want to have a consistent type across multiple objects, and you want to limit the values that may be passed to a type parameter, by either imposing an upper bound with extends or a lower bound with super. Another case is if you want to both produce / write, and also consume / read. Always use type parameters with generic methods (can be bounded or unbounded) c. Define a method whose parameter uses an unbounded wildcard when... - The type parameter itself is used solely in a single place, not inside a method or inside a different parameter or return type. d. Define a method whose parameter uses a bounded wildcard when... - Use bounded wildcards when declaring variables of generic types and defining methods that operate on generic types, - e.g.:public static double sumList(List<? extends Number> myList) { double sum = 0.0; for (Number num : myList) { sum += num.doubleValue(); } return sum;} - ? extends Number is a bounded type parameter - You have any other case that isn't satisfied by a, b, or c.

6.W: The Command Line + Processes Sample problem: Imagine that you've defined a file, MyProgram.java, that includes a definition of the MyProgram class which has a public static void main(String[] args) function that's set up to take command-line args. What commands would you run to compile MyProgram.java and run main() with args ["a", "b"] ? Assume that you're running the commands within the directory containing MyProgram.java, and therefore you can use the relative path.

javac MyProgram.java java MyProgram a b


Set pelajaran terkait

CYBERSECURITY CHAPTER 1 QUIZ STUDY GUIDE

View Set

NCLEX PN Infancy to Adolescence Review

View Set

ENSP 2000 Exam 2 MyLab and Mastering

View Set

Commercial Law Chapters 9,10,11,12,13,14,(little)15,17,18 Test #2

View Set

International Business Test #3 - Chapter 8

View Set

Chapter 4: Identity and Access Management

View Set