540 likes | 662 Views
Lecture 8. CS 202 Fall 2013. Review of Abstract Classes. Abstract classes can not be instantiated but can have concrete subclasses can have concrete methods can have abstract methods can also have instance variables. Types and Data Structure Parameters.
E N D
Lecture 8 CS 202 Fall 2013
Review of Abstract Classes • Abstract classes • can not be instantiated but can have concrete subclasses • can have concrete methods • can have abstract methods • can also have instance variables
Types and Data Structure Parameters Arrays and Lists (and also other data structures you will study in CS 203) can be parameterized with abstract classes. An array or list whose type is a class can hold objects of any concrete class that extends the class.
Types and Data Structure Parameters package vehicles; public class ShrinerMobile extends Car { public ShrinerMobile(double weightInKgIn, double displacementIn, String fuelTypeIn) { super("Shriners' Lodge #1", weightInKgIn, displacementIn, "gasoline"); } public String toString() { return "ShrinerMobile with a " + engineDisplacementInCc + " cc gasoline engine weighs " + weightInKg + " kg and is going " + speedInKmPerHr +" KPH " + direction.toString(); } }
Breaking Down At The Interfaces • Software is said to "break down at the interfaces." • You can write a class that does a good job of modeling a savings account • Somebody else will do a good job writing a class that models a bank • The hard part is making them work together
Need For Interfaces • Inheritance is very powerful and can be used well where • subclasses will have many similarities and a few differences • Subclasses can be grouped hierarchically • MotorVehicle Vs. Spacecraft • Subclasses will all have very similar data fields • But • class hierarchies can be confusing • since methods might be defined at any point in the hierarchy, they can be hard to change without creating unanticipated consequences
Need For Interfaces • Interfaces meet some of the same needs in a different and simpler way • Interfaces provide another way to use polymorphism. • If you are programming a furnace, you need a heatAir() method. However, the internal workings of the method will differ depending on whether you are programming a gas furnace or an oil one. • Interfaces contain method declarations and may contain constants • No method definitions • No variables • Interfaces can’t declare private or protected methods, just public ones (that's why they're called that!)
Interface Syntax • Syntax for defining an interface: Access modifier interface name{ final declarations method declarations } For example: public interface Vehicle { public static final double KMTOMILES = .609; public void accelerate(double speedIncrementInKmPH); public double getSpeedInKmPH(); public void steer(Direction d); }
Need For Interfaces • Classes implement interfaces. This is declared in the class header: public class MyClass implements MyInterface { Eg public class Car implements Vehicle { • The distinction between interface and implementation is very important in OOP. • A class can implement any number of interfaces
Implementing Interfaces • A class that implements an interface must implement the methods declared in the interface • Thus, if a class implements a particular interface, we know that we can call certain methods on objects of the class. We don’t care that the methods may work differently for different implementations of the interface. • Here's another way to make the last point. Different classes that implement the interface may use methods whose internal workings are completely different, as long as they have the signature defined in the interface
Implementing Interfaces • Implementations of methods required by an interface need the @Override annotation: @Override public void setLocation(String location){ this.location= location; }
Not Breaking Down At The Interfaces • Other objects that deal with objects of your class should only have to know the public interface, not the internal workings of your class • Reduce what other programmers need to learn (or you need to remember) in order to use your classes • Minimize problems at interfaces • You can change the internal workings of your class without any problems if the interface stays the same.
Not Breaking Down At The Interfaces • Consider a WeatherReport class and a WeatherBalloon class. • A WeatherReport should be able to get the barometric pressure from a WeatherBalloon by just calling an accessor method. It shouldn’t be dependent on the particular way WeatherBalloon determines what the pressure is. • WeatherReport's programmer doesn’t need to learn how the measurement is made • WeatherBalloon's programmer can change the method for determining pressure without affecting WeatherReport at all. • Compare this to a computer storing data in permanent storage. I should be able to swap out my hard drive and controller for an SSD drive and controller without affecting the CPU, operating system, etc.
Not Breaking Down At The Interfaces • Consider an application that monitors monster attacks. We need to track both various species of monsters, like zombies, and unique monsters, like Godzilla and the Jersey Devil (https://en.wikipedia.org/wiki/Jersey_devil). We also need to be able to track new types of monsters we don’t even know about yet. • Although there are many types of monsters, all monsters can do these things: • tell us their names • change locations • tell us their origin stories • rampage
List Parameterized by an Interface List<Monster> monsters = new ArrayList<Monster>(); Monster ebenezer = new Zombie("New Orleans"); Monster godzilla = new UniqueMonster("Godzilla", "radioactive breath of fire", "Tokyo", "ancient sea monster awakened from millennia of sleep by radioactive fallout after " + "World War II"); // if the type in the list or array is an interface, we can add objects of any class that implements the interface monsters.add(ebenezer); // ebenezer is a Zombie monsters.add(godzilla); // godzilla is a UniqueMonster
Monster Interface package monsters; public interface Monster { public void setName(String name); public String getName(); public void setLocation(String location); public void rampage(); public String getOriginStory(); }
package monsters; public class Zombie implements Monster{ private static int zombieCount; // since zombieCount is static, we will get the same zombieCount from any Zombie private String name; private String location; public Zombie(String location){ this.name = "Zombie # " + String.valueOf(zombieCount); zombieCount++; this.location=location; } @Override public void setName(String name) { this.name = name; } @Override public String getName() { return name; } @Override public void rampage() { System.out.println(name + " joins a herd of zombies who attack citizens of " + location + " and eat their brains"); } @Override public String getOriginStory() { return "former human being who was raised from the dead by a magical spell spread by the bite of " + "other zombies."; } @Override public void setLocation(String location) { this.location = location; } }
package monsters; public class UniqueMonster implements Monster{ private String name; private String weapon; private String location; private String originStory; public UniqueMonster(String name, String weapon, String location, String originStory) { this.name = name; this.weapon = weapon; this.location = location; this.originStory = originStory; } @Override public void setName(String name) { this.name = name; } @Override public void setLocation(String location){ this.location = location; } @Override public void rampage() { System.out.println(name + " destroys " + location + " with " + weapon); } @Override public String getOriginStory() { return originStory; } @Override public String getName() { return name; } }
package monsters; import java.util.ArrayList; import java.util.List; public class MonsterAttackDriver { public static void main(String[] args) { List<Monster> monsters = new ArrayList<Monster>(); Monster m1 = new UniqueMonster("Godzilla", "radioactive breath of fire", "Tokyo", "ancient sea monster awakened from millennia of sleep by radioactive fallout after World War II"); monsters.add(m1); // here is a more concise way to create and object and add it to the list monsters.add(new UniqueMonster("Jersey Devil", "claws", "Princeton", "giant " + "batlike creature born to a cursed farmer in the 1700s")); Monster m = new Zombie("New Orleans"); monsters.add(m); for (int counter = 0; counter < 3; counter++) { m = new Zombie("London"); // reuse the Monster variable from a few lines back monsters.add(m); } for (Monster z : monsters) { System.out.println(z.getName() + " is a(n) " + z.getOriginStory()); z.rampage(); System.out.println(); } } }
clone() and Cloneable • Cloneable is an interface that requires that implementing classes have clone() methods. • clone() methods return deep copies of objects, so a variable that refers to a clone points to a separate object, not the one that was cloned • Object contains a protected clone() method which you need to call from your own clone methods, but Object.clone() will not copy objects to which your instance variables point. • If these are mutable, your clone() method must also copy them. If you fail to do this, your variables will point to the same objects the original object variables pointed to. This can lead to strange results.
clone() and Cloneable package monsters; public class Crypt { private String location; public Crypt(String location) { this.location = location; } public void setLocation(String location) { this.location = location; } public String getLocation() { return location; } public String toString(){ return "a mysterious crypt in " + location; } }
clone() and Cloneable package monsters; public class Vampire implements Monster, Cloneable { private String name; private Crypt crypt; public Vampire(String name, String location) { this.name = name; crypt = new Crypt(location); } @Override public void setName(String name) { this.name = name; } @Override public String getName() { return name; } @Override public void setLocation(String location) { crypt.setLocation(location); @Override public String getOriginStory() { return "undead creature which lives by sucking the blood of living humans"; }
clone() and Cloneable @Override public void rampage() { StringBuildersb = new StringBuilder(name + " arises from " + crypt.toString() + " and "); if (crypt.getLocation() == "Transylvania") sb.append("sucks people's blood all night, then returns to a coffin to hide from sunlight"); else if (crypt.getLocation() == "Burbank") sb.append("takes over the entire television industry"); else { System.out.println("wreaks unknown havoc in fresh Vampire territory"); return; } System.out.println(sb); } @Override public Object clone() { Vampire newV; try { /* Object clone() returns an Object. It will be a Vampire, but in order to get to anything specific to Vampires, we need to cast it to a Vampire and use a Vampire reference variable */ newV = (Vampire) super.clone(); //newV.crypt= new Crypt(crypt.getLocation()); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } return newV; } }
package monsters; public class MonsterAttackDriver { public static void main(String[] args) { // traps with faulty clone() methods Vampire v1 = new Vampire("Dracula", "Transylvania"); Vampire v2 = v1; // the Object version of toString is the one that shows the memory // location. v1 and v2 point to the same Vampire in memory System.out.println("v1: " + (Object) v1 + " v2: " + (Object) v2); // clone() returns an Object, but it will be a Vampire. To use methods // that are not inherited from Object, we need to cast. Vampire v3 = (Vampire) v1.clone(); // v1 and v3 are two different Vampires with all the same data values System.out.println("v1: " + (Object) v1 + " " + "v3: " + (Object) v3); System.out.println(); v1.rampage(); v3.rampage(); System.out.println(); // Since Strings are interned, when we change v3's name, we are creating // a new String and making v3's name variable refer to the new one v3.setName("Timmy"); v1.rampage(); v3.rampage(); System.out.println(); v3.setLocation("Burbank"); // crypt, the variable, is a reference variable of type Crypt. We didn't // write any code to reset v1's home, but watch what happens v1.rampage(); v3.rampage(); // we just changed the value to which both v1's and v3's crypt variables // refer System.out.println(); } }
Corrected clone() // next term, add a clone() to Crypt @Override public Object clone() { Vampire newV; try { /* Object clone() returns an Object. It will be a Vampire, but in order to get to anything specific to Vampires, we need to cast it to a Vampire and use a Vampire reference variable */ newV = (Vampire) super.clone(); newV.crypt= new Crypt(crypt.getLocation()); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } return newV; }
Driver Code For Corrected clone() package monsters; public class MonsterAttackDriver { public static void main(String[] args) { Vampire v1 = new Vampire("Dracula", "Transylvania"); // clone() returns an Object, but it will be a Vampire. To use methods that are not inherited from Object, we need to cast. Vampire v2 = (Vampire) v1.clone(); v2.setName("Timmy"); v2.setLocation("Burbank"); v1.rampage(); v2.rampage(); } }
Inner and Anonymous Classes • Be sure you understand the material in the book on inner classes and anonymous classes. • There may be a question on the midterm that requires you to know how to write an inner class • Study the syntax for creating inner classes within methods as well as within the parent class but outside any method. • Whether to use inner classes is a matter of individual preference • They make it obvious that the inner class is intended to be used with the main class, but • They clutter up your code • Only use them when • The inner class is very simple, and • You are sure it will only be used with the parent class
Inner and Anonymous Classes Anonymous inner classes, which almost always implement interfaces, are very common in GUI programming and client-server apps (that's nearly everything these days!) When we cover GUIs, you will need to understand anonymous classes.
Code To The Interface, Not The Implementation Use variables of the most abstract type available Use interface methods rather than methods that are unique to particular concrete classes whenever possible Coding to particular implementations, as opposed to interfaces, exposes you to the risk that your code will break when some other implementation changes. This is *very* dangerous because you may not even know when the other classes change. Coding to the interface also makes your code more modular. If you rely on well-defined interfaces, you can more easily swap out parts of your code later or determine which implementations of an interface at runtime.
CompareTo • Java contains a way to make it easy to sort objects • Collections.sort(list) is a static method of the Collections class. Lists are a type of Collection; we will cover this when we discuss inheritance. • Sort() sorts according to the result of running compareTo() when objects are compared during the sort • compareTo() compares the current object with another one sent as input to the method • compareTo() can compare objects using any method you can code.
CompareTo • Objects with compareTo() methods must be declared to implement the interface Comparable<>. Here is an example of the interface declaration: public class Student implements Comparable<Student> • Note the parameterization of Comparable, which looks just like the paramaterization used when declaring a list. For now, use the class name of the current class as the parameter.
CompareTo package demos; public class Student implements Comparable<Student>{ private String name; private Double gpa; public Student(String nameIn, Double gpaIn){ name = nameIn; gpa = gpaIn; } public String toString(){ return "Name: " + name + "; GPA: " + gpa; } // getters and setters omitted @Override public intcompareTo(Student otherStudent) { return this.gpa.compareTo(otherStudent.gpa); } }
CompareTo package demos; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class GradeBook { public static void main(String[] args) { List<Student> students = new ArrayList<Student>(); String[] names = {"Skipper", "Gilligan", "Mary Anne", "Ginger", "Mr. Howell", "Mrs. Howell", "The Professor"}; double[] gpas = {2.7, 2.1, 3.9, 3.5, 3.4, 3.2, 4.0}; Student currStudent; for(int counter = 0; counter < names.length; counter++){ currStudent=new Student(names[counter], gpas[counter]); students.add(currStudent); } // output the data System.out.println("Unsorted:"); for(Student s: students) System.out.println(s); Collections.sort(students); System.out.println("\nSorted:"); for(Student s: students) System.out.println(s); } }
One-Off Implementations • Java interfaces are usually used for cases in which many classes must implement partially or fully identical public interfaces • Some developers write interfaces for classes even when there will be only one implementation • Separate interface from implementation even when there is only one implementation • Interface is easier to understand because it is described in a stand-alone file • You, or some other programmer, are less likely to introduce bugs by carelessly changing the interface later.
One-Off Implementations package calculator; public interface Calculator { public double add(double op1, double op2); public double subtract(double op1, double op2); public double multiply(double op1, double op2); public double divide(double op1, double op2); }
One-Off Implementations package calculator; public class CalculatorImpl implements Calculator{ @Override public double add(double op1, double op2) { return op1 + op2; } @Override public double subtract(double op1, double op2) { return op1 - op2; } // other methods omitted }
Errors • Big Java's classification of errors: • Syntax/Compiletime • Incorrect Java, caught by IDE or compiler intx == 0; • Runtime / Logic • Logical problems in the program public static void main(String[] args){ intquotient = 0; for(intcounter = 10; counter >=0; counter--){ System.out.print("10 / " + counter + " = "); quotient = 10/counter; System.out.println(quotient); } }
Errors Other books / programmers distinguish runtime errors from logic errors: • Runtime errors occur when an operation is impossible to carry out. Without additional handling, they cause the application to crash: • Division by zero • Read from a file that does not exist • Integer.parseInt(1.2);
Errors • Logic errors cause incorrect results, not crashes String[] options = {"Yes", "No"}; intbuy = 0; JOptionPane.showOptionDialog(null, "Place Order?", "Buy Dialog", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, "Yes"); switch (buy) { case 0: buyProduct(); break; case 1: returnToShopping(); }
Stack Trace • A stack trace is a list of method calls and code line numbers, usually seen when an error occurs: • You should love a stack trace like a mother who only criticizes you for your own good. • Note that you can tell what line of your code was executing when the error occurred, but you may have to read several lines of the trace to get to it.
Exceptions Definitions of "exception:" • Wikipedia: "An anomalous event requiring special processing" • Liang Introduction To Java Programming, referring to Java exceptions specifically: "An object that represents an error or condition that prevents execution from proceeding normally." • In Java, an exception is an object! • Various kinds of Exceptions are defined in different Exception classes. • Exception classes have methods; the ones you are likely to use involve stack traces and error messages. • Exceptions are "raised" or "thrown" by code, either written by you or by the author of a class you are using.
Exceptions: • You can create your own exceptions to deal with the problems that are likely to arise in your programs. • Many exception types are built in to Java; you have already encountered many of these • This code will raise a StackOverflowException : public class Demo { int x; public Demo(intoldX){ x = oldX + 1; System.out.println(x); Demo d = new Demo(x); } public static void main(String[] args) { Demo demo = new Demo(1); } } Other exceptions are programmer-defined.
Exceptions • Try/Catch: • Run an error-prone operation in try{}; if an exception occurs, deal with it in catch{} • Optional finally{} runs after the try code, whether or not an exception occurs
Try…catch…finally Try-Catch-Finally try{ error-prone code } catch(TypeOfException nameForException){ what to do if this type of exception occurs } catch(OtherTypeOfException nameForException){ what to do if this type of exception occurs } catch(Exception ex){ what to do if any other type of exception occurs } … finally{ code to execute afterwards, whether or not an exception occurred }
Throwing Exceptions • The exceptions we have seen so far are thrown by code you did not write • You can also check for exceptional conditions and throw exceptions, either standard ones or your own • See the textbook for syntax for creating your own exception types. Only do this when none of the standard ones seem correct.
Try-Catch-Finally • Consider this error-prone code: public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.println("Enter two numbers:"); double num1 = input.nextDouble(); double num2 = input.nextDouble(); double result = num1 / num2; System.out.println(num1 + "/" + num2 + "=" + result); input.close(); }
Try-Catch-Finally public static double quotient(double num1, double num2) { if (num2 == 0) throw new ArithmeticException("Divisor cannot be zero"); return num1 / num2; } public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.println("Enter two numbers:"); try { double num1 = input.nextDouble(); double num2 = input.nextDouble(); double result = quotient(num1, num2); System.out.println(num1 + "/" + num2 + "=" + result); } catch (ArithmeticException ex) { System.out.println(ex.getMessage()); } finally { input.close(); } System.out.println("festivities continue without a crash!"); }
Checked Exceptions • The compiler will force you to handle certain kinds of exceptions; these are called Checked Exceptions. • Recall the file operations in week 2. Since file I/O is so error-prone, many file operations throw exceptions which you must catch in order to use the File, PrintWriter, etc. classes.