Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Lecture 9 CS 202 clone() and Cloneable • Cloneable is an interface that marks implementing classes as ones that can be "cloned", ie copied. The method that does this is normally called clone(). However, the interface does not actually contain the clone() method, so there is no guarantee that a call to clone() will always work on every Cloneable. • 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 hard-to-diagnose bugs. • Cloneable is not widely used because of its design flaws. After the following example, I will discuss other ways to accomplish deep copying. 2 clone() and Cloneable package monsters; public interface Monster { public void setName(String name); public String getName(); public void setLocation(String location); public void rampage(); public String getOriginStory(); } 3 clone() and Cloneable package monsters; public class Crypt implements Cloneable { 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; } @Override public Crypt clone(){ return new Crypt(location); } } 4 clone() and Cloneable package monsters; public class Vampire implements Monster, Cloneable { private String name; private Crypt crypt; public Vampire(String nameIn, String location) { name = nameIn; 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"; } 5 clone() and Cloneable @Override public void rampage() { System.out.println(name + " arises from " + crypt.toString() + " as a bat, flies around and sucks people's blood all night, then returns to his coffin" + " to hide from sunlight"); } @Override public Vampire clone() { Vampire newV; try { /* * Object clone() returns an Object. 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= crypt.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } return newV; } } 6 Clone and Cloneable 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("Bob"); v2.setLocation("Burbank"); v1.rampage(); v2.rampage(); } } 7 Copy Constructor • An alternative approach is to write a copy constructor for each class you need to be able to "clone." • Write a constructor that takes an instance of the class as an argument and deep copies the data to create an identical instance. • If data includes reference types, need to be careful to deep-copy it! • Some programmers would write a method called newInstance() that calls a constructor and setters to accomplish the same thing package monsters; Copy Constructors public class RobotSoldier implements Monster{ private String location; private SerialNumber serialNumber; private String name; public RobotSoldier(String location, SerialNumber serialNumber){ this.name = "RobotSoldier " + serialNumber; this.location = location; this.serialNumber = serialNumber; } public RobotSoldier(RobotSoldier r){ this.location = r.location; // bad code! easy trap with reference types! this.serialNumber = r.serialNumber; } @Override public void setName(String name) { this.name = name; } @Override public String getName(){ return name; } Copy Constructors @Override public void setLocation(String location) { this.location = location; } public SerialNumber getSerialNumber() { return serialNumber; } public void setSerialNumber(SerialNumber serialNumber) { this.serialNumber = serialNumber; } public void changeSerialNumber(String prefix, int number){ serialNumber.setPrefix(prefix); serialNumber.setNumber(number); } @Override public void rampage() { System.out.println("Robot Soldier " + serialNumber + " joins an army of robot soldiers to seize power in " + location+ " as part of a plot to take over the world"); } @Override public String getOriginStory() { return "evil robot soldier created by a mad scientist"; } } Copy Constructors package monsters; public class SerialNumber { private String prefix; private int number; public SerialNumber(String inString, int inInt){ prefix = inString; number = inInt; } public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } public String toString(){ return prefix + "-" + number; } } Copy Constructors package monsters; import java.util.ArrayList; import java.util.List; public class MadScientist { private String name; List<RobotSoldier> army = new ArrayList<RobotSoldier>(); public MadScientist(String name) { this.name = name; } public void createArmy(RobotSoldier r, int armySize) { for (int counter = 0; counter < armySize; counter++) { RobotSoldier n = new RobotSoldier(r); n.changeSerialNumber("Chi", counter); army.add(n); } } public void addSoldier(RobotSoldier r) { army.add(r); } public void mountPutsch() { for (RobotSoldier r : army) r.rampage(); } } Copy Constructors package monsters; public class MonsterAttackDriver { public static void main(String[] args) { RobotSoldier r = new RobotSoldier("Chicago", new SerialNumber("CHI", 1)); MadScientist m = new MadScientist("Dr. Evil"); m.createArmy(r, 5); m.mountPutsch(); r.changeSerialNumber("CHI", 2); System.out.println(r.getSerialNumber()); m.mountPutsch(); } } Corrected Copy Constructor package monsters; public class SerialNumber { private String prefix; private int number; public SerialNumber(String inString, int inInt){ prefix = inString; number = inInt; } // give SerialNumber a copy constructor too! public SerialNumber(SerialNumber s){ // this.prefix = s.prefix would really still be OK due to String immutability this.prefix = new String(s.prefix); // this is OK because number is an int, which is a value type this.number = s.number; } public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } public String toString(){ return prefix + "-" + number; Corrected Copy Constructor package monsters; public class RobotSoldier implements Monster{ private String location; private SerialNumber serialNumber; private String name; public RobotSoldier(String location, SerialNumber serialNumber){ this.name = "RobotSoldier " + serialNumber; this.location = location; this.serialNumber = serialNumber; } public RobotSoldier(RobotSoldier r){ this.location = r.location; // make a deep copy of serialNumber using its copy constructor! this.serialNumber = new SerialNumber(r.serialNumber); } @Override public void setName(String name) { this.name = name; } @Override public String getName(){ return name; } @Override public void setLocation(String location) { this.location = location; } Corrected Copy Constructor public SerialNumber getSerialNumber() { return serialNumber; } public void setSerialNumber(SerialNumber serialNumber) { this.serialNumber = serialNumber; } public void changeSerialNumber(String prefix, int number){ serialNumber.setPrefix(prefix); serialNumber.setNumber(number); } @Override public void rampage() { System.out.println("Robot Soldier " + serialNumber + " joins an army of robot soldiers to seize power in " + location+ " as part of a plot to take over the world"); } @Override public String getOriginStory() { return "evil robot soldier created by a mad scientist"; } } 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 in programmer-defined sort orders • 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 parameterization 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); } } Instanceof • The next demo uses Java's instanceof operator, which determines whether an object is an instance of a particular class • The syntax of instanceof may be counterintuitive; the keyword does not use camel case and there are no parentheses around the class name • The next slide shows a simple demo of instanceof Instanceof package demos; public class Demo { Object myObject; public void setMyObject(Object o) { myObject = o; } public Object getMyObject() { return myObject; } public static void main(String[] args) { Demo demo = new Demo(); String newObject = new String("John"); demo.setMyObject(newObject); Object o = demo.getMyObject(); if (o instanceof String) System.out.println("\"" + o + "\"" + " is a String!"); else System.out.println("not a String!"); Object arch = new Archimedes(); demo.setMyObject(arch); Object o2 = demo.getMyObject(); if (o2 instanceof String) System.out.println("\"" + 02 + "\"" + " is a String!"); else System.out.println(o2 + " is not a String!"); } }