Simulators - California State University, Los Angeles

Download Report

Transcript Simulators - California State University, Los Angeles

Lecture 7
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
2
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.
3
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();
}
}
4
Types and Data Structure Parameters
5
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
6
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
7
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!)
8
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);
}
9
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
10
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
11
Implementing Interfaces
• Implementations of methods required by an
interface need the @Override annotation:
@Override
public void setLocation(String location){
this.location = location;
}
12
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.
13
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.
14
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
15
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
16
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();
}
17
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;
}
}
18
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;
}
}
19
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();
}
}
}
20
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
21
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;
}
}
22
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";
}
23
clone() and Cloneable
@Override
public void rampage() {
StringBuilder sb = 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;
}
}
24
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();
}
}
25
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;
}
26
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();
}
}
27
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
28
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.
29
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.
30
Code To Interface, Not Implementation
31
equals()
• We have already seen the String method equals()
• You can define a method that determines whether two objects of your
own class are equal. Often this tests whether all the data variables are
equal.
• If this method overrides the one from Object, it must take the same
argument type, namely another Object. This means that you could test
two objects of different types for equality. More likely, you will use a cast
to make sure you are comparing two objects of your class.
equals()
package demos;
public class 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;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != this.getClass()) {
return false;
}
Student otherStudent = (Student) obj;
if (name.equals(otherStudent.name) && gpa.equals(otherStudent.gpa))
return true;
return false;
}
}
equals()
package demos;
import java.util.ArrayList;
import java.util.List;
public class GradeBook {
public static void main(String[] args) {
// create an array list of Students
List<Student> students = new ArrayList<Student>();
String[] names = {"Skipper", "Gilligan", "Mary Anne", "Ginger", "Mr. Howell", "Mrs. Howell", "The Professor", "Mary Anne"};
double[] gpas = {2.7, 2.1, 3.9, 3.5, 3.4, 3.2, 4.0, 3.9};
Student currStudent;
for(int counter = 0; counter < names.length; counter++){
currStudent=new Student(names[counter], gpas[counter]);
students.add(currStudent);
}
testStudentsForEquality(students.get(0), students.get(4));
testStudentsForEquality(students.get(2), students.get(7));
testStudentsForEquality(students.get(3), students.get(3));
}
public static void testStudentsForEquality(Student s1, Student s2){
if(s1.equals(s2)) System.out.println(s1 + " = " + s2);
else System.out.println(s1 + " != " + s2);
}
}
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.
• 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.
package demos;
CompareTo
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 int compareTo(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
}
Avoid continue; use break only in switch blocks
Consider this code using break and continue:
public class Demo {
public static void main(String[] args) {
while (true) {
String[] choices = {"Quit", "Easy A", "Ask Again", "Boot To The Head"};
int choice = JOptionPane.showOptionDialog(null, "What Next??", "Choice",
JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, choices, choices[0]);
switch (choice) {
case 0:
break;
case 1:
easyA();
break;
case 2:
continue;
case 3:
bootToTheHead();
break;
}
if(choice == 0) break;
}
}
public static void easyA() {
}
public static void bootToTheHead() {
}
Avoid continue; use break only in switch blocks
The code above is hard to understand because control may break out of the switch or loop at times that you cannot predict
when coding. Instead, use loop conditions and variables to get the same results:
public class Demo {
public static void main(String[] args) {
int choice;
do {
String[] choices = {"Easy A", "Boot To The Head", "Ask Again", "Quit" };
choice = JOptionPane.showOptionDialog(null, "What Next??",
"Choice", JOptionPane.DEFAULT_OPTION,
JOptionPane.QUESTION_MESSAGE, null, choices, choices[0]);
switch (choice) {
case 0:
easyA();
break;
case 2:
bootToTheHead();
break;
}
} while(choice != 3);
}
public static void easyA() {
}
public static void bootToTheHead() { }
}
Avoid continue; use break only in switch blocks
In the revised version, control will always continue
through the end of the loop.
The loop has a "single point of exit," which makes is
easier to understand