This chapter describes how your Java program can deal with its immediate surroundings with what we call the runtime environment. In one sense, everything you do in a Java program using almost any Java API involves the environment. Here we focus more narrowly on things that directly surround your program. Along the way we’ll be introduced to the System
class, which knows a lot about your particular system.
Two other runtime classes deserve brief mention. The
first, java.lang.Runtime
, lies behind many of the methods in the System
class. System.exit()
, for example, just calls Runtime.exit()
. Runtime
is technically part of the environment, but the only time we use it
directly is to run other programs, which is covered in
Recipe 18.1.
You want to get the value of environment variables from within your Java program.
Use System.getenv()
.
The seventh edition of Unix, released in 1979, had a new feature known as environment variables. Environment variables are in all modern Unix systems (including macOS) and in most later command-line systems, such as the DOS or Command Prompt in Windows, but they are not in some older platforms or other Java runtimes. Environment variables are commonly used for customizing an individual computer user’s runtime environment, hence the name. To take one familiar example, on Unix or DOS the environment variable PATH determines where the system looks for executable programs. So, of course people want to know how they access environment variables from their Java program.
The answer is that you can do this in all modern versions of Java, but you should exercise caution in depending on being able to specify environment variables because some rare operating systems may not provide them. That said, it’s unlikely you’ll run into such a system because all “standard” desktop systems provide them at present.
In some ancient versions of Java, System.getenv()
was deprecated and/or just didn’t work. Nowadays the getenv()
method is no longer deprecated, though it still carries the warning that System Properties (see Recipe 2.2) should be used instead. Even among systems that support environment variables, their names are case sensitive on some platforms and case insensitive on others. The code in Example 2-1 is a short program that uses the getenv()
method.
public
class
GetEnv
{
public
static
void
main
(
String
[]
argv
)
{
System
.
out
.
println
(
"System.getenv("PATH") = "
+
System
.
getenv
(
"PATH"
));
}
}
Running this code will produce output similar to the following:
C:javasrc>java environ.GetEnv System.getenv("PATH") = C:windowsin;c:jdk1.8in;c:documents and settingsianin C:javasrc>
The no-argument form of the method System.getenv()
returns all the environment
variables in the form of an immutable String Map
. You can iterate through this map
and access all the user’s settings or retrieve multiple environment settings.
Both forms of getenv()
require you to have permissions to access the environment, so they typically do not work in restricted environments such as applets.
You need to get information from the system properties.
Use System.getProperty()
or System.getProperties()
.
What is a property anyway? A property is just a name and value pair stored in a java.util.Properties
object, which we discuss more fully in Recipe 7.10.
The System.Properties
object controls and describes the Java runtime. The System
class has a static Properties
member whose content is the merger of operating system specifics (os.name
, for example), system and user tailoring (java.class.path
), and properties defined on the command line (as we’ll see in a moment). Note that the use of periods in these names (like os.arch
, os.version
, java.class.path
, and java.lang.version
) makes it look as though there is a hierarchical relationship similar to that for package/class names. The Properties
class, however, imposes no such relationships: each key is just a string, and dots are not special.
To view all the defined system properties, you can iterate through the output of calling System.getProperties()
as in Example 2-2.
jshell
>
System
.
getProperties
().
forEach
((
k
,
v
)
->
System
.
out
.
println
(
k
+
"->"
+
v
))
awt
.
toolkit
->
sun
.
awt
.
X11
.
XToolkit
java
.
specification
.
version
->
11
sun
.
cpu
.
isalist
->
sun
.
jnu
.
encoding
->
UTF
-
8
java
.
class
.
path
->.
java
.
vm
.
vendor
->
Oracle
Corporation
sun
.
arch
.
data
.
model
->
64
java
.
vendor
.
url
->
http:
//java.oracle.com/
user
.
timezone
->
os
.
name
->
OpenBSD
java
.
vm
.
specification
.
version
->
11
...
many
more
...
jshell
>
Remember that properties whose names begin with “sun” are unsupported and subject to change.
To retrieve one system-provided property, use System.getProperty(propName)
.
If I just wanted to find out if the System Properties
had a property named "pencil_color"
, I could say:
String
sysColor
=
System
.
getProperty
(
"pencil_color"
);
But what does that return? Surely Java isn’t clever enough to know about everybody’s
favorite pencil color? Right you are! But we can easily tell Java about our pencil color
(or anything else we want to tell it) using the -D
argument.
When starting a Java runtime, you can define a value in the system properties object using a -D
option.
Its argument must have a name, an equals sign, and a value, which are parsed the same way as in a properties file (see Recipe 7.10). You can have more than one -D
definition between the java
command and your class name on the command line. At the Unix or Windows command line, type:
java -D"pencil_color=Deep Sea Green" environ.SysPropDemo
When running this under an IDE, put the variable’s name and value in the appropriate dialog box, for example, in Eclipse’s “Run Configuration” dialog under “Program Arguments”. You can also set environment variables and system properties using the build tools (Maven, Gradle, etc.).
The SysPropDemo
program has code to extract just one or a few properties, so you can run it like this:
$ java environ.SysPropDemo os.arch os.arch = x86
If you invoke the SysPropDemo
program with no arguments, it outputs the same information
as the jshell
fragment in Example 2-2.
Which reminds me—this is a good time to mention system-dependent code. Recipe 2.3 talks about OS-dependent code and release-dependent code.
Recipe 7.10 lists more details on using and naming your own Properties
files. The javadoc page for java.util.Properties
lists the exact rules used in the load()
method, as well as other details.
You need to write code that adapts to the underlying operating system.
You can use System.Properties
to find out the Java version and the operating system,
various features in the File
class to find out some platform-dependent features,
and java.awt.TaskBar
to see if you can use the system-dependent Taskbar or Dock.
Some things depend on the version of Java you are running.
Use System.getProperty()
with an argument of java.specification.version
.
Alternatively, and with greater generality, you may want to test for the presence or absence of particular classes. One way to do this is with Class.forName("class")
(see Chapter 17), which throws an exception if the class cannot be loaded—a good indication that it’s not present in the runtime’s library. Example 2-3 shows code for this, from an application wanting to find out whether the common Swing UI components are
available. The javadoc for the standard classes reports the version of the JDK in which this class first appeared, under the heading “Since.” If there is no such heading, it normally means that the class has been present since the beginnings of Java:
public
class
CheckForSwing
{
public
static
void
main
(
String
[]
args
)
{
try
{
Class
.
forName
(
"javax.swing.JButton"
);
}
catch
(
ClassNotFoundException
e
)
{
String
failure
=
"Sorry, but this version of MyApp needs "
+
"a Java Runtime with JFC/Swing components "
+
"having the final names (javax.swing.*)"
;
// Better to make something appear in the GUI. Either a
// JOptionPane, or: myPanel.add(new Label(failure));
System
.
err
.
println
(
failure
);
}
// No need to print anything here - the GUI should work...
}
}
It’s important to distinguish between testing this code at compile time and at runtime. In both cases, it must be compiled on a system that includes the classes you are testing for: JDK >= 1.1 and Swing, respectively. These tests are only attempts to help the poor backwater Java runtime user trying to run your up-to-date application. The goal is to provide this user with a message more meaningful than the simple “class not found” error that the runtime gives. It’s also important to note that this test becomes unreachable if you write it inside any code that depends on the code you are testing for. Put the test early in the main flow of your application, before any GUI objects are constructed. Otherwise the code just sits there wasting space on newer runtimes and never gets run on Java systems that don’t include Swing. Obviously this is a very early example, but you can use the same technique to test for any runtime feature added at any stage of Java’s evolution (see Appendix A for an outline of the features added in each release of Java). You can also use this technique to determine whether a needed third-party library has been successfully added to your classpath.
Also, although Java is designed to be portable, some things aren’t. These include
such variables as the filename separator. Everybody on Unix knows that the
filename separator is a slash character (/) and that a backward slash, or
backslash (), is an escape character. Back in the late 1970s, a group at
Microsoft was actually working on Unix—their version was called
Xenix, later taken over by SCO—and the people working on DOS saw and liked
the Unix filesystem model. The earliest versions of MS-DOS didn’t have
directories; it just had user numbers like the system it was a clone of,
Digital Research CP/M (itself a clone of various other systems). So the
Microsoft developers set out to clone the Unix filesystem organization.
Unfortunately, MS-DOS had already committed the slash character for use as an
option delimiter, for which Unix had used a dash (-
); and the PATH
separator (:) was also used as a drive letter delimiter, as in C
: or
A
:. So we now have commands like those shown
in Table 2-1.
System | Directory list command | Meaning | Example PATH setting |
---|---|---|---|
Unix |
ls -R / |
Recursive listing of /, the top-level directory |
PATH=/bin:/usr/bin |
DOS |
dir/s |
Directory with subdirectories option (i.e., recursive) of , the top-level directory (but only of the current drive) |
PATH=C:windows;D:mybin |
Where does this get us? If we are going to generate filenames in Java, we may need to know whether to put a / or a or some other character. Java has two solutions to this. First, when moving between Unix and Microsoft systems, at least, it is permissive: either / or can be used,1 and the code that deals with the operating system sorts it out. Second, and more generally, Java makes the platform-specific information available in a platform-independent way. For the file separator (and also the PATH separator), the java.io.File
class makes available some static variables containing this information. Because the File
class manages platform dependent information, it makes sense to anchor this information here. The variables are shown in Table 2-2.
Name | Type | Meaning |
---|---|---|
|
|
The system-dependent filename separator character (e.g., / or ) |
|
|
The system-dependent filename separator character (e.g., / or ) |
|
|
The system-dependent path separator character, represented as a string for convenience |
|
|
The system-dependent path separator character |
Both filename and path separators are normally characters, but they are also available in String
form for convenience.
A second, more general, mechanism is the system Properties object mentioned in Recipe 2.2. You can use this to determine the operating system you are running on. Here is code that simply lists the system properties; it can be informative to run this on several different implementations:
public
class
SysPropDemo
{
public
static
void
main
(
String
[]
argv
)
throws
IOException
{
if
(
argv
.
length
==
0
)
// tag::sysprops[]
System
.
getProperties
().
list
(
System
.
out
);
// end::sysprops[]
else
{
for
(
String
s
:
argv
)
{
System
.
out
.
println
(
s
+
" = "
+
System
.
getProperty
(
s
));
}
}
}
}
Some OSes, for example, provide a mechanism called the null device that can be used to discard output (typically used for timing purposes). Here is code that asks the system properties for the os.name and uses it to make up a name that can be used for discarding data (if no null device is known for the given platform, we return the name jnk
, which means that on such platforms, we’ll occasionally create, well, junk files; I just remove these files when I stumble across them):
package
com
.
darwinsys
.
lang
;
import
java.io.File
;
/** Some things that are system-dependent. * All methods are static. * @author Ian Darwin */
public
class
SysDep
{
final
static
String
UNIX_NULL_DEV
=
"/dev/null"
;
final
static
String
WINDOWS_NULL_DEV
=
"NUL:"
;
final
static
String
FAKE_NULL_DEV
=
"jnk"
;
/** Return the name of the null device on platforms which support it, * or "jnk" (to create an obviously well-named temp file) otherwise. * @return The name to use for output. */
public
static
String
getDevNull
(
)
{
if
(
new
File
(
UNIX_NULL_DEV
)
.
exists
(
)
)
{
return
UNIX_NULL_DEV
;
}
String
sys
=
System
.
getProperty
(
"os.name"
)
;
if
(
sys
=
=
null
)
{
return
FAKE_NULL_DEV
;
}
if
(
sys
.
startsWith
(
"Windows"
)
)
{
return
WINDOWS_NULL_DEV
;
}
return
FAKE_NULL_DEV
;
}
}
If /dev/null
exists, use it.
If not, ask System.properties
if it knows the OS name.
Nope, so give up, return jnk
.
We know it’s Microsoft Windows, so use NUL:
.
All else fails, go with jnk
.
Although Java’s Swing GUI aims to be portable, Apple’s implementation for macOS does not automatically do the right thing for everyone. For example, a JMenuBar
menu container appears by default at the top of the application window. This is the norm on Windows and on most Unix platforms, but Mac users expect the menu bar for the active application to appear at the top of the screen. To enable normal behavior, you have to set the System
property apple.laf.useScreenMenuBar
to the value true
before the Swing GUI starts up. You might want to set some other properties too, such as a short name for your application to appear in the menu bar (the default is the full class name of your main application class).
There is an example of this in the book’s source code, at src/main/java/gui/MacOsUiHints.java.
There is probably no point in setting these properties unless you are, in
fact, being run under macOS. How do you tell? Apple’s recommended way
is to check for the system property mrj.runtime
and, if so, assume
you are on macOS:
boolean isMacOS = System.getProperty("mrj.version") != null; if (isMacOS) { System.setProperty("apple.laf.useScreenMenuBar", "true"); System.setProperty("com.apple.mrj.application.apple.menu.about.name", "My Super App"); }
On the other hand, these properties are likely harmless on non-Mac systems, so you could just skip the test and set the two properties unconditionally.
Finally, the Mac’s Dock or the TaskBar on most other systems can be accessed
using the java.awt.Taskbar
class that was added in Java 9.
This is not discussed here, but there is an example TaskbarDemo
in the gui
subdirectory.
You have a JAR file of classes you want to use.
Simply add the JAR file to your CLASSPATH.
As you build more sophisticated applications, you will need to use more and more third-party libraries. You can add these to your CLASSPATH.
It used to be recommended that you drop these JAR files into the Java Extensions mechanism directory, typically something like jdk1.xjrelibext., instead of listing each JAR file in your CLASSPATH variable. However, this is no longer generally recommended and is no longer available in the latest JDKs. Instead, you may wish to use build tools like Maven (see Recipe 1.7) or Gradle, as well as IDEs, to automate the addition of JAR files to your classpath.
One reason I’ve never been fond of using the extensions directory is that it requires modifying the installed JDK or JRE, which can lead to maintenance issues and problems when a new JDK or JRE is installed.
Java 9 introduced a major change to Java, the Java 9 Modules System for program modularization, which we discuss in Recipe 2.5.
You are using Java 9 or later, and need to deal with the Modules mechanism.
Read on.
Java’s Modules System, formerly known as Project Jigsaw, was designed to handle
the need to build large applications out of many small pieces.
To an extent this problem had been solved by tools like Maven and Gradle, but
the Modules system solves a slightly different problem than those tools.
Maven or Gradle will find dependencies, download them, install them on your
development and test runtimes, and package them into runnable JAR files.
The Modules system is more concerned with the visbility of classes from one chunk of application code
to another, typically provided by different developers who may not know or trust each other.
As such, it is an admission that Java’s original set of access modifiers—such as public
, private
,
protected
, and default visibility—was not sufficient for building large-scale applications.
What follows is a brief discussion of using JPMS, the Java Platform Module System, to import modules into your application. There is an introduction to creating your own modules in Chapter 15. For a more detailed presentation, you should refer to a book-length treatment such as Java 9 Modularity: Patterns and Practices for Developing Maintainable Applications by Sander Mak and Paul Bakker (O’Reilly).
Java has always been a language for large-scale development. Object orientation is one of the keys: classes and objects group methods, and access modifiers can be applied so that public and private methods are clearly separated. When developing large applications, having just a single flat namespace of classes is still not enough. Enter packages: they gather classes into logical groups within their own namespace. Access control can be applied at the package level as well so that some classes are only accessible inside a package. Modules are the next logical step up. A module groups some number of related packages, has a distinct name, and can restrict access to some packages while exposing other packages to different modules as public API.
One thing to understand at the outset: JPMS is not a replacement for your existing build tool.
Whether you use Maven, Gradle, Ant, or just dump all needed JAR files into a lib directory,
you still need to do that.
Also, don’t confuse Maven’s modules
with JPMS modules
; the former is the physical structuring
of a project into subprojects, and the latter is something the Java platform (compiler, runtime) understands.
Usually when working with Java modules, each Java module will equate to a single Maven module.
When you’re dealing with a tiny, self-contained program, you don’t need to be concerned with modules. Just put all the necessary JAR files on your classpath at compile time and runtime, and all will be well. Probably.
You may see warning messages like this along the way:
Illegal reflective access by com.foo.Bar (file:/Users/ian/.m2/repository/com/foo/1.3.1/foo-1.3.1.jar) to field java.util.Properties.defaults Please consider reporting this to the maintainers of com.foo.Bar Use --illegal-access=warn to enable warnings of further illegal reflective access operations All illegal access operations will be denied in a future release
The warning message comes about as a result of JPMS doing its job, checking that no types are accessed in encapsulated packages within a module. Such messages will go away over time as all public Java libraries and all apps being developed get modularized.
Why will all be well only “probably”? If you are using certain classes that were deprecated over the
last few releases, things won’t compile. For that, you must make the requisite modules available.
In the unsafe subdirectory (also a Maven module) under javasrc, there is a class called
LoadAverage
. The load average is a feature of Unix/Linux systems that gives a rough measure of
system load or busyness, by reporting the number of processes that are waiting to be run. There are almost always more processes running than CPU cores to run them on, so some always have to wait.
Higher numbers mean a busier system with slower response.
Sun’s unsupported Unsafe
class has a method for obtaining the load average, on systems
that support it.
The code has to use the Reflection API (see
Chapter 17) to obtain the Unsafe
object; if you try to instantiate Unsafe
directly
you will get a SecurityException
(this was the case before the Modules system).
Once the instance is obtained and casted to Unsafe
, you can invoke methods
such as loadAverage()
(Example 2-4).
public
class
LoadAverage
{
public
static
void
main
(
String
[]
args
)
throws
Exception
{
Field
f
=
Unsafe
.
class
.
getDeclaredField
(
"theUnsafe"
);
f
.
setAccessible
(
true
);
Unsafe
unsafe
=
(
Unsafe
)
f
.
get
(
null
);
int
nelem
=
3
;
double
loadAvg
[]
=
new
double
[
nelem
];
unsafe
.
getLoadAverage
(
loadAvg
,
nelem
);
for
(
double
d
:
loadAvg
)
{
System
.
out
.
printf
(
"%4.2f "
,
d
);
}
System
.
out
.
println
();
}
}
This code, which used to compile, gives warnings.
If we are using Java Modules, we must modify our module-info.java
file to tell the compiler and VM that we require use of the module with the
semi-obvious name jdk.unsupported
.
module
javasrc
.
unsafe
{
requires
jdk
.
unsupported
;
// others...
}
We’ll say more about the module file format in Recipe 15.9).
Now that we have the code in place and the module file in the top level of the source folder,
we can build the project, run the program, and compare its output against the system-level tool for
displaying the load average, uptime
.
We’ll still get the “internal proprietary API” warnings, but it works:
$ java -version openjdk version "14-ea" 2020-03-17 OpenJDK Runtime Environment (build 14-ea+27-1339) OpenJDK 64-Bit Server VM (build 14-ea+27-1339, mixed mode, sharing) $ mvn clean package [INFO] Scanning for projects... [INFO] [INFO] --------------------< com.darwinsys:javasrc-unsafe >-------------------- [INFO] Building javasrc - Unsafe 1.0.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ javasrc-unsafe --- [INFO] Deleting /Users/ian/workspace/javasrc/unsafe/target [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ javasrc-unsafe --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory /Users/ian/workspace/javasrc/unsafe/src/main/resources [INFO] [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ javasrc-unsafe --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 2 source files to /Users/ian/workspace/javasrc/unsafe/target/classes [WARNING] /Users/ian/workspace/javasrc/unsafe/src/main/java/unsafe/LoadAverage.java:[3,16] sun.misc.Unsafe is internal proprietary API and may be removed in a future release [WARNING] /Users/ian/workspace/javasrc/unsafe/src/main/java/unsafe/LoadAverage.java:[12,27] sun.misc.Unsafe is internal proprietary API and may be removed in a future release [WARNING] /Users/ian/workspace/javasrc/unsafe/src/main/java/unsafe/LoadAverage.java:[14,17] sun.misc.Unsafe is internal proprietary API and may be removed in a future release [WARNING] /Users/ian/workspace/javasrc/unsafe/src/main/java/unsafe/LoadAverage.java:[14,34] sun.misc.Unsafe is internal proprietary API and may be removed in a future release [INFO] [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ javasrc-unsafe --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory /Users/ian/workspace/javasrc/unsafe/src/test/resources [INFO] [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ javasrc-unsafe --- [INFO] No sources to compile [INFO] [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ javasrc-unsafe --- [INFO] No tests to run. [INFO] [INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ javasrc-unsafe --- [INFO] Building jar: /Users/ian/workspace/javasrc/unsafe/target/javasrc-unsafe-1.0.0-SNAPSHOT.jar [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 4.668 s [INFO] Finished at: 2020-01-05T14:53:55-05:00 [INFO] ------------------------------------------------------------------------ $ $ java -cp target/classes unsafe/LoadAverage 3.54 1.94 1.62 $ uptime 14:54 up 1 day, 21:50, 5 users, load averages: 3.54 1.94 1.62 $
Thankfully, it works and gives the same numbers as the standard Unix uptime command. At least, it works on Java 11. As the warnings imply, it may (i.e., probably will) be removed in a later release.
If you are building a more complex app, you will probably need to put together a more complete module-info.java file. But at this stage it’s primarily a matter of requiring the modules you need. The standard Java API is divided into several modules, which you can list using the java command:
$ java --list-modules java.base java.compiler java.datatransfer java.desktop java.instrument java.logging java.management java.management.rmi java.naming java.net.http java.prefs java.rmi java.scripting java.se java.security.jgss java.security.sasl java.smartcardio java.sql java.sql.rowset java.transaction.xa java.xml java.xml.crypto ... plus a bunch of JDK modules ...
Of these, java.base
is always available and doesn’t need to be listed in your module file,
java.desktop
adds AWT and Swing for graphics,
and java.se
includes basically all of what used to be public API in the Java SDK.
If our Load Average program wanted to display the result in a Swing window, for example,
it would need to add this into its module file:
requires java.desktop;
When your application is big enough to be divided into tiers or layers, you will probably want to describe these modules using JPMS. Since that topic comes under the heading of packaging, it is described in Recipe 15.9.
1 When compiling strings for use on Windows, remember to double them because is an escape character in most places other than the MS-DOS command line: String rootDir = "C:\";
.
3.138.120.136