Received: from SOUTH-STATION-ANNEX.MIT.EDU by po10.MIT.EDU (5.61/4.7) id AA00507; Tue, 14 Mar 00 16:07:54 EST Received: from hermes.javasoft.com by MIT.EDU with SMTP id AA28359; Tue, 14 Mar 00 16:07:37 EST Received: (from nobody@localhost) by hermes.java.sun.com (8.9.3+Sun/8.9.1) id VAA09934; Tue, 14 Mar 2000 21:03:24 GMT Date: Tue, 14 Mar 2000 21:03:24 GMT Message-Id: <200003142103.VAA09934@hermes.java.sun.com> X-Authentication-Warning: hermes.java.sun.com: Processed from queue /bulkmail/data/ed_67/mqueue2 X-Mailing: 195 From: JDCTechTips@sun.com Subject: JDC Tech Tips March 14, 2000 To: JDCMember@sun.com Reply-To: JDCTechTips@sun.com Errors-To: bounced_mail@hermes.java.sun.com Precedence: junk Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii X-Mailer: Beyond Email 2.2 J D C T E C H T I P S TIPS, TECHNIQUES, AND SAMPLE CODE WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, March 14, 2000. This issue covers: * Using java.lang.Class * Overload Resolution These tips were developed using Java(tm) 2 SDK, Standard Edition, v 1.2.2. You can view this issue of the Tech Tips on the Web at http://developer.java.sun.com/developer/TechTips/2000/tt0314.html. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING JAVA.LANG.CLASS In object-oriented programming, a class is a user-defined type, with a set of methods that operate on instances (objects) of the class. For example, you might have a class Point in an application: public class Point { int x; int y; } and the application operates on instances of Point, like (37,47) and (153,89). java.lang.Class is one of the standard classes in the Java programming language, and instances of java.lang.Class represent other classes and interfaces. This tip could use Class to refer to java.lang.Class, but that terminology might be confusing. So you'll see java.lang.Class throughout the discussion. java.lang.Class gives you the ability to find out information about classes that are currently loaded into the Java(tm) Virtual Machine* (JVM). It also gives you a way to dynamically load additional classes into your application. Here's an example, it's a short application that uses java.lang.Class features: public class ClassDemo1 { public static void main(String args[]) { Class cls = null; // load a class by name (like java.util.ArrayList) try { cls = Class.forName(args[0]); } catch (ClassNotFoundException exc) { System.err.println(exc); } // display the hierarchy back to java.lang.Object do { System.out.println(cls); cls = cls.getSuperclass(); } while (cls != null); } } This program uses Class.forName to load a class into the JVM; if the class is already loaded, it finds the java.lang.Class instance that represents the class. Then the program calls Class.getSuperclass repeatedly to find the class's superclass. The result of this process is the class hierarchy for a given class. For example, if you invoke the program with an argument of "java.util.ArrayList", the output is: class java.util.ArrayList class java.util.AbstractList class java.util.AbstractCollection class java.lang.Object which is the class hierarchy of ArrayList up through java.lang.Object. In other words, you can find out about the class properties of ArrayList from within a running program. java.lang.Class is a class whose instances represent other Java classes and interfaces, such as ArrayList, String, and Cloneable. java.lang.Class doesn't have a public constructor, so you can't say: Class c = new Class(...); Instead, you obtain java.lang.Class instance references using Class.forName as illustrated above. Class.forName is used to load a class at run time, based on a string containing the class name (such as "java.util.ArrayList"). Another way you can obtain a java.lang.Class instance reference is to use the ".class" notation, as in: Class c = java.lang.String.class; Because Java arrays fit into the class hierarchy rooted at java.lang.Object, you can also say: Class c = int[].class; A final way to obtain a java.lang.Class reference is through an object reference. In this case, you say: Class c = obj.getClass(); or: int vec[] = new int[10]; Class c = vec.getClass(); Once you have a java.lang.Class instance reference, you can create new instances of the class that it represents. Here's an example: public class ClassDemo2 { public static void main(String args[]) { Class cls = null; Object obj = null; // load a class by name try { cls = Class.forName(args[0]); } catch (ClassNotFoundException exc) { System.err.println(exc); } // create a new instance of that class try { obj = cls.newInstance(); } catch (IllegalAccessException exc1) { System.err.println(exc1); } catch (InstantiationException exc2) { System.err.println(exc2); } } } Class.newInstance is used to create new instances of the class represented by the java.lang.Class instance. It assumes the existence of a default (no parameter) constructor for the class. You could ask why you can't simply use "new" to create a new class instance, and not go to all the trouble of using java.lang.Class and newInstance. The key point about the above approach is that it's dynamic; you can load classes by name into a program and create new instances of these classes. If you load classes using a string name, like "pkg.classname", then you might wonder how the loaded classes are manipulated. One way is to program in terms of interfaces. Suppose that you have an interface A, and two classes B and C that implement that interface. Class B uses an implementation that is the most space efficient, while class C uses more space but runs faster than B. You can dynamically load B or C, but then program in terms of the interface A. So the line above that reads: obj = cls.newInstance(); might better read: A aref = null; ... load B or C by name ... aref = (A)cls.newIntance(); A complete example looks like this: // file A.java public interface A { void f(); } // file B.java public class B implements A { public void f() { System.out.println("B.f"); } } // file C.java public class C implements A { public void f() { System.out.println("C.f"); } } // file ClassDemo3.java public class ClassDemo3 { public static void main(String args[]) { A aref = null; Class cls = null; // load a class (B or C) try { cls = Class.forName(args[0]); } catch (ClassNotFoundException exc) { System.err.println(exc); } // create a new instance of that class try { aref = (A)cls.newInstance(); } catch (IllegalAccessException exc1) { System.err.println(exc1); } catch (InstantiationException exc2) { System.err.println(exc2); } // call f() in the loaded class aref.f(); } } If you say: $ java ClassDemo3 C then the output is: C.f that is, the f method in the loaded class C is called through the interface reference. In ClassDemo1 above, getSuperclass finds the superclass of a class represented by a java.lang.Class instance. There are other query methods that you can use with java.lang.Class. For example, a common one is illustrated by this program: public class ClassDemo4 { public static void classify(Object obj) { if (obj == null) { System.out.println("null"); } else if (obj.getClass().isArray()) { System.out.println("array"); } else { System.out.println("non-array"); } } public static void main(String args[]) { classify(null); classify(new int[10]); classify(new String()); } } In this example there's an object reference passed to the classify method, and you'd like to know whether it represents an array. A final area worth mentioning is reflection. The Java system defines a separate package for reflection (java.lang.reflect), but it relies on methods found in java.lang.Class. One way to distinguish java.lang.reflect facilities from those in java.lang.Class is to note that the java.lang.reflect operates on individual fields and methods in a class instance. java.lang.Class is concerned with a class as a whole unit. For example, this program displays a list of all the public methods found in String: import java.lang.reflect.*; public class ClassDemo5 { public static void main(String args[]) { Class cls = java.lang.String.class; Method methlist[] = cls.getMethods(); for (int i = 0; i < methlist.length; i++) System.out.println(methlist[i]); } } java.lang.reflect.Method is a class found in the reflection package. Class.getMethods gets a list of all the methods in the class. You could go deeper into the internals of a method, for example, you could obtain a list of all the method's parameters in a form that could be manipulated within an application. To do that, you would use facilities such as Method.getParameterTypes. In other words, in this simple demo program, the names of all methods and their parameters are displayed. However, to find out which of the methods has a third parameter of type "int", use reflection facilities within java.lang.reflect.Method. java.lang.Class is a powerful mechanism for examining the properties of classes, and for dynamically loading and manipulating those classes. OVERLOAD RESOLUTION Suppose that you're doing some Java programming, and you have code like this: public class OverDemo1 { static void f(float f) {} static void f(double d) {} public static void main(String args[]) { f(37); } } What happens here? It's possible to convert an integer value (37) to either a float or a double, so which of the f methods is called? Or is this program invalid? (The equivalent C++ program gives a compile error.) The answers to these questions relate to "overload resolution," that is, the rules that are applied to determine which method is called when there is a set of identically-named methods. The first rule says that a method is "applicable" in a given overloading case if the number of parameters in the method declaration matches the number of arguments in the method invocation, and the type of each argument can be converted to the type of the corresponding method parameter. You might think that a rule stating that the number of parameters and arguments must match would be obvious, but not necessarily so. In C++, functions are allowed default parameters, so that you can declare a function by saying: void f(int, double = 12.34); and then call it with: f(37); which is converted by the C++ compiler to: f(37, 12.34); The rule about conversions can be illustrated by a slight variation on the first example: public class OverDemo2 { static void f(float f) {} static void f(String s) {} public static void main(String args[]) { f(37); } } In this example, 37 can be converted to a float through a "widening primitive conversion." But there's no corresponding way to convert 37 to a String, so there's no issue of choosing one f method over the other. The compiler chooses f(float). The second rule is that a method declaration must be "accessible," that is, available at the point where the method is invoked. Here is another example: class A { public static void f(float f, double d) {} private static void f(double d, float f) {} } public class OverDemo3 { public static void main(String args[]) { A.f(37, 47); } } At the point where A.f is invoked within main, only one of the f methods from A is accessible; the other one is private and not available outside of the A class. This particular example will trigger an ambiguity error if both f methods are public. Note that it's never an error to simply declare more than one method with the same name but different parameter types. The error occurs at the point of method invocation if the compiler cannot determine which method to invoke. The third and final rule is the trickiest. Suppose that after the first two rules above are applied, there are still two or more methods with the same name that could possibly be called. For example, in the OverDemo1 program above, neither of the two rules already described removes either of the f methods from consideration. Both methods have the right number of parameters, it's possible to convert 37 to a float or a double, and both methods are accessible. So the third rule is to choose the most "specific" method. The rule is: if any method still under consideration has parameter types that are assignable to another method that's also still in play, then the other method is removed from consideration. This process is repeated until no other method can be eliminated. If the result is a single "most specific" method, then that method is called. If there's more than one method left, the call is ambiguous. Suppose that you have the methods: f(float) f(double) In this case, the parameter types for the first method are assignable to the parameter types of the second method, that is, it's legal to say: double = float through a widening primitive conversion. By contrast, saying: float = double is not valid without a cast. Based on this third rule, f(double) is removed from the set of possible methods to call, and therefore f(float) is called. You can confirm this behavior with another demo program: public class OverDemo4 { static void f(float f) {System.out.println("float");} static void f(double d) {System.out.println("double");} public static void main(String args[]) { f(37); } } which prints the value "float" when you run it. Suppose that you have another case, with two methods: f(float, double) f(double, float) and a call: f(37, 47) Here, the first parameter type of the first method (float) can be assigned to the first parameter type of the second method (double). But this doesn't work for the second parameter; you can't assign double to float. If you start with the second parameter and go in reverse order, you find the opposite to be true. That is, the first parameter type of the second method (double) cannot be assigned to the first parameter type of the first method (float). But you can assign the second parameter float to double. So neither of the f methods can be removed from consideration. Therefore the f(37, 47) call is ambiguous. In section 15.11.2.2 of the Java Language Specification there's a short paragraph that helps explain this rule: The informal intuition is that one method declaration is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error. Another example will help to clarify this rule: class A {} class B extends A {} class C extends B {} public class OverDemo5 { static void f(A a) { System.out.println("f(A)"); } static void f(B b) { System.out.println("f(B)"); } public static void main(String args[]) { C cref = new C(); f(cref); } } In this example, f(B) is called, because it is more specific than f(A). Any invocation of f(B) could also be handled by f(A), but the reverse is not true. One final point about overload resolution: it's usually a good idea to avoid being clever with this mechanism, even if you understand it thoroughly. If you have a case where the parameter types are completely distinct, as in: void f(int) void f(String) this usage is good, but a case like: void f(float) void f(double) called with f(37) starts to get tricky and confusing. . . . . . . . . . . . . . . . . . . . . . . . - NOTE The names on the JDC mailing list are used for internal Sun Microsystems(tm) purposes only. To remove your name from the list, see Subscribe/Unsubscribe below. - FEEDBACK Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster@sun.com - SUBSCRIBE/UNSUBSCRIBE The JDC Tech Tips are sent to you because you elected to subscribe when you registered as a JDC member. To unsubscribe from JDC email, go to the following address and enter the email address you wish to remove from the mailing list: http://developer.java.sun.com/unsubscribe.html To become a JDC member and subscribe to this newsletter go to: http://java.sun.com/jdc/ - ARCHIVES You'll find the JDC Tech Tips archives at: http://developer.java.sun.com/developer/TechTips/index.html - COPYRIGHT Copyright 2000 Sun Microsystems, Inc. All rights reserved. 901 San Antonio Road, Palo Alto, California 94303 USA. This document is protected by copyright. For more information, see: http://developer.java.sun.com/developer/copyright.html This issue of the JDC Tech Tips is written by Glen McCluskey. JDC Tech Tips March 14, 2000 * As used on this document, the terms "Java Virtual Machine" or "JVM" mean a virtual machine for the Java platform.