NetBeans 7.1's Internal Compiler and JDK 6 Respecting Return Type for Method Overloading
I was first exposed to Java after several years of C++ experience and so it seemed natural when I learned that Java does not allow method overloading based on return type. The Defining Methodssection of the Classes and Objects lesson in the Java Language Tutorial states, "The compiler does not consider return type when differentiating methods, so you cannot declare two methods with the same signature even if they have a different return type." Indeed, as Vinit Joglekar has pointed out, "It is an accepted fact that Java does not support return-type-based method overloading." TheStackOverflow thread Java - why no return type based method overloading? explains why this is the case in Java. Given this, I was surprised when a colleague showed me a code snippet with two overloaded methods with the same runtime signature that compiled in JDK 6 as long as the return types differed.
The following class compiles successfully with JDK 6, but not with JDK 7.
Compiles in JDK 6 But Not in JDK 7
As described in
Angelika Langer's What Is Method Overloading?, the above code should not compile. It doesn't in Java SE 7. In NetBeans 7.1, it doesn't. Or, more properly, it's a mixed bag.
As the screen snapshot below demonstrates, NetBeans 7.1 builds the source code above fine as shown in the Output Window when the version of Java associated with the project is Java SE 6. However, the NetBeans editor shows the red squiggly lines indicating compiler error. The next image shows what the error message is.
Although NetBeans 7.1 is able to build the code shown above when it's part of a project associated with Java SE 6 (Update 31 in this case), the code editor still reports the error shown above. This is because NetBeans uses a different version of the Java compiler internally than the one explicitly associated with the project being edited. If I change the version of Java associated with the NetBeans project for the source code above, it will no longer build in NetBeans. This is shown next.
There are a couple interesting things about this bug. First, the fact that this code compiles fine in Java SE 6 but is addressed and does not compile in Java SE 7 means that it is possible for code working in Java SE 6 to not work when the code base is moved to Java SE 7. I downloaded the latest version of JDK 6 available (Java SE 6 Update 31) and confirmed the original code shown above still builds in Java SE 6. It does not build in Java SE 7.
There are other versions of the code above that do not build in Java SE 6 or in Java SE 7. For example, if the code above is changed so that the methods return the same type, the code doesn't build even in Java SE 6. Similarly, if the Collection parameters to the two overloaded methods include a "raw" Collection (no parameterized type), it won't compile in Java SE 6 either. Of course, even if the return types are different, if the same Collection parameterized types are passed to both overloaded methods, even Java SE 6 won't compile this. These three situation are depicted in the following three screen snapshots.
The code that builds in Java SE 6 but not in Java SE 7 needs to have overloaded methods that differ in both return types and in terms of the parameterized types of the collections that make up their method parameters. It doesn't matter if a given return type matches or is related to the parameterized type of the method's parameter as long as they differ. If the return types are the same, Java SE 6 detects a compiler error. Java SE 6 also detects the error if the erased parameters boil down to the same collection after erasure and the return types are not different.
A second interesting thing about this bug is how its handled in NetBeans. Because NetBeans use itsown internal compiler that does not necessarily match the version of the compiler that the developer has associated the IDE project to, you can run into situations like this where the code actually builds in the IDE, but the IDE's functionality such as code editors and project browsers indicate the code breaking.
Because NetBeans 7.1 uses its own internal Java compiler for the code editor, one might wonder if this means Java 7 features could be sneaked in and would work in the IDE but then would not build when attempted from the command line or when explicitly built in the IDE. The next screen snapshot demonstrates why that is not the case. In that snapshot, a Java 7 specific feature is in the code and NetBeans 7.1 properly warns that this is not compatible with the Java 1.6 source setting.
Bug 6182950 (methods clash algorithm should not depend on return type) has addressed the issue in JDK 7, but not in JDK 6. A related bug is Bug 6730568 ("Type erasure affects return types + type parameters"). Three additional references that provide sufficiently more background details are twoStackOverflow threads (Differing behaviour between Java 5 & 6 when overloading generic methodsand What is the concept of erasure in generics in java?) and the Java Tutorial entry on Type Erasure.
The colleague who showed me this issue realized its existence because NetBeans 7.1 reported the "name clash ... have the same erasure" even when he was working with Java SE 6 code. This discovery was "accidental" due to the newer version of NetBeans using Java SE 7 compiler internally, but he welcomed the opportunity to fix the issue now rather than when he migrates to Java SE 7.
I found this issue worth posting a blog post on because it provides a warning about a bug that may already be in some Java SE 6 code bases but will be made all too evident when the code base is moved to Java SE 7. I also posted this because I think it's important to be aware that modern versions of NetBeans use an internal compiler that may be of a different version than the compiler the developer has explicitly associated with his or her NetBeans project.
The following class compiles successfully with JDK 6, but not with JDK 7.
Compiles in JDK 6 But Not in JDK 7
- package examples.dustin;
- import java.util.Collection;
- /**
- * Simple example that breaks in Java SE 7, but not in Java SE 6.
- *
- * @author Dustin
- */
- public class Main
- {
- public static String[] collectionToArray(final Collection<String> strings)
- {
- return new String[] { "five" };
- }
- public static int[] collectionToArray(final Collection<Integer> integers)
- {
- return new int[] { 5 };
- }
- /**
- * Main function.
- *
- * @param arguments The command line arguments; none expected.
- */
- public static void main(String[] arguments)
- {
- }
- }
As the screen snapshot below demonstrates, NetBeans 7.1 builds the source code above fine as shown in the Output Window when the version of Java associated with the project is Java SE 6. However, the NetBeans editor shows the red squiggly lines indicating compiler error. The next image shows what the error message is.
Although NetBeans 7.1 is able to build the code shown above when it's part of a project associated with Java SE 6 (Update 31 in this case), the code editor still reports the error shown above. This is because NetBeans uses a different version of the Java compiler internally than the one explicitly associated with the project being edited. If I change the version of Java associated with the NetBeans project for the source code above, it will no longer build in NetBeans. This is shown next.
There are a couple interesting things about this bug. First, the fact that this code compiles fine in Java SE 6 but is addressed and does not compile in Java SE 7 means that it is possible for code working in Java SE 6 to not work when the code base is moved to Java SE 7. I downloaded the latest version of JDK 6 available (Java SE 6 Update 31) and confirmed the original code shown above still builds in Java SE 6. It does not build in Java SE 7.
There are other versions of the code above that do not build in Java SE 6 or in Java SE 7. For example, if the code above is changed so that the methods return the same type, the code doesn't build even in Java SE 6. Similarly, if the Collection parameters to the two overloaded methods include a "raw" Collection (no parameterized type), it won't compile in Java SE 6 either. Of course, even if the return types are different, if the same Collection parameterized types are passed to both overloaded methods, even Java SE 6 won't compile this. These three situation are depicted in the following three screen snapshots.
The code that builds in Java SE 6 but not in Java SE 7 needs to have overloaded methods that differ in both return types and in terms of the parameterized types of the collections that make up their method parameters. It doesn't matter if a given return type matches or is related to the parameterized type of the method's parameter as long as they differ. If the return types are the same, Java SE 6 detects a compiler error. Java SE 6 also detects the error if the erased parameters boil down to the same collection after erasure and the return types are not different.
A second interesting thing about this bug is how its handled in NetBeans. Because NetBeans use itsown internal compiler that does not necessarily match the version of the compiler that the developer has associated the IDE project to, you can run into situations like this where the code actually builds in the IDE, but the IDE's functionality such as code editors and project browsers indicate the code breaking.
Because NetBeans 7.1 uses its own internal Java compiler for the code editor, one might wonder if this means Java 7 features could be sneaked in and would work in the IDE but then would not build when attempted from the command line or when explicitly built in the IDE. The next screen snapshot demonstrates why that is not the case. In that snapshot, a Java 7 specific feature is in the code and NetBeans 7.1 properly warns that this is not compatible with the Java 1.6 source setting.
Bug 6182950 (methods clash algorithm should not depend on return type) has addressed the issue in JDK 7, but not in JDK 6. A related bug is Bug 6730568 ("Type erasure affects return types + type parameters"). Three additional references that provide sufficiently more background details are twoStackOverflow threads (Differing behaviour between Java 5 & 6 when overloading generic methodsand What is the concept of erasure in generics in java?) and the Java Tutorial entry on Type Erasure.
The colleague who showed me this issue realized its existence because NetBeans 7.1 reported the "name clash ... have the same erasure" even when he was working with Java SE 6 code. This discovery was "accidental" due to the newer version of NetBeans using Java SE 7 compiler internally, but he welcomed the opportunity to fix the issue now rather than when he migrates to Java SE 7.
I found this issue worth posting a blog post on because it provides a warning about a bug that may already be in some Java SE 6 code bases but will be made all too evident when the code base is moved to Java SE 7. I also posted this because I think it's important to be aware that modern versions of NetBeans use an internal compiler that may be of a different version than the compiler the developer has explicitly associated with his or her NetBeans project.
A Plethora of Java Developments in February 2012
There are several sites (Java.net, JavaWorld, JavaLobby/DZone, Java reddit, and Java Code Geeks) that I like to browse for the latest Java news. These sites are great and bring the best from around the web from the wider Java community. A nice complement to these sites is Oracle Technology Network'sJava page. There are several Java stories available on the OTN Java page that originate from Oracle and its employees that are of interest to Java developers. I briefly summarize and link to a subset of these in this blog post.
New Java Language and Java VM SpecificationsAlex Buckley's post JLS7 and JVMS7 online announces the availability of new versions of the Java Language Specification and of the Java Virtual Machine Specification. Besides announcing the availability of these new specifications associated explicitly with Java SE 7, the post also provides some interesting background regarding the history of these two specifications. For example, Buckley states, "Only a major Java SE release can change the Java language and JVM." I also find it interesting that these specifications no longer have names based on their edition (was Third Edition for JLS and Second Edition for JVMS). Instead, these two specifications are now named for the edition of Java SE they are associated with. To me, that's much clearer. You may wonder why this wasn't done in the first place and Buckley explains that, "Historically, the JLS and JVMS pre-date the Java Community Process so there was no Java SE platform to which they could be tied." The specifications are available in HTML or PDF format and it is anticipated that they will be published in printed book format in the future.
Java SE 6 End of Life ExtendedHenrik Stahl uses the post Updated Java 6 EOL date to announce that the JDK6 "EOL date has been extended from July 2012 to November 2012, to allow some more time for the transition to JDK 7." He also highlights portions of the updated EOL policy. The Oracle Java SE Support Roadmap (AKA "Java SE EOL Policy") was updated on 15 February 2012 with this new EOL date.
New Java UpdatesThe Java SE News blog contains posts regarding newly available Java updates. The titles of the posts say it all: Java 7 Update 3 and Java 6 Update 31 have released!, 7u4 Developer Preview is now Available, and 6u32 Developer Preview is now Available.
JSR 354: Money and Currency APIThe JCP Program Office blog features a post announcing JSR 354: Money and Currency API. This JSR proposal describes deficiencies with the already available java.util.Currency class that will be addressed by the JSR. The "proposed Specification" section states:
JavaFX 2 Developer CommunityNicolas Lorain writes in JavaFX 2 and the developer community that "JavaFX 2 was only released in October 2011, but there's already a thriving developer community kicking the tires of the new kid on the block." He adds, "There's no denying that we've pretty much started from scratch with JavaFX 2." Lorain then provides evidence of the growing JavaFX 2 community that includes increasing number of discussion threads on the JavaFX 2.0 and Later forum, the developer community contributing roughly 20% of the bug reports related to JavaFX, an "increasing number of people interested in JavaFX are following me" (@javafx4you), and number of community blog posts on JavaFX (references JavaFX Links of the Week). Lorain concludes, "pretty much all the [metrics] I've seen show that JavaFX is growing in terms of popularity."
Incidentally, one of the co-authors of Pro JavaFX 2: A Definitive Guide to Rich Clients with Java Technology has provided some details about that book which will soon be in print and is already available in electronic format.
ConclusionThe Java development community seems more lively and more energetic in recent months (especially since JavaOne 2011) than it has been for years. After years of seeming stagnation, Java-related developments appear to be coming at us more quickly again. It is nice to have so many online forums to get information about these developments.
New Java Language and Java VM SpecificationsAlex Buckley's post JLS7 and JVMS7 online announces the availability of new versions of the Java Language Specification and of the Java Virtual Machine Specification. Besides announcing the availability of these new specifications associated explicitly with Java SE 7, the post also provides some interesting background regarding the history of these two specifications. For example, Buckley states, "Only a major Java SE release can change the Java language and JVM." I also find it interesting that these specifications no longer have names based on their edition (was Third Edition for JLS and Second Edition for JVMS). Instead, these two specifications are now named for the edition of Java SE they are associated with. To me, that's much clearer. You may wonder why this wasn't done in the first place and Buckley explains that, "Historically, the JLS and JVMS pre-date the Java Community Process so there was no Java SE platform to which they could be tied." The specifications are available in HTML or PDF format and it is anticipated that they will be published in printed book format in the future.
Java SE 6 End of Life ExtendedHenrik Stahl uses the post Updated Java 6 EOL date to announce that the JDK6 "EOL date has been extended from July 2012 to November 2012, to allow some more time for the transition to JDK 7." He also highlights portions of the updated EOL policy. The Oracle Java SE Support Roadmap (AKA "Java SE EOL Policy") was updated on 15 February 2012 with this new EOL date.
New Java UpdatesThe Java SE News blog contains posts regarding newly available Java updates. The titles of the posts say it all: Java 7 Update 3 and Java 6 Update 31 have released!, 7u4 Developer Preview is now Available, and 6u32 Developer Preview is now Available.
JSR 354: Money and Currency APIThe JCP Program Office blog features a post announcing JSR 354: Money and Currency API. This JSR proposal describes deficiencies with the already available java.util.Currency class that will be addressed by the JSR. The "proposed Specification" section states:
This JSR will provide a money and currency API for Java, targeted at all users of currencies and monetary amounts in Java. The API will provide support for standard ISO-4217 and custom currencies, and a representation of a monetary amount. It will support currency arithmetic, even across different currencies, and will support foreign currency exchange. Additionally, implementation details surrounding serialization and thread safety are to be considered.It sounds like there is some optimism about this making it into Java SE 8.
JavaFX 2 Developer CommunityNicolas Lorain writes in JavaFX 2 and the developer community that "JavaFX 2 was only released in October 2011, but there's already a thriving developer community kicking the tires of the new kid on the block." He adds, "There's no denying that we've pretty much started from scratch with JavaFX 2." Lorain then provides evidence of the growing JavaFX 2 community that includes increasing number of discussion threads on the JavaFX 2.0 and Later forum, the developer community contributing roughly 20% of the bug reports related to JavaFX, an "increasing number of people interested in JavaFX are following me" (@javafx4you), and number of community blog posts on JavaFX (references JavaFX Links of the Week). Lorain concludes, "pretty much all the [metrics] I've seen show that JavaFX is growing in terms of popularity."
Incidentally, one of the co-authors of Pro JavaFX 2: A Definitive Guide to Rich Clients with Java Technology has provided some details about that book which will soon be in print and is already available in electronic format.
ConclusionThe Java development community seems more lively and more energetic in recent months (especially since JavaOne 2011) than it has been for years. After years of seeming stagnation, Java-related developments appear to be coming at us more quickly again. It is nice to have so many online forums to get information about these developments.
Generating XML Schema with schemagen and Groovy
I have previously blogged on several utilitarian tools that are provided with the Java SE 6 HotSpot SDK such as jstack, javap, and so forth. I focus on another tool in the same
We'll begin with a simple Java class called
The class above is very simple, but is adequate for the first example of employing
The next screen snapshot demonstrates the most simple use of
The generated XSD is shown next.
schema1.xsd
This is pretty convenient, but is even easier with Groovy. Suppose that one wanted to generate an XSD using schemagen and did not care about or need the original Java class. The following very simple Groovy class could be used. Very little effort is required to write this, but it's compiled
When the above Groovy class is compiled with groovyc, its resulting
When the
Because I did not explicitly state that Groovy's automatic get/set methods should not be applied, all attributes are represented in the XML. Very little Groovy, but XSD nonetheless.
It is interesting to see what happens when the attributes of the Groovy class are untyped. The next Groovy class listing does not explicitly type the class attributes.
When schemagen is run against the above class with untyped attributes, the output XSD looks like this:
Not surprisingly, the Groovy class with the untyped attributes leads to an XSD with elements ofanyType. It is remarkably easy to generate Schema with
Groovy makes it really easy to generate an XSD. The Groovy code required to do so is barely more than a list of attributes and their data types.
Conclusion
The schemagen tool is a highly useful tool most commonly used in conjunction with web services and with JAXB, but I have found several instances where I have needed to create a "quick and dirty" XSD file for a variety of purposes. Taking advantage of Groovy's automatically generated set/get methods and other Groovy conciseness makes it really easy to generate a simple XSD.
$JAVA_HOME/bin
(or%JAVA_HOME%\bin
directory: schemagen. Although schemagen
is typically used in conjunction with web services and/or JAXB, it can be useful in other contexts as well. Specifically, it can be used as an easy way to create a starting point XML Schema Definition (XSD) for someone who is more comfortable with Java than with XML Schema.We'll begin with a simple Java class called
Person
to demonstrate the utility of schemagen
. This is shown in the next code listing.- package dustin.examples;
- public class Person
- {
- private String lastName;
- private String firstName;
- private char middleInitial;
- private String identifier;
- /**
- * No-arguments constructor required for 'schemagen' to create XSD from
- * this Java class. Without this "no-arg default constructor," this error
- * message will be displayed when 'schemagen' is attempted against it:
- *
- * error: dustin.examples.Person does not have a no-arg default
- * constructor.
- */
- public Person() {}
- public Person(final String newLastName, final String newFirstName)
- {
- this.lastName = newLastName;
- this.firstName = newFirstName;
- }
- public Person(
- final String newLastName,
- final String newFirstName,
- final char newMiddleInitial)
- {
- this.lastName = newLastName;
- this.firstName = newFirstName;
- this.middleInitial = newMiddleInitial;
- }
- public String getLastName()
- {
- return this.lastName;
- }
- public void setLastName(final String newLastName)
- {
- this.lastName = newLastName;
- }
- public String getFirstName()
- {
- return this.firstName;
- }
- public void setFirstName(final String newFirstName)
- {
- this.firstName = newFirstName;
- }
- public char getMiddleInitial()
- {
- return this.middleInitial;
- }
- }
The class above is very simple, but is adequate for the first example of employing
schemagen
. As the comment on the no-arguments constructor in the above code states, a constructor without arguments (sometimes called a "default constructor") must be available in the class. Because other constructors are in this class, it is required that a no-args constructor be explicitly specified. I also intentionally provided get/set (accesor/mutator) methods for some of the fields, only an accessor for one of the fields, and neither for a field to demonstrate that schemagen
requires get/set methods to be specified if the schema it generates includes a reference to those attributes.The next screen snapshot demonstrates the most simple use of
schemagen
in which the generated XML schema file (.xsd
) is generated with the default name of schema1.xsd
(there is no current way to control this directly with schemagen) and is placed in the same directory from which the schemagen
command is run (output location can be dictated with the -d
option).The generated XSD is shown next.
schema1.xsd
- <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
- <xs:complexType name="person">
- <xs:sequence>
- <xs:element name="firstName" type="xs:string" minOccurs="0"/>
- <xs:element name="lastName" type="xs:string" minOccurs="0"/>
- </xs:sequence>
- </xs:complexType>
- </xs:schema>
This is pretty convenient, but is even easier with Groovy. Suppose that one wanted to generate an XSD using schemagen and did not care about or need the original Java class. The following very simple Groovy class could be used. Very little effort is required to write this, but it's compiled
.class
file can be used with schemagen
.- package dustin.examples;
- public class Person2
- {
- String lastName;
- String firstName;
- char middleInitial;
- String identifier;
- }
When the above Groovy class is compiled with groovyc, its resulting
Person2.class
file can be viewed through another useful tool (javap) located in the same directory as schemagen
. This is shown in the next screen snapshot. The most important observation is that get/set methods have been automatically generated by Groovy.When the
groovyc
-generated .class
file is run through schemagen
, the XSD is generated and is shown next.- <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
- <xs:complexType name="person2">
- <xs:sequence>
- <xs:element name="firstName" type="xs:string" minOccurs="0"/>
- <xs:element name="identifier" type="xs:string" minOccurs="0"/>
- <xs:element name="lastName" type="xs:string" minOccurs="0"/>
- <xs:element name="middleInitial" type="xs:unsignedShort"/>
- </xs:sequence>
- </xs:complexType>
- </xs:schema>
Because I did not explicitly state that Groovy's automatic get/set methods should not be applied, all attributes are represented in the XML. Very little Groovy, but XSD nonetheless.
It is interesting to see what happens when the attributes of the Groovy class are untyped. The next Groovy class listing does not explicitly type the class attributes.
- package dustin.examples;
- public class Person2
- {
- def lastName;
- def firstName;
- def middleInitial;
- def identifier;
- }
When schemagen is run against the above class with untyped attributes, the output XSD looks like this:
- <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
- <xs:complexType name="person2">
- <xs:sequence>
- <xs:element name="firstName" type="xs:anyType" minOccurs="0"/>
- <xs:element name="identifier" type="xs:anyType" minOccurs="0"/>
- <xs:element name="lastName" type="xs:anyType" minOccurs="0"/>
- <xs:element name="middleInitial" type="xs:anyType" minOccurs="0"/>
- </xs:sequence>
- </xs:complexType>
- </xs:schema>
Not surprisingly, the Groovy class with the untyped attributes leads to an XSD with elements ofanyType. It is remarkably easy to generate Schema with
schemagen
from a Groovy class, but what if I don't want an attribute of the class to be part of the generated schema? Explicitly specifying an attribute as private
communicates to Groovy to not automatically generate get/set methods and hence schemagen
will not generate XSD elements for those attributes. The next Groovy class shows two attributes explicitly defined as private
and the resultant XSD from running schemagen
against the compiled Groovy class is then shown.- package dustin.examples;
- public class Person2
- {
- String lastName;
- String firstName;
- /** private modifier prevents auto Groovy set/get methods */
- private String middleInitial;
- /** private modifier prevents auto Groovy set/get methods */
- private String identifier;
- }
- <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
- <xs:complexType name="person2">
- <xs:sequence>
- <xs:element name="firstName" type="xs:string" minOccurs="0"/>
- <xs:element name="lastName" type="xs:string" minOccurs="0"/>
- </xs:sequence>
- </xs:complexType>
- </xs:schema>
Groovy makes it really easy to generate an XSD. The Groovy code required to do so is barely more than a list of attributes and their data types.
Conclusion
The schemagen tool is a highly useful tool most commonly used in conjunction with web services and with JAXB, but I have found several instances where I have needed to create a "quick and dirty" XSD file for a variety of purposes. Taking advantage of Groovy's automatically generated set/get methods and other Groovy conciseness makes it really easy to generate a simple XSD.
Groovy, JMX, and the Attach API
One of the exciting new features of the (then Sun) HotSpot Java SE 6 release was support for theAttach API. The Attach API is "a Sun Microsystems extension that provides a mechanism to attach to a Java virtual machine." Tools such as JConsole and VisualVM "use the Attach API to attach to a target virtual machine and load its tool agent into that virtual machine." Custom Java and Groovy clients can likewise use the Attach API to monitor and manage JVMs.
There are several online resources that demonstrate Java client code that uses the Attach API. These include Daniel Fuchs's code listing for a JVMRuntimeClient, the "Setting up Monitoring and Management Programmatically" section of "Monitoring and Management Using JMX Technology," theCore Java Technology Tech Tip called "The Attach API," and the Javadoc API documentation for the class
All of the previously mentioned examples demonstrate use of the Attach API from Java clients. In this post, I demonstrate use of the Attach API via Groovy. The three code listings that follow present three pieces of Groovy code but all work together as a single script. The main script is embodied in the Groovy file
getJvmDetails.groovy
attachToVirtualMachine.groovy
displayMxBeanDerivedInfo.groovy
The three Groovy code listings above together form a script that will use the Attach API to contact to an executing JVM without host or port specified and solely based on the provided process ID. The examples demonstrate use of several of the available MXBeans built into the virtual machine. Because it's Groovy, the code is somewhat more concise than its Java equivalent, especially because no checked exceptions must be explicitly handled and there is no need for explicit classes.
Much more could be done with the information provided via the Attach API and the MXBeans. For example, the Groovy script could be adjusted to persist some of the gathered details to build reports, Java mail could be used to alert individuals when memory constraints or other issues requiring notice occurred, and nearly anything else that can be done in Java could be added to these client scripts to make it easier to monitor and manage Java applications.
Running with the Attach API
The main implementation class of the Attach API,
Conclusion
The Attach API makes it easier for the Java (or Groovy) developer to write clients that can communicate with, manage, and monitor Java processes. The Attach API provides the same benefits to the developer of custom JMX clients that JConsole and VisualVM leverage.
There are several online resources that demonstrate Java client code that uses the Attach API. These include Daniel Fuchs's code listing for a JVMRuntimeClient, the "Setting up Monitoring and Management Programmatically" section of "Monitoring and Management Using JMX Technology," theCore Java Technology Tech Tip called "The Attach API," and the Javadoc API documentation for the class
com.sun.tools.attach.VirtualMachine
. These examples generally demonstrate usingVirtualMachine.attach(String) to attach to the virtual machine based on its process ID in String form. This is generally followed by loading the appropriate agent with VirtualMachine.loadAgent(String), where the String parameter represents the path to the JAR file containing the agent. TheVirtualMachine.detach() method can be called to detach from the previously attached JVM.All of the previously mentioned examples demonstrate use of the Attach API from Java clients. In this post, I demonstrate use of the Attach API via Groovy. The three code listings that follow present three pieces of Groovy code but all work together as a single script. The main script is embodied in the Groovy file
getJvmThreadInfo.groovy
and is a simple script file that calls the other two Groovy script files (attachToVirtualMachine.groovy
and displayMxBeanDerivedInfo.groovy
) to attach to the virtual machine and to display details regarding that virtual machine via its MXBeans.getJvmDetails.groovy
- #!/usr/bin/env groovy
- // getJvmDetails.groovy
- //
- // Main script for extracting JVM details via Attach API and JMX.
- // Accepts single parameter which is the process ID (pid) of the Java application
- // whose JVM is to be connected to.
- //
- import static attachToVirtualMachine.retrieveConnector
- import static displayMxBeanDerivedInfo.*
- def serverConnection = attachToVirtualMachine.retrieveServerConnection(args[0])
- displayMxBeanDerivedInfo.displayThreadInfo(serverConnection)
- displayMxBeanDerivedInfo.displayOperatingSystemInfo(serverConnection)
- displayMxBeanDerivedInfo.displayRuntimeInfo(serverConnection)
- displayMxBeanDerivedInfo.displayMemoryInfo(serverConnection)
attachToVirtualMachine.groovy
- // attachToVirtualMachine.groovy
- //
- // Provide an MBeanServerConnection acquired via the Attach API.
- import javax.management.MBeanServerConnection
- import javax.management.remote.JMXConnector
- import javax.management.remote.JMXConnectorFactory
- import javax.management.remote.JMXServiceURL
- import com.sun.tools.attach.VirtualMachine
- /**
- * Provide an MBeanServerConnection based on the provided process ID (pid).
- *
- * @param pid Process ID of Java process for which MBeanServerConnection is
- * desired.
- * @return MBeanServerConnection connecting to Java process identified by pid.
- */
- def static MBeanServerConnection retrieveServerConnection(String pid)
- {
- println "Get JMX Connector for pid ${pid}!"
- def connectorAddressStr = "com.sun.management.jmxremote.localConnectorAddress"
- def jmxUrl = retrieveUrlForPid(pid, connectorAddressStr)
- def jmxConnector = JMXConnectorFactory.connect(jmxUrl)
- return jmxConnector.getMBeanServerConnection()
- }
- /**
- * Provide JMX URL for attaching to the provided process ID (pid).
- *
- * @param @pid Process ID for which JMX URL is needed to connect.
- * @param @connectorAddressStr String for connecting.
- * @return JMX URL to communicating with Java process identified by pid.
- */
- def static JMXServiceURL retrieveUrlForPid(String pid, String connectorAddressStr)
- {
- // Attach to the target application's virtual machine
- def vm = VirtualMachine.attach(pid)
- // Obtain Connector Address
- def connectorAddress =
- vm.getAgentProperties().getProperty(connectorAddressStr)
- // Load Agent if no connector address is available
- if (connectorAddress == null)
- {
- def agent = vm.getSystemProperties().getProperty("java.home") +
- File.separator + "lib" + File.separator + "management-agent.jar"
- vm.loadAgent(agent)
- // agent is started, get the connector address
- connectorAddress =
- vm.getAgentProperties().getProperty(connectorAddressStr)
- }
- return new JMXServiceURL(connectorAddress);
- }
displayMxBeanDerivedInfo.groovy
- // displayMxBeanDerivedInfo.groovy
- //
- // Display details regarding attached virtual machine and associated MXBeans.
- import java.lang.management.ManagementFactory
- import java.lang.management.MemoryMXBean
- import java.lang.management.OperatingSystemMXBean
- import java.lang.management.RuntimeMXBean
- import java.lang.management.ThreadMXBean
- import javax.management.MBeanServerConnection
- /**
- * Display thread information based on ThreadMXBean associated with the provided
- * MBeanServerConnection.
- *
- * @param server MBeanServerConnection to use for obtaining thread information
- * via the ThreadMXBean.
- */
- def static void displayThreadInfo(MBeanServerConnection server)
- {
- def remoteThreadBean = ManagementFactory.newPlatformMXBeanProxy(
- server,
- ManagementFactory.THREAD_MXBEAN_NAME,
- ThreadMXBean.class);
- println "Deadlocked Threads: ${remoteThreadBean.findDeadlockedThreads()}"
- println "Monitor Deadlocked Threads: ${remoteThreadBean.findMonitorDeadlockedThreads()}"
- println "Thread IDs: ${Arrays.toString(remoteThreadBean.getAllThreadIds())}"
- def threads = remoteThreadBean.dumpAllThreads(true, true);
- threads.each
- {
- println "\t${it.getThreadName()} (${it.getThreadId()}): ${it.getThreadState()}"
- }
- }
- /**
- * Display operating system information based on OperatingSystemMXBean associated
- * with the provided MBeanServerConnection.
- *
- * @param server MBeanServerConnection to use for obtaining operating system
- * information via the OperatingSystemMXBean.
- */
- def static void displayOperatingSystemInfo(MBeanServerConnection server)
- {
- def osMxBean = ManagementFactory.newPlatformMXBeanProxy(
- server,
- ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME,
- OperatingSystemMXBean.class)
- println "Architecture: ${osMxBean.getArch()}"
- println "Number of Processors: ${osMxBean.getAvailableProcessors()}"
- println "Name: ${osMxBean.getName()}"
- println "Version: ${osMxBean.getVersion()}"
- println "System Load Average: ${osMxBean.getSystemLoadAverage()}"
- }
- /**
- * Display operating system information based on RuntimeMXBean associated with
- * the provided MBeanServerConnection.
- *
- * @param server MBeanServerConnection to use for obtaining runtime information
- * via the RuntimeMXBean.
- */
- def static void displayRuntimeInfo(MBeanServerConnection server)
- {
- def remoteRuntime = ManagementFactory.newPlatformMXBeanProxy(
- server,
- ManagementFactory.RUNTIME_MXBEAN_NAME,
- RuntimeMXBean.class);
- println "Target Virtual Machine: ${remoteRuntime.getName()}"
- println "Uptime: ${remoteRuntime.getUptime()}"
- println "Classpath: ${remoteRuntime.getClassPath()}"
- println "Arguments: ${remoteRuntime.getInputArguments()}"
- }
- /**
- * Display operating system information based on MemoryMXBean associated with
- * the provided MBeanServerConnection.
- *
- * @param server MBeanServerConnection to use for obtaining memory information
- * via the MemoryMXBean.
- */
- def static void displayMemoryInfo(MBeanServerConnection server)
- {
- def memoryMxBean = ManagementFactory.newPlatformMXBeanProxy(
- server,
- ManagementFactory.MEMORY_MXBEAN_NAME,
- MemoryMXBean.class);
- println "HEAP Memory: ${memoryMxBean.getHeapMemoryUsage()}"
- println "Non-HEAP Memory: ${memoryMxBean.getNonHeapMemoryUsage()}"
- }
The three Groovy code listings above together form a script that will use the Attach API to contact to an executing JVM without host or port specified and solely based on the provided process ID. The examples demonstrate use of several of the available MXBeans built into the virtual machine. Because it's Groovy, the code is somewhat more concise than its Java equivalent, especially because no checked exceptions must be explicitly handled and there is no need for explicit classes.
Much more could be done with the information provided via the Attach API and the MXBeans. For example, the Groovy script could be adjusted to persist some of the gathered details to build reports, Java mail could be used to alert individuals when memory constraints or other issues requiring notice occurred, and nearly anything else that can be done in Java could be added to these client scripts to make it easier to monitor and manage Java applications.
Running with the Attach API
The main implementation class of the Attach API,
VirtualMachine
, is located in the${JAVA_HOME}\lib\tools.jar
or %JAVA_HOME\lib\tools.jar
JAR file included with the HotSpot SDK distribution. This file typically needs to be explicitly placed on the classpath of the Java client that uses the Attach API unless it is otherwise placed in a directory that is part of that inherent classpath. This is typically not required when using Groovy because it's normally already in Groovy's classpath. I briefly demonstrated this in the post Viewing Groovy Application's Classpath.Conclusion
The Attach API makes it easier for the Java (or Groovy) developer to write clients that can communicate with, manage, and monitor Java processes. The Attach API provides the same benefits to the developer of custom JMX clients that JConsole and VisualVM leverage.
HotSpot JVM Options Displayed: -XX:+PrintFlagsInitial and -XX:+PrintFlagsFinal
Inspecting HotSpot JVM Options is a great post for those wishing to understand better the options provided by Oracle's (formerly Sun's) HotSpot Java Virtual Machine. In this thorough post, Zahid Qureshi discusses how to use the option
Before reading Zahid's post, I had never seen or read about the
Neither specifying
The
I haven't seen any formal explanation regarding these options (Zahid refers to one of them being "hidden away in the JVM source code" - an advantage of open source!). From the output generated, however, it is possible to gain a good idea of what is displayed. The output of running
The first column appears to reflect the data type of the option (intx, uintx, uint64_t, bool, double, ccstr, ccstrlist). The second column is the name of the flag and the third column is the value, if any, that the flag is set to. The fourth column appears to indicate the type of flag and has values such as {product},{pd product}, {C1 product} for client or {C2 product} for server, {C1 pd product} for client or {C2 pd product} for server, {product rw}, {diagnostic} (only if
The plethora of virtual machine options offered makes it possible to understand and control the virtual machine's behavior more granularly. Although a small subset of the virtual machine options for HotSpotare available online, it is always nice to be able to list them explicitly when needed. The option
-XX:+PrintFlagsFinal
in conjunction with -XX:+UnlockDiagnosticVMOptions
to "dump out every JVM option and its value." Zahid goes further than this and runs these flags against the HotSpot JVM in both client (his client output here) and server mode (his server output here), compares/diffs the options each uses (his diff results here), and analyzes some of the differences. In doing so, Zahid also demonstrates the "super option" -XX:+AggressiveOpts
.Before reading Zahid's post, I had never seen or read about the
XX:+PrintFlagsFinal
option. After downloading the latest SDK (Java SE 6 Update 23), I was able to use the option.Neither specifying
-version
or specifying -XX:+UnlockDiagnosticVMOptions
are required to use the -XX:+PrintFlagsFinal
option, but there are advantages of doing so. The advantage of specifying -version
is that doing so leads to only the version information being printed after the options rather than the longer Java application launcher usage being printed. The advantage of specifying the unlocking of diagnostic VM options with -XX:+UnlockDiagnosticVMOptions
is that diagnostic VM options are included in the output. This is shown in the next screen snapshot.The
-XX:+PrintFlagsFinal
(emphasis on "Final") option displays what options HotSpot ended up using for running Java code while -XX:+PrintFlagsInitial
(emphasis on "Initial") displays what options were provided to HotSpot initially, before HotSpot has made its own tweaks. Comparing the results of -XX:+PrintFlagsFinal
to -XX:+PrintFlagsInitial
can obviously be helpful in understanding optimizations that HotSpot has made. More details on these options are available in Re: Bleeding Edge JVM Options and SDN Bug 6914622.I haven't seen any formal explanation regarding these options (Zahid refers to one of them being "hidden away in the JVM source code" - an advantage of open source!). From the output generated, however, it is possible to gain a good idea of what is displayed. The output of running
java -XX:+PrintFlagsFinal -XX:+UnlockDiagnosticVMOptions -version
is text with a header row stating [Global flags]
followed by numerous rows with one option and its metdata per row. Each row of the output represents a particular global option and has four columns.The first column appears to reflect the data type of the option (intx, uintx, uint64_t, bool, double, ccstr, ccstrlist). The second column is the name of the flag and the third column is the value, if any, that the flag is set to. The fourth column appears to indicate the type of flag and has values such as {product},{pd product}, {C1 product} for client or {C2 product} for server, {C1 pd product} for client or {C2 pd product} for server, {product rw}, {diagnostic} (only if
-XX:+UnlockDiagnosticVMOptions
was specified), {experimental}, and {manageable}. See Eugene Kuleshov's The most complete list of -XX options for Java 6 JVM for a brief description of most of these categories as well as a listing of most of these options themselves.The plethora of virtual machine options offered makes it possible to understand and control the virtual machine's behavior more granularly. Although a small subset of the virtual machine options for HotSpotare available online, it is always nice to be able to list them explicitly when needed. The option
-XX:+PrintFlagsFinal
does just that.Reproducing "too many constants" Problem in Java
In my previous blog post, I blogged on the "code too large" problem and reproduced that error message. In this post, I look at the very similar "too many constants" error message (not the same thing as the question too many constants?) and demonstrate reproducing it by having too many methods in a generated Java class.
With a few small adaptations, I can adjust the Groovy script that I used to generate a Java class to reproduce the "code too large" error to instead generate a Java class to reproduce the "too many constants" error. Here is the revised script.
generateJavaClassWithManyMethods.groovy
When the above script is run with a parameter of 5 for the number of methods, the following Java code is generated.
When I turn up the number of generated methods to 65000 methods, I run out of heap space as shown in the next screen snapshot.

The next screen snapshot shows the output of running the script again, but this time with 512 MB maximum heap space specified for the JVM.

What happens when we try to compile a class with too many methods? That is shown in the next screen snapshot that demonstrates what happens when just such a compilation is attempted.

The "too many constants" error message is shown with a pointer at the class keyword in the class definition. The method has too many methods to compile.
When I run
Conclusion
As with the last blog post, this post used Groovy and the Java Compiler API to intentionally reproduce an error that we hope to not see very often.
With a few small adaptations, I can adjust the Groovy script that I used to generate a Java class to reproduce the "code too large" error to instead generate a Java class to reproduce the "too many constants" error. Here is the revised script.
generateJavaClassWithManyMethods.groovy
- #!/usr/bin/env groovy
- import javax.tools.ToolProvider
- println "You're running the script ${System.getProperty('script.name')}"
- if (args.length < 2)
- {
- println "Usage: javaClassGenerationWithManyMethods packageName className baseDir #methods"
- System.exit(-1)
- }
- // No use of "def" makes the variable available to entire script including the
- // defined methods ("global" variables)
- packageName = args[0]
- packagePieces = packageName.tokenize(".") // Get directory names
- def fileName = args[1].endsWith(".java") ? args[1] : args[1] + ".java"
- def baseDirectory = args.length > 2 ? args[2] : System.getProperty("user.dir")
- numberOfMethods = args.length > 3 ? Integer.valueOf(args[3]) : 10
- NEW_LINE = System.getProperty("line.separator")
- // The setting up of the indentations shows off Groovy's easy feature for
- // multiplying Strings and Groovy's tie of an overloaded * operator for Strings
- // to the 'multiply' method. In other words, the "multiply" and "*" used here
- // are really the same thing.
- SINGLE_INDENT = ' '
- DOUBLE_INDENT = SINGLE_INDENT.multiply(2)
- TRIPLE_INDENT = SINGLE_INDENT * 3
- def outputDirectoryName = createDirectories(baseDirectory)
- def generatedJavaFile = generateJavaClass(outputDirectoryName, fileName)
- compileJavaClass(generatedJavaFile)
- /**
- * Generate the Java class and write its source code to the output directory
- * provided and with the file name provided. The generated class's name is
- * derived from the provided file name.
- *
- * @param outDirName Name of directory to which to write Java source.
- * @param fileName Name of file to be written to output directory (should include
- * the .java extension).
- * @return Fully qualified file name of source file.
- */
- def String generateJavaClass(outDirName, fileName)
- {
- def className = fileName.substring(0,fileName.size()-5)
- outputFileName = outDirName.toString() + File.separator + fileName
- outputFile = new File(outputFileName)
- outputFile.write "package ${packageName};${NEW_LINE.multiply(2)}"
- outputFile << "public class ${className}${NEW_LINE}"
- outputFile << "{${NEW_LINE}"
- outputFile << "${SINGLE_INDENT}public static void main(final String[] arguments)"
- outputFile << "${NEW_LINE}${SINGLE_INDENT}{${NEW_LINE}"
- outputFile << DOUBLE_INDENT << 'final String someString = "Dustin";' << NEW_LINE
- outputFile << "${SINGLE_INDENT}}${NEW_LINE}"
- outputFile << buildManyMethods()
- outputFile << "}"
- return outputFileName
- }
- /**
- * Compile the provided Java source code file name.
- *
- * @param fileName Name of Java file to be compiled.
- */
- def void compileJavaClass(fileName)
- {
- // Use the Java SE 6 Compiler API (JSR 199)
- // http://java.sun.com/mailers/techtips/corejava/2007/tt0307.html#1
- compiler = ToolProvider.getSystemJavaCompiler()
- // The use of nulls in the call to JavaCompiler.run indicate use of defaults
- // of System.in, System.out, and System.err.
- int compilationResult = compiler.run(null, null, null, fileName)
- if (compilationResult == 0)
- {
- println "${fileName} compiled successfully"
- }
- else
- {
- println "${fileName} compilation failed"
- }
- }
- /**
- * Create directories to which generated files will be written.
- *
- * @param baseDir The base directory used in which subdirectories for Java
- * source packages will be generated.
- */
- def String createDirectories(baseDir)
- {
- def outDirName = new StringBuilder(baseDir)
- for (pkgDir in packagePieces)
- {
- outDirName << File.separator << pkgDir
- }
- outputDirectory = new File(outDirName.toString())
- if (outputDirectory.exists() && outputDirectory.isDirectory())
- {
- println "Directory ${outDirName} already exists."
- }
- else
- {
- isDirectoryCreated = outputDirectory.mkdirs() // Use mkdirs in case multiple
- println "Directory ${outputDirectoryName} ${isDirectoryCreated ? 'is' : 'not'} created."
- }
- return outDirName.toString()
- }
- /**
- * Generate the body of generated Java class source code's main function.
- */
- def String buildManyMethods()
- {
- def str = new StringBuilder() << NEW_LINE
- for (i in 0..numberOfMethods)
- {
- str << SINGLE_INDENT << "private void doMethod${i}(){}" << NEW_LINE
- }
- return str
- }
When the above script is run with a parameter of 5 for the number of methods, the following Java code is generated.
- package dustin.examples;
- public class LotsOfMethods
- {
- public static void main(final String[] arguments)
- {
- final String someString = "Dustin";
- }
- private void doMethod0(){}
- private void doMethod1(){}
- private void doMethod2(){}
- private void doMethod3(){}
- private void doMethod4(){}
- private void doMethod5(){}
- }
When I turn up the number of generated methods to 65000 methods, I run out of heap space as shown in the next screen snapshot.

The next screen snapshot shows the output of running the script again, but this time with 512 MB maximum heap space specified for the JVM.

What happens when we try to compile a class with too many methods? That is shown in the next screen snapshot that demonstrates what happens when just such a compilation is attempted.

The "too many constants" error message is shown with a pointer at the class keyword in the class definition. The method has too many methods to compile.
When I run
javap -c -private dustin.examples.LotsOfMethods
(-c
to disassemble the code, -private
to display the many private methods, and dustin.examples.LotsOfMethods
is the name of the generated Java class), I see output like the following (only the first and end shown instead of displaying all 60,000+ methods).Compiled from "LotsOfMethods.java" public class dustin.examples.LotsOfMethods extends java.lang.Object{ public dustin.examples.LotsOfMethods(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return public static void main(java.lang.String[]); Code: 0: return private void doMethod0(); Code: 0: return private void doMethod1(); Code: 0: return private void doMethod2(); Code: 0: return private void doMethod3(); Code: 0: return private void doMethod4(); Code: 0: return private void doMethod5(); Code: 0: return private void doMethod6(); Code: 0: return private void doMethod7(); Code: 0: return private void doMethod8(); Code: 0: return . . . . . . . . . private void doMethod64992(); Code: 0: return private void doMethod64993(); Code: 0: return private void doMethod64994(); Code: 0: return private void doMethod64995(); Code: 0: return private void doMethod64996(); Code: 0: return private void doMethod64997(); Code: 0: return private void doMethod64998(); Code: 0: return private void doMethod64999(); Code: 0: return private void doMethod65000(); Code: 0: return }
Conclusion
As with the last blog post, this post used Groovy and the Java Compiler API to intentionally reproduce an error that we hope to not see very often.
Reproducing "code too large" Problem in Java
Code conventions and standard software development wisdom dictate that methods should not be too long because they become difficult to fully comprehend, they lose readability when they get too long, they are difficult to appropriately unit test, and they are difficult to reuse. Because most Java developers strive to write highly modular code with small, highly cohesive methods, the "code too large" error in Java is not seen very often. When this error is seen, it is often in generated code.
In this blog post, I intentionally force this "code too large" error to occur. Why in the world would one intentionally do this? In this case, it is because I always understand things better when I tinker with them rather than just reading about them and because doing so gives me a chance to demonstrateGroovy, the Java Compiler API (Java SE 6), and javap.
It turns out that the magic number for the "code too large" error is 65535 bytes (compiled byte code, not source code). Hand-writing a method large enough to lead to this size of a .class file would be tedious (and not worth the effort in my opinion). However, it is typically generated code that leads to this in the wild and so generation of code seems like the best approach to reproducing the problem. When I think of generic Java code generation, I think Groovy.
The Groovy script that soon follows generates a Java class that isn't very exciting. However, the class will have its main function be of an approximate size based on how many conditions I tell the script to create. This allows me to quickly try generating Java classes with different
After the script generates the Java class, it also uses the Java Compiler API to automatically compilethe newly generated Java class for me. The resultant
generateJavaClass.groovy
Because this script is intended primarily for generating Java code to learn more about the "code too large" error and to demonstrate a few things, I did not make it nearly as fancy as it could be. For one thing, I did not use Groovy's built-in Apache CLI support for handling command-line arguments as I have demonstrated in previous blog posts on using Groovy to check seventh grade homework.
Even though the script above does not apply Groovy's full potential, it still manages to demonstrate some Groovy niceties. I tried to add comments in the script describing some of these. These include features such as Groovy GDK's String.tokenize method and other useful Groovy String extensions.
When I run this script from the directory

This output tells us that the generated Java class with five conditionals compiled successfully. To get a taste of what this generated Java class looks like, we'll look at this newly generated version with only five conditionals.
BigClass.java (generated with 5 conditionals)
The above code includes two default conditionals every time regardless of how many conditionals are selected when the class generation script is run. In between the check for null/empty String and the
I now try the Groovy script for generating the Java class again, but this time go all out and select 5000 as the number of desired conditionals. As the output shown below and in the following screen snapshot indicate, Groovy has no trouble generating the text file representing the Java class with this many conditionals in its main() function, but the Java compiler doesn't like it one bit.

Obviously, the attempt to compile the generated class with a 5000+2 conditional main was too much. Through a little iterative trial-and-error, I was able to determine that 2265 conditionals (beyond the two defaults) was the maximum compilable number for my main() function and 2266 would break it. This is demonstrated in the next screen snapshot.

Knowing our limits better, we can now "look" at the byte code using the javap tool provided with Sun's JDK to analyze the corresponding class file. Because there was a compiler error when we tried to compile the code with 2266 additional conditionals, we must run
From the snippets of javap output shown above, we see that the highest
Conclusion
Most Java developers don't see the "code too large" problem very often because they write methods and classes that are reasonable in size (or at least more reasonable than the limits allow). However, generated code can much more easily exceed these "limitations." So of what value is intentionally reproducing this error? Well, the next time someone tries to convince you that bigger is better, you can refer that person to this post.
Other Resources
⇒ Java Language Specification, Third Edition
⇒ Class File Format
⇒ Code Too Large for Try Statement?
⇒ Code Too Long
⇒ Is There Any Number of Lines Limit in a Java Class?
In this blog post, I intentionally force this "code too large" error to occur. Why in the world would one intentionally do this? In this case, it is because I always understand things better when I tinker with them rather than just reading about them and because doing so gives me a chance to demonstrateGroovy, the Java Compiler API (Java SE 6), and javap.
It turns out that the magic number for the "code too large" error is 65535 bytes (compiled byte code, not source code). Hand-writing a method large enough to lead to this size of a .class file would be tedious (and not worth the effort in my opinion). However, it is typically generated code that leads to this in the wild and so generation of code seems like the best approach to reproducing the problem. When I think of generic Java code generation, I think Groovy.
The Groovy script that soon follows generates a Java class that isn't very exciting. However, the class will have its main function be of an approximate size based on how many conditions I tell the script to create. This allows me to quickly try generating Java classes with different
main()
method sizes to ascertain when the main()
becomes too large.After the script generates the Java class, it also uses the Java Compiler API to automatically compilethe newly generated Java class for me. The resultant
.class
file is placed in the same directory as the source .java
file. The script, creatively named generateJavaClass.groovy
, is shown next.generateJavaClass.groovy
- #!/usr/bin/env groovy
- import javax.tools.ToolProvider
- println "You're running the script ${System.getProperty('script.name')}"
- if (args.length < 2)
- {
- println "Usage: javaClassGeneration packageName className baseDir #loops"
- System.exit(-1)
- }
- // No use of "def" makes the variable available to entire script including the
- // defined methods ("global" variables)
- packageName = args[0]
- packagePieces = packageName.tokenize(".") // Get directory names
- def fileName = args[1].endsWith(".java") ? args[1] : args[1] + ".java"
- def baseDirectory = args.length > 2 ? args[2] : System.getProperty("user.dir")
- numberOfConditionals = args.length > 3 ? Integer.valueOf(args[3]) : 10
- NEW_LINE = System.getProperty("line.separator")
- // The setting up of the indentations shows off Groovy's easy feature for
- // multiplying Strings and Groovy's tie of an overloaded * operator for Strings
- // to the 'multiply' method. In other words, the "multiply" and "*" used here
- // are really the same thing.
- SINGLE_INDENT = ' '
- DOUBLE_INDENT = SINGLE_INDENT.multiply(2)
- TRIPLE_INDENT = SINGLE_INDENT * 3
- def outputDirectoryName = createDirectories(baseDirectory)
- def generatedJavaFile = generateJavaClass(outputDirectoryName, fileName)
- compileJavaClass(generatedJavaFile)
- /**
- * Generate the Java class and write its source code to the output directory
- * provided and with the file name provided. The generated class's name is
- * derived from the provided file name.
- *
- * @param outDirName Name of directory to which to write Java source.
- * @param fileName Name of file to be written to output directory (should include
- * the .java extension).
- * @return Fully qualified file name of source file.
- */
- def String generateJavaClass(outDirName, fileName)
- {
- def className = fileName.substring(0,fileName.size()-5)
- outputFileName = outDirName.toString() + File.separator + fileName
- outputFile = new File(outputFileName)
- outputFile.write "package ${packageName};${NEW_LINE.multiply(2)}"
- outputFile << "public class ${className}${NEW_LINE}"
- outputFile << "{${NEW_LINE}"
- outputFile << "${SINGLE_INDENT}public static void main(final String[] arguments)"
- outputFile << "${NEW_LINE}${SINGLE_INDENT}{${NEW_LINE}"
- outputFile << DOUBLE_INDENT << 'final String someString = "Dustin";' << NEW_LINE
- outputFile << buildMainBody()
- outputFile << "${SINGLE_INDENT}}${NEW_LINE}"
- outputFile << "}"
- return outputFileName
- }
- /**
- * Compile the provided Java source code file name.
- *
- * @param fileName Name of Java file to be compiled.
- */
- def void compileJavaClass(fileName)
- {
- // Use the Java SE 6 Compiler API (JSR 199)
- // http://java.sun.com/mailers/techtips/corejava/2007/tt0307.html#1
- compiler = ToolProvider.getSystemJavaCompiler()
- // The use of nulls in the call to JavaCompiler.run indicate use of defaults
- // of System.in, System.out, and System.err.
- int compilationResult = compiler.run(null, null, null, fileName)
- if (compilationResult == 0)
- {
- println "${fileName} compiled successfully"
- }
- else
- {
- println "${fileName} compilation failed"
- }
- }
- /**
- * Create directories to which generated files will be written.
- *
- * @param baseDir The base directory used in which subdirectories for Java
- * source packages will be generated.
- */
- def String createDirectories(baseDir)
- {
- def outDirName = new StringBuilder(baseDir)
- for (pkgDir in packagePieces)
- {
- outDirName << File.separator << pkgDir
- }
- outputDirectory = new File(outDirName.toString())
- if (outputDirectory.exists() && outputDirectory.isDirectory())
- {
- println "Directory ${outDirName} already exists."
- }
- else
- {
- isDirectoryCreated = outputDirectory.mkdirs() // Use mkdirs in case multiple
- println "Directory ${outputDirectoryName} ${isDirectoryCreated ? 'is' : 'not'} created."
- }
- return outDirName.toString()
- }
- /**
- * Generate the body of generated Java class source code's main function.
- */
- def String buildMainBody()
- {
- def str = new StringBuilder() << NEW_LINE
- str << DOUBLE_INDENT << "if (someString == null || someString.isEmpty())" << NEW_LINE
- str << DOUBLE_INDENT << "{" << NEW_LINE
- str << TRIPLE_INDENT << 'System.out.println("The String is null or empty.");'
- str << NEW_LINE << DOUBLE_INDENT << "}" << NEW_LINE
- for (i in 0..numberOfConditionals)
- {
- str << DOUBLE_INDENT << 'else if (someString.equals("a' << i << '"))' << NEW_LINE
- str << DOUBLE_INDENT << "{" << NEW_LINE
- str << TRIPLE_INDENT << 'System.out.println("You found me!");' << NEW_LINE
- str << DOUBLE_INDENT << "}" << NEW_LINE
- }
- str << DOUBLE_INDENT << "else" << NEW_LINE
- str << DOUBLE_INDENT << "{" << NEW_LINE
- str << TRIPLE_INDENT << 'System.out.println("No matching string found.");'
- str << DOUBLE_INDENT << NEW_LINE << DOUBLE_INDENT << "}" << NEW_LINE
- return str
- }
Because this script is intended primarily for generating Java code to learn more about the "code too large" error and to demonstrate a few things, I did not make it nearly as fancy as it could be. For one thing, I did not use Groovy's built-in Apache CLI support for handling command-line arguments as I have demonstrated in previous blog posts on using Groovy to check seventh grade homework.
Even though the script above does not apply Groovy's full potential, it still manages to demonstrate some Groovy niceties. I tried to add comments in the script describing some of these. These include features such as Groovy GDK's String.tokenize method and other useful Groovy String extensions.
When I run this script from the directory
C:\java\examples\groovyExamples\javaClassGeneration
with the arguments "dustin.examples" (package structure), "BigClass" (name of generated Java class), "." (current directory is based directory for generated code, and "5" (number of conditionals to be in generated code), the script's output is shown here and in the following screen snapshot:You're running the script C:\java\examples\groovyExamples\javaClassGeneration\generateJavaClass.groovy Directory .\dustin\examples already exists. .\dustin\examples\BigClass.java compiled successfully

This output tells us that the generated Java class with five conditionals compiled successfully. To get a taste of what this generated Java class looks like, we'll look at this newly generated version with only five conditionals.
BigClass.java (generated with 5 conditionals)
- package dustin.examples;
- public class BigClass
- {
- public static void main(final String[] arguments)
- {
- final String someString = "Dustin";
- if (someString == null || someString.isEmpty())
- {
- System.out.println("The String is null or empty.");
- }
- else if (someString.equals("a0"))
- {
- System.out.println("You found me!");
- }
- else if (someString.equals("a1"))
- {
- System.out.println("You found me!");
- }
- else if (someString.equals("a2"))
- {
- System.out.println("You found me!");
- }
- else if (someString.equals("a3"))
- {
- System.out.println("You found me!");
- }
- else if (someString.equals("a4"))
- {
- System.out.println("You found me!");
- }
- else if (someString.equals("a5"))
- {
- System.out.println("You found me!");
- }
- else
- {
- System.out.println("No matching string found.");
- }
- }
- }
The above code includes two default conditionals every time regardless of how many conditionals are selected when the class generation script is run. In between the check for null/empty String and the
else
clause if no other else if
has been satisfied are the number of else if
statements specified when the class generation script was run. In this case, 5 was that number and so there are five else if
conditionals between the two default conditionals on either end. As this demonstrates, it will be easy to scale up the number of conditionals until the Java compiler just won't take it anymore.I now try the Groovy script for generating the Java class again, but this time go all out and select 5000 as the number of desired conditionals. As the output shown below and in the following screen snapshot indicate, Groovy has no trouble generating the text file representing the Java class with this many conditionals in its main() function, but the Java compiler doesn't like it one bit.
You're running the script C:\java\examples\groovyExamples\javaClassGeneration\generateJavaClass.groovy Directory .\dustin\examples already exists. .\dustin\examples\BigClass.java:5: code too large public static void main(final String[] arguments) ^ 1 error .\dustin\examples\BigClass.java compilation failed

Obviously, the attempt to compile the generated class with a 5000+2 conditional main was too much. Through a little iterative trial-and-error, I was able to determine that 2265 conditionals (beyond the two defaults) was the maximum compilable number for my main() function and 2266 would break it. This is demonstrated in the next screen snapshot.

Knowing our limits better, we can now "look" at the byte code using the javap tool provided with Sun's JDK to analyze the corresponding class file. Because there was a compiler error when we tried to compile the code with 2266 additional conditionals, we must run
javap
against the BigClass.class file generated with 2265 additional conditionals. The output of running javap
with the -c
option for this large class is too large (~1 MB) to bludgeon readers with here. However, I include key snippets from its output below.Compiled from "BigClass.java" public class dustin.examples.BigClass extends java.lang.Object{ public dustin.examples.BigClass(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #2; //String Dustin 2: ifnonnull 10 5: goto_w 23 10: ldc #2; //String Dustin 12: invokevirtual #3; //Method java/lang/String.isEmpty:()Z 15: ifne 23 18: goto_w 36 23: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream; 26: ldc #5; //String The String is null or empty. 28: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 31: goto_w 65512 36: ldc #2; //String Dustin 38: ldc #7; //String a0 40: invokevirtual #8; //Method java/lang/String.equals:(Ljava/lang/Object;)Z 43: ifne 51 46: goto_w 64 51: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream; 54: ldc #9; //String You found me! 56: invokevirtual #6; //Method java/io/PrintStream.println: . . . . . . . . . 65411: goto_w 65512 65416: ldc #2; //String Dustin 65418: ldc_w #2272; //String a2263 65421: invokevirtual #8; //Method java/lang/String.equals:(Ljava/lang/Object;)Z 65424: ifne 65432 65427: goto_w 65445 65432: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream; 65435: ldc #9; //String You found me! 65437: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 65440: goto_w 65512 65445: ldc #2; //String Dustin 65447: ldc_w #2273; //String a2264 65450: invokevirtual #8; //Method java/lang/String.equals:(Ljava/lang/Object;)Z 65453: ifne 65461 65456: goto_w 65474 65461: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream; 65464: ldc #9; //String You found me! 65466: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 65469: goto_w 65512 65474: ldc #2; //String Dustin 65476: ldc_w #2274; //String a2265 65479: invokevirtual #8; //Method java/lang/String.equals:(Ljava/lang/Object;)Z 65482: ifne 65490 65485: goto_w 65503 65490: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream; 65493: ldc #9; //String You found me! 65495: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 65498: goto_w 65512 65503: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream; 65506: ldc_w #2275; //String No matching string found. 65509: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 65512: return }
From the snippets of javap output shown above, we see that the highest
Code
offset (65512) for this function pushing the limits of the method size was getting awfully close to the magic 65535 bytes (216-1
or Short.MAX_VALUE - Short.MIN_VALUE
).Conclusion
Most Java developers don't see the "code too large" problem very often because they write methods and classes that are reasonable in size (or at least more reasonable than the limits allow). However, generated code can much more easily exceed these "limitations." So of what value is intentionally reproducing this error? Well, the next time someone tries to convince you that bigger is better, you can refer that person to this post.
Other Resources
⇒ Java Language Specification, Third Edition
⇒ Class File Format
⇒ Code Too Large for Try Statement?
⇒ Code Too Long
⇒ Is There Any Number of Lines Limit in a Java Class?
Firefox 3.6 Requires Java SE 6 Update 10
Java SE 6 Update 10 was a significant release despite its version numbering indicating a minor release. Among other advancements, Java SE 6 Update 10 introduced the Next-Generation Java Plug-in for a better Java applet experience. It has now been announced that Firefox 3.6 (Namoroka) will require this next-generation plug-in. Users can access the web page athttp://java.com/en/download/help/testvm.xml to test the version of Java supported on a particular machine. The Firefox 3.6 for Developers page specifies advancements in support for Cascading Style Sheets, HTML, JavaScript, DOM, and XPath.
Java SourceVersion and Character
The SourceVersion class provides information on Java source versions and can provide some interesting details, including demonstration of terminology covered in the Java Language Specification. In this blog posting, I look briefly at some of the more interesting observations one can make using this class that was introduced with Java SE 6 in conjunction with the Java long-timer class Character.
The
The following code snippet demonstrates these two methods in action.
The output from running this code is shown next.

As the code example and corresponding output above indicate, the currently supported modeled version and currently fully supported versions are easily accessible. Although the

The Javadoc documentation tells us the meanings of the various enum values shown in the above output. Each represents a different "source version of the Java programming language" and the platform version it is associated with. As shown earlier, the RELEASE_6 is associated with Java SE 6, RELEASE_5 is associated with J2SE 5, RELEASE_4 is associated with JDK 1.4, RELEASE_3 is associated with JDK 1.3, RELEASE_2 is associated with JDK 1.2, RELEASE_1 is associated with JDK 1.1 and RELEASE_0 is associated with "the original version." The Javadoc documentation for Java SE 7 indicates that SourceVersion.RELEASE_7 is supported in Java SE 7.
The
Using these methods allows one to determine if a particular string is reserved as a keyword, is even considered a valid identifier, and if a string that is a valid identifier is not a keyword and is thus a valid name. The
I have run many different strings of various combinations of these three types in the following code.
When the above code is executed the output shown next is generated.
The above output demonstrates that a valid name must be a valid identifier without being a keyword. A keyword must be a valid identifier, but not all identifiers are keywords. Some string values that are not keywords or reserved words are not even identifiers because they don't meet the rules of Java identifiers.
The examples above indicate that we cannot use a name for a variable or other construct that begins with a numeral, but we can use $ and _ for the first character in a name. Another way to determine this is through use of the static method Character.isJavaIdentifierStart(char). The following code snippet demonstrates this along with the similar method Character.isJavaIdentifierPart(char), which returns
The output from the above appears below.
Because the Character.getType(char) method has been with us for quite a while and predates the J2SE 5-introduced enum construct, this method returns primitives integers. One can refer to Java'sConstant Field Values to determine what each of these constants stand for.
To make the above example's output a little more readable, I have added a simple "converter" method that converts the returned
When the integers returned from
The
The
SourceVersion
class provides several handy static methods for ascertaining details about the source version of the current Java runtime. The methods SourceVersion.latest() andSourceVersion.latestSupported() provide details regarding the latest source versions that can be "modeled" and "fully supported" respectively.The following code snippet demonstrates these two methods in action.
- out.println("Latest source version that can be modeled: ");
- out.println("\tSourceVersion.latest(): " + SourceVersion.latest());
- out.println("Latest source version fully supported by current execution environment ");
- out.println("\tSourceVersion.latestSupported(): " + SourceVersion.latestSupported());
The output from running this code is shown next.

As the code example and corresponding output above indicate, the currently supported modeled version and currently fully supported versions are easily accessible. Although the
SourceVersion
class was introduced with Java SE 6, it has been built to be support future versions of Java. Not only does the Javadoc documentation state that "additional source version constants will be added to model future releases of the language," but the SourceVersion.values() method also provides all supported version enums. A code example and associated output are shown next to demonstrate this method in action.- out.println("SourceVersion enum Values:");
- final SourceVersion[] versions = SourceVersion.values();
- for (final SourceVersion version : versions)
- {
- out.println("\t" + version);
- }

The Javadoc documentation tells us the meanings of the various enum values shown in the above output. Each represents a different "source version of the Java programming language" and the platform version it is associated with. As shown earlier, the RELEASE_6 is associated with Java SE 6, RELEASE_5 is associated with J2SE 5, RELEASE_4 is associated with JDK 1.4, RELEASE_3 is associated with JDK 1.3, RELEASE_2 is associated with JDK 1.2, RELEASE_1 is associated with JDK 1.1 and RELEASE_0 is associated with "the original version." The Javadoc documentation for Java SE 7 indicates that SourceVersion.RELEASE_7 is supported in Java SE 7.
The
SourceVersion
class provides three static methods that each indicate whether a providedCharSequence is an identifier, keyword, or name. The three methods that allow one to dynamically determine if a particular CharSequence
fits one or more of the types identifier, name, or keyword are (respectively) SourceVersion.isIdentifier(), SourceVersion.isName(), and SourceVersion.isKeyword().Using these methods allows one to determine if a particular string is reserved as a keyword, is even considered a valid identifier, and if a string that is a valid identifier is not a keyword and is thus a valid name. The
isName()
method returns true
for a "syntactically valid name" that is not also a keyword orliteral. The isKeyword()
method indicates if the provided string is one of the keywords listed here.I have run many different strings of various combinations of these three types in the following code.
- public static void printIdentifierTest(final String stringToBeTested)
- {
- out.println(
- "Is '" + stringToBeTested + "' an identifier? "
- + SourceVersion.isIdentifier(stringToBeTested));
- }
- public static void printKeywordTest(final String stringToBeTested)
- {
- out.println(
- "Is '" + stringToBeTested + "' a keyword? "
- + SourceVersion.isKeyword(stringToBeTested));
- }
- public static void printNameTest(final String stringToBeTested)
- {
- out.println(
- "Can '" + stringToBeTested + "' be used as a name? "
- + SourceVersion.isName(stringToBeTested));
- }
- public static void printTests(final String stringToBeTested)
- {
- out.println("\n=============== " + stringToBeTested + " ===============");
- printIdentifierTest(stringToBeTested);
- printKeywordTest(stringToBeTested);
- printNameTest(stringToBeTested);
- }
- public static void printTests(
- final String stringToBeTested,
- final String alternateHeaderString)
- {
- out.println("\n=============== " + alternateHeaderString + " ===============");
- printIdentifierTest(stringToBeTested);
- printKeywordTest(stringToBeTested);
- printNameTest(stringToBeTested);
- }
- /**
- * Main function for demonstrating SourceVersion enum.
- *
- * @param arguments Command-line arguments: none expected.
- */
- public static void main(final String[] arguments)
- {
- final String dustinStr = "Dustin";
- printTests(dustinStr);
- final String dustinLowerStr = "dustin";
- printTests(dustinLowerStr);
- final String instanceOfStr = "instanceof";
- printTests(instanceOfStr);
- final String constStr = "const";
- printTests(constStr);
- final String gotoStr = "goto";
- printTests(gotoStr);
- final String trueStr = "true";
- printTests(trueStr);
- final String nullStr = "null";
- printTests(nullStr);
- final String weirdStr = "/#";
- printTests(weirdStr);
- final String tabStr = "\t";
- printTests(tabStr, "TAB (\\t)");
- final String classStr = "class";
- printTests(classStr);
- final String enumStr = "enum";
- printTests(enumStr);
- final String assertStr = "assert";
- printTests(assertStr);
- final String intStr = "int";
- printTests(intStr);
- final String numeralStartStr = "1abc";
- printTests(numeralStartStr);
- final String numeralEmbeddedStr = "abc1";
- printTests(numeralEmbeddedStr);
- final String dollarStartStr = "$dustin";
- printTests(dollarStartStr);
- final String underscoreStartStr = "_dustin";
- printTests(underscoreStartStr);
- final String spacesStartStr = " dustin";
- printTests(spacesStartStr, " dustin (space in front)");
- final String spacesInStr = "to be";
- printTests(spacesInStr);
- }
When the above code is executed the output shown next is generated.
- =============== Dustin ===============
- Is 'Dustin' an identifier? true
- Is 'Dustin' a keyword? false
- Can 'Dustin' be used as a name? true
- =============== dustin ===============
- Is 'dustin' an identifier? true
- Is 'dustin' a keyword? false
- Can 'dustin' be used as a name? true
- =============== instanceof ===============
- Is 'instanceof' an identifier? true
- Is 'instanceof' a keyword? true
- Can 'instanceof' be used as a name? false
- =============== const ===============
- Is 'const' an identifier? true
- Is 'const' a keyword? true
- Can 'const' be used as a name? false
- =============== goto ===============
- Is 'goto' an identifier? true
- Is 'goto' a keyword? true
- Can 'goto' be used as a name? false
- =============== true ===============
- Is 'true' an identifier? true
- Is 'true' a keyword? true
- Can 'true' be used as a name? false
- =============== null ===============
- Is 'null' an identifier? true
- Is 'null' a keyword? true
- Can 'null' be used as a name? false
- =============== /# ===============
- Is '/#' an identifier? false
- Is '/#' a keyword? false
- Can '/#' be used as a name? false
- =============== TAB (\t) ===============
- Is ' ' an identifier? false
- Is ' ' a keyword? false
- Can ' ' be used as a name? false
- =============== class ===============
- Is 'class' an identifier? true
- Is 'class' a keyword? true
- Can 'class' be used as a name? false
- =============== enum ===============
- Is 'enum' an identifier? true
- Is 'enum' a keyword? true
- Can 'enum' be used as a name? false
- =============== assert ===============
- Is 'assert' an identifier? true
- Is 'assert' a keyword? true
- Can 'assert' be used as a name? false
- =============== int ===============
- Is 'int' an identifier? true
- Is 'int' a keyword? true
- Can 'int' be used as a name? false
- =============== 1abc ===============
- Is '1abc' an identifier? false
- Is '1abc' a keyword? false
- Can '1abc' be used as a name? false
- =============== abc1 ===============
- Is 'abc1' an identifier? true
- Is 'abc1' a keyword? false
- Can 'abc1' be used as a name? true
- =============== $dustin ===============
- Is '$dustin' an identifier? true
- Is '$dustin' a keyword? false
- Can '$dustin' be used as a name? true
- =============== _dustin ===============
- Is '_dustin' an identifier? true
- Is '_dustin' a keyword? false
- Can '_dustin' be used as a name? true
- =============== dustin (space in front) ===============
- Is ' dustin' an identifier? false
- Is ' dustin' a keyword? false
- Can ' dustin' be used as a name? false
- =============== to be ===============
- Is 'to be' an identifier? false
- Is 'to be' a keyword? false
- Can 'to be' be used as a name? false
The above output demonstrates that a valid name must be a valid identifier without being a keyword. A keyword must be a valid identifier, but not all identifiers are keywords. Some string values that are not keywords or reserved words are not even identifiers because they don't meet the rules of Java identifiers.
The examples above indicate that we cannot use a name for a variable or other construct that begins with a numeral, but we can use $ and _ for the first character in a name. Another way to determine this is through use of the static method Character.isJavaIdentifierStart(char). The following code snippet demonstrates this along with the similar method Character.isJavaIdentifierPart(char), which returns
true
if the provided character can be in the name anywhere other than the first character.- public static void printTestForValidIdentifierCharacter(
- final char characterToBeTested)
- {
- out.println(
- "Character '" + characterToBeTested
- + ( Character.isJavaIdentifierStart(characterToBeTested)
- ? "': VALID "
- : "': NOT VALID ")
- + "FIRST character and "
- + ( Character.isJavaIdentifierPart(characterToBeTested)
- ? "VALID "
- : "NOT VALID ")
- + "OTHER character in a Java name.");
- out.println( "\tType of '" + characterToBeTested + "': "
- + Character.getType(characterToBeTested));
- }
- public static void demonstrateCharacterJavaIdentifierStart()
- {
- out.println("\nTEST FOR FIRST AND OTHER CHARACTERS IN A VALID JAVA NAME");
- printTestForValidIdentifierCharacter('A');
- printTestForValidIdentifierCharacter('a');
- printTestForValidIdentifierCharacter('1');
- printTestForValidIdentifierCharacter('\\');
- printTestForValidIdentifierCharacter('_');
- printTestForValidIdentifierCharacter('$');
- printTestForValidIdentifierCharacter('#');
- printTestForValidIdentifierCharacter('\n');
- printTestForValidIdentifierCharacter('\t');
- }
The output from the above appears below.
- TEST FOR FIRST AND OTHER CHARACTERS IN A VALID JAVA NAME
- Character 'A': VALID FIRST character and VALID OTHER character in a Java name.
- Type of 'A': 1
- Character 'a': VALID FIRST character and VALID OTHER character in a Java name.
- Type of 'a': 2
- Character '1': NOT VALID FIRST character and VALID OTHER character in a Java name.
- Type of '1': 9
- Character '\': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.
- Type of '\': 24
- Character '_': VALID FIRST character and VALID OTHER character in a Java name.
- Type of '_': 23
- Character '$': VALID FIRST character and VALID OTHER character in a Java name.
- Type of '$': 26
- Character '#': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.
- Type of '#': 24
- Character '
- ': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.
- Type of '
- ': 15
- Character ' ': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.
- Type of ' ': 15
Because the Character.getType(char) method has been with us for quite a while and predates the J2SE 5-introduced enum construct, this method returns primitives integers. One can refer to Java'sConstant Field Values to determine what each of these constants stand for.
To make the above example's output a little more readable, I have added a simple "converter" method that converts the returned
int
to a more readable String
. I have only added switch case
s for the integers returned from my example, but one could add cases for all supported types represented by different integers.- public static String extractReadableStringFromJavaCharacterTypeInt(
- final int characterTypeInt)
- {
- String characterType;
- switch (characterTypeInt)
- {
- case Character.CONNECTOR_PUNCTUATION :
- characterType = "Connector Punctuation";
- break;
- case Character.CONTROL :
- characterType = "Control";
- break;
- case Character.CURRENCY_SYMBOL :
- characterType = "Currency Symbol";
- break;
- case Character.DECIMAL_DIGIT_NUMBER :
- characterType = "Decimal Digit Number";
- break;
- case Character.LETTER_NUMBER :
- characterType = "Letter/Number";
- break;
- case Character.LOWERCASE_LETTER :
- characterType = "Lowercase Letter";
- break;
- case Character.OTHER_PUNCTUATION :
- characterType = "Other Punctuation";
- break;
- case Character.UPPERCASE_LETTER :
- characterType = "Uppercase Letter";
- break;
- default : characterType = "Unknown Character Type Integer: " + characterTypeInt;
- }
- return characterType;
- }
When the integers returned from
Character.getType(char)
in the example two listings ago are run through this switch statement, the revised output appears as shown next.- TEST FOR FIRST AND OTHER CHARACTERS IN A VALID JAVA NAME
- Character 'A': VALID FIRST character and VALID OTHER character in a Java name.
- Type of 'A': Uppercase Letter
- Character 'a': VALID FIRST character and VALID OTHER character in a Java name.
- Type of 'a': Lowercase Letter
- Character '1': NOT VALID FIRST character and VALID OTHER character in a Java name.
- Type of '1': Decimal Digit Number
- Character '\': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.
- Type of '\': Other Punctuation
- Character '_': VALID FIRST character and VALID OTHER character in a Java name.
- Type of '_': Connector Punctuation
- Character '$': VALID FIRST character and VALID OTHER character in a Java name.
- Type of '$': Currency Symbol
- Character '#': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.
- Type of '#': Other Punctuation
- Character '
- ': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.
- Type of '
- ': Control
- Character ' ': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.
- Type of ' ': Control
The
SourceVersion
class is useful for dynamically determining information about the Java source code version and the keywords and valid names applicable for that version. The Character
class also provides useful information on what a particular character's type is and whether or not that character can be used as the first character of a name or as any other character in a valid name.
No comments:
Post a Comment