Wu chapter 4: defining your own classes Programmer-defined class: any non-standard class is a programmer-defined class – including every class you have written thus far Classes consist of a set of methods and a set of data that can be manipulated by these methods; when we define a class, we think about the thing we’re going to model, and come up with a list of operations, or tasks, that we want the thing to do. For example, consider a thermometer. This is an instrument that measures temperature, and displays its reading (as a line of mercury or a set of LED digits, for example). The set of operations on a thermometer is rather simple – basically, we just need to display the temperature. Now consider an enhanced thermometer. Instead of just a single display function, it has three – it can display the temperature in degrees Fahrenheit, degrees Celsius, or degrees Kelvin. We could model such a thermometer in Java using a class similar to the following: import java.util.*; // for Random public class BetterTherm { private int kelvinTemp; private private private private final final final final static static static static // temperature in Kelvin int CFACTOR = 273; // int RANGE = 101; // int FCONVI = 32; // double FCONVD = 1.8;// conversion constant for Celsius # of values in range conversion constant for Fahrenheit second constant for F conversion // Constructor: a method for initializing a data member public BetterTherm () { Random temp = new Random(); kelvinTemp = Math.abs(temp.nextInt()); kelvinTemp = kelvinTemp % RANGE + CFACTOR; // initializes the temperature to somewhere between 273K and 373K // the freezing point and boiling point of water, respectively } // return Kelvin temperature public int getKelvin () { return kelvinTemp; } // Convert to Celcius & return public int getCelcius () { return kelvinTemp - CFACTOR; } // Convert to Fahrenheit & return public int getFahrenheit () { int fTemp; // Fahrenheit temperature to be returned fTemp = (int)(FCONVD * getCelcius()) + FCONVI; return fTemp; } } Computer Science I Sheller Wu ch4 - laptop Fall 2005 Page 1 This class differs from those we have created thus far in several ways: There are multiple methods There is no main method There are several constants and one variable that are declared inside the class, but outside any method Before we delve too deeply into the code for this class, let’s examine how it might be used in a program. The class defined below declares three BetterTherm objects, then displays the temperature each one is reading using the three different scales: public class TestTherm { public static void main (String [] args) { BetterTherm bt1, bt2, bt3; bt1 = new BetterTherm(); bt2 = new BetterTherm(); bt3 = new BetterTherm(); System.out.println ("The three thermometers are reading as follows:"); System.out.println ("\tK\tC\tF"); System.out.println ("1)\t" + bt1.getKelvin() + "\t" + bt1.getCelsius() + "\t" + bt1.getFahrenheit()); System.out.println ("2)\t" + bt2.getKelvin() + "\t" + bt2.getCelsius()+ "\t" + bt2.getFahrenheit()); System.out.println ("3)\t" + bt3.getKelvin() + "\t" + bt3.getCelsius() + "\t" + bt3.getFahrenheit()); } } The output looks like this: The three thermometers are reading as follows: K C F 1) 350 77 170 2) 371 98 208 3) 322 49 120 Both the BetterTherm class and the TestTherm class follow the syntactic pattern that should be familiar by now: public class ClassName { // class members defined here } The BetterTherm class has several data members. A member variable or constant is a variable or constant that belongs to the class (or object), but not to any particular method within the class. Because both member data and member methods end up being part of the same object when a class is instantiated, member data items are accessible from all member methods, and a member method can Computer Science I Sheller Wu ch4 - laptop Fall 2005 Page 2 call another member method. Access to data or methods by any other type of objects is restricted by the visibility modifier. Two types of accessibility are used in BetterTherm: public and private. a public member of a class is visible to the world in general; methods are usually public a private member (usually data, but methods can also be private) is accessible only to the methods defined within the class The data members of BetterTherm include a single instance variable, kelvinTemp, and several class constants. An instance variable is a variable belonging to an object – when a BetterTherm object is created, it gets its own unique kelvinTemp variable. As we have already seen, the value of this variable can be different from object to object. The positioning of the declaration – inside the class but outside any method – makes a variable an instance variable, rather than a local variable. A local variable is a variable declared inside a method. The variable fTemp in the getFahrenheit() method is an example of a local variable. A class variable or class constant is one that is shared by all instances of a class; in other words, it exists in just one place in memory, and all objects of the class type are able to access its value. The keyword static is used to declare a class data member (or method). Both class variables and instance variables may be referred to by name in any member method, while a local variable can only be referred to within the method in which it is declared. This restriction on the ability to refer to a variable is called the scope of the variable. In general, the scope of a local variable is limited to its method, while the scope of a class or instance variable is class-wide. Most of the methods defined in the BetterTherm class are value-returning methods. When an object or class receives a message to one of its value-returning methods, it returns a value matching its specified return type to the calling object. The syntax for a method heading is generally: modifier(s) returnType name (parameter(s)) where: Modifiers include visibility modifiers (e.g. public or private) and occasionally other modifiers, such as static. The return type of a method indicates what kind of data value, if any, the method will return when it concludes. In BetterTherm, the getKelvin, getCelsius and getFahrenheit methods all have the return data type int. This means that a message requesting one of these methods can be used in an expression by the calling object. An example from TestTherm: System.out.println ("1)\t" + bt1.getKelvin() + "\t" + bt1.getCelsius() + "\t" + bt1.getFahrenheit()); As we have already seen, this line of code prints out the three return values from the three messages to bt1 from TestTherm. The three "get" methods of the BetterTherm class are all value-returning, so they all contain a statement at the end indicating the value to be returned. The general syntax for a return statement is: return expression; where expression evaluates to a result that is the same data type as the one indicated as the return type in the method heading. Some examples in the BetterTherm class include: Computer Science I Fall 2005 Sheller Page 3 Wu ch4 - laptop return kelvinTemp - CFACTOR; from getCelsius and return fTemp; from getFahrenheit. It is possible that we don’t want the method to return a value. In that case, we use void as the return type. This is the approach we have always used with main(): public static void main (String [] args) { The name of a method is simply a valid identifier that will be used to call the method. The identifier main is a special case, since this method executes automatically when a program starts, and not in response to a message. In all other cases, we use the method’s name in the message that requests its execution. The parameter list indicates what kind(s) of data, if any, are required for the method to accomplish its task. None of the methods defined so in the BetterTherm class requires an argument, so their parameter lists are empty. The parameter list of TestTherm’s main method (like all main methods) contains a single parameter: (String [] args) You should recognize that String is the name of a class from the Java API. The identifier for the parameter is args; the notation [] indicates that args is a special kind of variable called an array, which we will talk about several weeks from now. Here’s what you should know about parameters: Parameters are declared just like variables or objects. The scope of a parameter is the same as the scope of a local variable; in other words, local to its method. This has some added implications: o You can use the same name for local variables and parameters as long as they are in different methods o You shouldn't use the name of a class-wide variable, constant, or object to refer to a local variable, constant or object – this makes the class-wide identifier invisible within the local scope. Consider the example code below: import javax.swing.*; public class JustChecking { private int num; int getNum () { return num; } void setNum (int n) { num = n; } void badSetNum () { int num; String n; n = JOptionPane.showInputDialog (null, "Enter a number:"); num = Integer.parseInt(n); } } Computer Science I Sheller Wu ch4 - laptop Fall 2005 Page 4 The identifier num is used both for the instance variable and a local variable in the badSetNum method. Consider the last line in the badSetNum method: which num (local or class-wide) is assigned a value here? We can find out by writing a program that uses this class, as in the example below: import javax.swing.*; public class TestJC { public static void main (String [] args) { JustChecking jc = new JustChecking(); String s = JOptionPane.showInputDialog (null, "Enter a test value:"); jc.setNum(Integer.parseInt(s)); JOptionPane.showMessageDialog (null, "You entered: " + jc.getNum ()); jc.badSetNum(); JOptionPane.showMessageDialog (null, "You entered: " + jc.getNum ()); } } // 1 // 2 // 3 // 4 A sample run of the program is shown below: Windows 1, 2 and 4 come from lines of code in the main method of TestJC. Window 3 comes from the badSetNum method of JustChecking. The local variable num in badSetNum is set to 333, but the classwide instance variable num retains its original value (1001). If another name had been used for the local variable in badSetNum, then the instruction (in badSetNum): num = Integer.parseInt(n); would have applied to the instance variable. The JustChecking class gives an example of another method that has a parameter. The setNum method takes a single int argument, which is stored in parameter n. This value is then assigned to the instance Computer Science I Sheller Wu ch4 - laptop Fall 2005 Page 5 variable num. The setNum method is an example of void, or non-value-returning method; it is also an example of a modifier, or a method that changes its object. Parameter matching Parameters specify the number, ordering, and data type(s) of the argument(s) that can be passed to a method. When a parameter is one of the simple numeric types, we can pass as an argument to the parameter any expression that is assignment-compatible with the parameter’s specified data type. Recall that expressions can include: o literal values, e.g. -4, 13.02 o initialized variables or constants o messages to value-returning methods (e.g. Integer.parseInt(), Math.sqrt(a)) o arithmetic operators & parentheses o explicit type casts Assignment compatibility means that the value being passed is only valid if it can be assigned to a variable of the parameter’s type. For example, while we can pass an int value or variable as an argument to a double parameter, we cannot pass a double value or variable to an int parameter unless we explicitly cast the argument as an int. Parameters, as we’ve already seen, are variables local to the method they are declared in. As we already know, all variables have two properties: a data type and an identifier. Arguments can have identifiers too. If we pass a variable or constant as an argument, we are passing a named argument. There is no relationship between the name of a parameter and the name of its corresponding argument. This means: o They can have different names (and usually do). o They can have the same name, because they belong to different methods, and thus have different scope. o Consider the code below: public class SimpleClass { private int x; public void setVar (int y) { x = y; } int getVar () { return x; } } import java.util.*; public class TestSimp { public static void main (String [] args) { SimpleClass simp = new SimpleClass(); int x; Scanner kb = new Scanner (System.in); System.out.print ("Enter a whole number: "); x = kb.nextInt(); simp.setVar(x); System.out.println ("Value of SimpleClass’s x = " + simp.getVar()); x += 10; System.out.println ("Value of TestSimp’s x = " + x); System.out.println ("Value of SimpleClass’s x = " + simp.getVar()); simp.setVar(); } } Computer Science I Sheller Wu ch4 - laptop Fall 2005 Page 6 In the first call to setVar, argument x’s value is passed to the method’s parameter y; this value is then assigned to SimpleClass’s x variable. When the x variable’s value changes in TestSimp’s main method, the change has no effect on x or y in SimpleClass. Class constructors The BetterTherm class has one more method we haven’t discussed yet. It looks like this: public BetterTherm () { Random temp = new Random(); kelvinTemp = Math.abs(temp.nextInt()); kelvinTemp = kelvinTemp % RANGE + CFACTOR; // initializes the temperature to somewhere between 273K and 373K // the freezing point and boiling point of water, respectively } This method is called a constructor; its purpose is to initialize instance variables to reasonable values when a new object is instantiated. Characteristics of constructors: o identifier is the same as the class’s (so BetterTherm’s constructor is named BetterTherm) o a call to a constructor occurs when the new operator is invoked – so, for example, the following lines of code in TestTherm: BetterTherm bt1, bt2, bt3; bt1 = new BetterTherm(); bt2 = new BetterTherm(); bt3 = new BetterTherm(); represent 3 calls to BetterTherm’s constructor. Accessors, constructors and mutators Most classes contain three kinds of methods: o Constructors, as we’ve seen, initialize data members of new objects. o Accessors report on the state of an object, usually by returning a data item from the object; in all the classes we’ve used as examples, the “get” methods are accessors. o Mutators are those methods that change the state of an object; this usually means they assign new value(s) to member variable(s). The “set” methods of the example classes are mutators. Since data members are usually declared private, the only access to the data in an object is via the class methods. This protects data integrity by ensuring that there are no “back door” mechanisms by which a client programmer can tamper with the data in an object. As originally written, the BetterTherm class had no mutators; once the temperature value was set by the constructor, the reading will always be the same. Of course, if we had a real thermometer, a change in temperature in the environment would change the temperature reading on the gauge. We don’t have a mechanism to simulate environmental conditions (yet), but we can at least provide some methods to manually set the temperature. To provide maximum flexibility, we will allow the temperature to be set using a value representing any of the three scales, then we’ll have the method convert the temperature to Kelvin (if necessary). The new methods would appear in the public section of the class: Computer Science I Sheller Wu ch4 - laptop Fall 2005 Page 7 public void setKtemp (int k) { kelvinTemp = Math.abs(k); } public void setCtemp (int c) { kelvinTemp = c + CFACTOR; } public void setFtemp (int f) { int cTemp = (int)((f - FCONVI) / FCONVD); kelvinTemp = cTemp + CFACTOR; } One more code example: import java.text.*; import java.util.*; import javax.swing.*; public class BigSign { private Date time; private BetterTherm temp; private String adMsg; private String jokeMsg; public BigSign () { time = new Date(); temp = new BetterTherm(); adMsg = new String ("Buy this now!"); jokeMsg = new String ("Time flies like an arrow,\n" + "fruit flies like a banana"); } public void displaySign() { SimpleDateFormat sdf = new SimpleDateFormat ("hh:mm"); JOptionPane.showMessageDialog(null, adMsg); JOptionPane.showMessageDialog(null, "" + sdf.format(time) + " temp.getFahrenheit()); JOptionPane.showMessageDialog(null, jokeMsg); } " + public void setAdMsg (String newAdMsg) { adMsg = newAdMsg; } public void setJokeMsg (String newJokeMsg) { jokeMsg = newJokeMsg; } public void setTime (Date t) { time = t; } Computer Science I Sheller Wu ch4 - laptop Fall 2005 Page 8 public void setTemp (BetterTherm t) { temp = t; } public static void main (String [] args) { BigSign mySign = new BigSign(); Random randGen = new Random(); Date newTime = new Date((long)(randGen.nextInt())); BetterTherm bt = new BetterTherm(); mySign.displaySign(); mySign.setAdMsg("Read this sign!"); mySign.setJokeMsg("Why did the pig cross the road?\n" + "He was stapled to the chicken"); mySign.setTime(newTime); mySign.setTemp(bt); mySign.displaySign(); System.out.println(mySign.toString()); } } The BigSign class illustrates several principles: Objects can be declared as class instance variables, parameters, or local variables, just as simple type variables can. BigSign contains an instance variable of type BetterTherm (as well as a Date object and two String objects). Because the class models an electronic sign, its most commonly-used method would probably be its displaySign method, which displays (using message windows), an advertisement, the time and temperature, and a joke. There is another method that can be used for display: the toString method, which converts the values of all of the instance variables into a single concatenated String object. This method is a sort of utility for client programmers; it provides access to all of a signs “contents” without making it necessary to display those contents. It would be possible, using String member methods, to parse this String and extract relevant parts, discarding the rest. Alternatively, we could have included “get” messages, which could have given access to the various parts without the necessity of String parsing. The class contains a main method, which demonstrates the use of the other methods. We could still create a separate client class, as we have done with the other example classes, and that client could have its own main method. In Java, any class can have a main method, and this can come in handy for testing a class even if its primary use will be as a server class. Computer Science I Sheller Wu ch4 - laptop Fall 2005 Page 9