System, but they may not be reproduced for publication



Yüklə 83 Mb.
Pdf görüntüsü
səhifə67/82
tarix19.04.2023
ölçüsü83 Mb.
#106251
1   ...   63   64   65   66   67   68   69   70   ...   82
Java A Beginner’s Guide, Eighth Edition ( PDFDrive )

type[]::new
Here, type specifies the type of object being created. For example, assuming the
form of MyClass shown in the preceding example and given the
MyClassArrayCreator interface shown here:
the following creates an array of MyClass objects and gives each element an initial
value:
Here, the call to func(3) causes a three­element array to be created. This example
can be generalized. Any functional interface that will be used to create an array
must contain a method that takes a single int parameter and returns a reference to
the array of the specified size.


As a point of interest, you can create a generic functional interface that can be used
with other types of classes, as shown here:
For example, you could create an array of five Thread objects like this:
One last point: In the case of creating a constructor reference for a generic class, you
can specify the type parameter in the normal way, after the class name. For example, if
MyGenClass is declared like this:
then the following creates a constructor reference with a type argument of Integer:
Because of type inference, you won’t always need to specify the type argument, but you
can when necessary.
PREDEFINED FUNCTIONAL INTERFACES
Up to this point, the examples in this chapter have defined their own functional
interfaces so that the fundamental concepts behind lambda expressions and functional
interfaces could be clearly illustrated. In many cases, however, you won’t need to define
your own functional interface because the package java.util.function provides
several predefined ones. Here is a sampling:


The following program shows the Predicate interface in action. It uses Predicate as
the functional interface for a lambda expression the determines if a number is even.
Predicate’s abstract method is called test(), and it is shown here:
boolean test(T val)
It must return true if val satisfies some constraint or condition. As it is used here, it
will return true if val is even.
Ask the Expert
Q
: At the start of this chapter, you mentioned that the inclusion of
lambda expressions resulted in new capabilities being incorporated
into the API library. Can you give me an example?


A
: One example is the stream package java.util.stream. This package defines
several stream interfaces, the most general of which is Stream. As it relates to
java.util.stream, a stream is a conduit for data. Thus, a stream represents a
sequence of objects. Furthermore, a stream supports many types of operations that
let you create a pipeline that performs a series of actions on the data. Often, these
actions are represented by lambda expressions. For example, using the stream API,
you can construct sequences of actions that resemble, in concept, the type of
database queries for which you might use SQL. Furthermore, in many cases, such
actions can be performed in parallel, thus providing a high level of efficiency,
especially when large data sets are involved. Put simply, the stream API provides a
powerful means of handling data in an efficient, yet easy to use way. One last point:
although the streams supported by the new stream API have some similarities with
the I/O streams described in 
Chapter 10
, they are not the same.
The program produces the following output:
 Chapter 14 Self Test
1.
What is the lambda operator?
2.
What is a functional interface?
3.
How do functional interfaces and lambda expressions relate?
4.
What are the two general types of lambda expressions?
5.
Show a lambda expression that returns true if a number is between 10 and 20,
inclusive.
6.
Create a functional interface that can support the lambda expression you created in
question 5. Call the interface MyTest and its abstract method testing().
7.
Create a block lambda that computes the factorial of an integer value. Demonstrate
its use. Use NumericFunc, shown in this chapter, for the functional interface.
8.
Create a generic functional interface called MyFunc. Call its abstract method


func(). Have func() return a reference of type T. Have it take a parameter of type T.
(Thus, MyFunc will be a generic version of NumericFunc shown in the chapter.)
Demonstrate its use by rewriting your answer to question 7 so it uses MyFunc
rather than NumericFunc.
9.
Using the program shown in 
Try This 14­1
, create a lambda expression that removes
all spaces from a string and returns the result. Demonstrate this method by passing it to
changeStr().
10.
Can a lambda expression use a local variable? If so, what constraint must be met?
11.
If a lambda expression throws a checked exception, the abstract method in the
functional interface must have a throws clause that includes that exception. True or
False?
12.
What is a method reference?
13.
When evaluated, a method reference creates an instance of the ____________
___________ supplied by its target context.
14.
Given a class called MyClass that contains a static method called
myStaticMethod(), show how to specify a method reference to myStaticMethod().
15.
Given a class called MyClass that contains an instance method called
myInstMethod() and assuming an object of MyClass called mcObj, show how to
create a method reference to myInstMethod() on mcObj.
16.
To the MethodRefDemo2 program, add a new method to MyIntNum called
hasCommonFactor(). Have it return true if its int argument and the value stored in
the invoking MyIntNum object have at least one factor in common. For example, 9
and 12 have a common factor, which is 3, but 9 and 16 have no common factor.
Demonstrate hasCommonFactor() via a method reference.
17.
How is a constructor reference specified?
18.
Java defines several predefined functional interfaces in what package?


Chapter 15
Modules
Key Skills & Concepts

Know the definition of a module

Know Java’s module­related keywords

Declare a module by use of the module keyword

Use requires and exports

Understand the purpose of module­info.java
History
Topics
Tutorials
Offers & Deals
Highlights
Settings
Support
Sign Out


B

Use javac and java to compile and run module­based programs

Understand the purpose of java.base

Understand how pre­module legacy code is supported

Export a package to a specific module

Use implied readability

Use services in a module
eginning with JDK 9, an important feature called modules was added to Java. Modules
give you a way to describe the relationships and dependencies of the code that
comprises an application. Modules also let you control which parts of a module are
accessible to other modules and which are not. Through the use of modules you can
create more reliable, scalable programs.
As a general rule, modules are most helpful to large applications because they help
reduce the management complexity often associated with a large software system.
However, small programs also benefit from modules because the Java API library has
now been organized into modules. Thus, it is now possible to specify which parts of the
API are required by your program and which are not. This makes it possible to deploy
programs with a smaller run­time footprint, which is especially important when
creating code for small devices, such as those intended to be part of the Internet of
Things (IoT).
Support for modules is provided both by language elements, including several
keywords, and by enhancements to javac, java, and other JDK tools. Furthermore,
new tools and file formats were introduced. As a result, the JDK and the run­time
system were substantially upgraded to support modules. In short, modules constitute a
major addition to, and evolution of, the Java language. This chapter introduces the key
aspects of this important, recently added capability.
MODULE BASICS
In its most fundamental sense, a module is a grouping of packages and resources that
can be collectively referred to by the module’s name. A module declaration specifies the
name of a module and defines the relationship a module and its packages have to other
modules. Module declarations are program statements in a Java source file and are


supported by several module­related keywords added to Java by JDK 9. They are
shown here:
It is important to understand that these keywords are recognized as keywords only in
the context of a module declaration. Otherwise, they are interpreted as identifiers in
other situations. Thus, the keyword module could, for example, also be used as a
parameter name, but such a use is certainly not now recommended.
A module declaration is contained in a file called module­info.java. Thus, a module
is defined in a Java source file. This file is then compiled by javac into a class file and is
known as a module descriptor. The module­info.java file must contain only a
module definition. It is not a general­purpose file.
A module declaration begins with the keyword module. Here is its general form:
The name of the module is specified by moduleName, which must be a valid Java
identifier or a sequence of identifiers separated by periods. The module definition is
specified within the braces. Although a module definition may be empty (which results
in a declaration that simply names the module), typically it specifies one or more
clauses that define the characteristics of the module.
Ask the Expert
Q
: Why are the module­related keywords, such as module and requires,
recognized as keywords only in the context of a module declaration?
A
: Restricting their use as keywords to a module declaration prevents problems
with preexisting code that uses one or more of them as identifiers. For example,
consider a situation in which a pre­JDK 9 program uses requires as the name of a
variable. When that program is ported to a modern version of Java if requires were
recognized as a keyword outside a module declaration, then any other place in


which it is used would result in a compilation error. By recognizing requires as a
keyword only within a module declaration, any other uses of requires in the
program are unaffected and remain valid. Of course, the same goes for the other
module­related keywords. Because they are context­sensitive, the module­related
keywords are formally called restricted keywords.
A Simple Module Example
At the foundation of a module’s capabilities are two key features. The first is a module’s
ability to specify that it requires another module. In other words, one module can
specify that it depends on another. A dependence relationship is specified by use of a
requires statement. By default, the presence of the required module is checked at both
compile time and run time. The second key feature is a module’s ability to control
which, if any, of its packages are accessible by another module. This is accomplished by
use of the exports keyword. The public and protected types within a package are
accessible to other modules only if they are explicitly exported. Here we will develop an
example that introduces both of these features.
The following example creates a modular application that demonstrates some simple
mathematical functions. Although this application is purposely very small, it illustrates
the core concepts and procedures required to create, compile, and run module­based
code. Furthermore, the general approach shown here also applies to larger, real­world
applications. It is strongly recommended that you work through the example on your
computer, carefully following each step.
NOTE
This chapter shows the process of creating, compiling, and running module­based code
by use of the command­line tools. This approach has two advantages. First, it works for
all Java programmers, because no IDE is required. Second, it very clearly shows the
fundamentals of the module system, including how it utilizes directories. To follow
along, you will need to manually create a number of directories and ensure that each
file is placed in its proper directory. As you might expect, when creating real­world,
module­based applications you will likely find a module­aware IDE easier to use
because, typically, it will automate much of the process. However, learning the
fundamentals of modules using the command­line tools ensures that you have a solid


understanding of the topic.
The application defines two modules. The first module is called appstart. It contains a
package called appstart.mymodappdemo that defines the application’s entry point
in a class called MyModAppDemo. Thus, MyModAppDemo contains the
application’s main( ) method. The second module is called appfuncs. It contains a
package called appfuncs.simplefuncs that includes the class SimpleMathFuncs.
This class defines three static methods that implement some simple mathematical
functions. The entire application will be contained in a directory tree that begins at
mymodapp.
Before continuing, a few words about module names are appropriate. First, in the
examples that follow, the name of a module (such as appfuncs) is the prefix of the
name of a package (such as appfuncs.simplefuncs) that it contains. This is not
required, but is used here as a way of clearly indicating to what module a package
belongs. In general, when learning about and experimenting with modules, short,
simple names, such as those used in this chapter, are helpful, and you can use any sort
of convenient names that you like. However, when creating modules suitable for
distribution, you must be careful with the names you choose because you will want
those names to be unique. At the time of this writing, the suggested way to achieve this
is to use the reverse domain name method. In this method, the reverse domain name of
the domain that “owns” the project is used as a prefix for the module. For example, a
project associated with 
herbschildt.com
would use com.herbschildt as the module
prefix. (The same goes for package names.) Because modules are a recent addition to
Java, naming conventions may evolve over time. You will want to check the Java
documentation for current recommendations.
Let’s now begin. Start by creating the necessary source code directories by following
these steps:
1. Create a directory called mymodapp. This is the top­level directory for the entire
application.
2. Under mymodapp, create a subdirectory called appsrc. This is the top­level
directory for the application’s source code.
3. Under appsrc, create the subdirectory appstart. Under this directory, create a
subdirectory also called appstart. Under this directory, create the directory
mymodappdemo. Thus, beginning with appsrc, you will have created this tree:


4. Also under appsrc, create the subdirectory appfuncs. Under this directory, create
a subdirectory also called appfuncs. Under this directory, create the directory called
simplefuncs. Thus, beginning with appsrc, you will have created this tree:
Your directory tree should look like that shown here.
After you have set up these directories, you can create the application’s source files.
This example will use four source files. Two are the source files that define the
application. The first is SimpleMathFuncs.java, shown here. Notice that
SimpleMathFuncs is packaged in appfuncs.simplefuncs.


SimpleMathFuncs defines three simple static math functions. The first, isFactor(
), returns true if a is a factor of b. The lcf( ) method returns the smallest factor
common to both a and b. In other words, it returns the least common factor of a and b.
The gcf( ) method returns the greatest common factor of a and b. In both cases, 1 is
returned if no common factors are found. This file must be put in the following
directory:


This is the appfuncs.simplefuncs package directory.
The second source file is MyModAppDemo.java, shown next. It uses the methods in
SimpleMathFuncs. Notice that it is packaged in appstart.mymodappdemo. Also
note that it imports the SimpleMathFuncs class because it depends on
SimpleMathFuncs for its operation.
This file must be put in the following directory:
This is the directory for the appstart.mymodappdemo package.
Next, you will need to add module­info.java files for each module. These files contain
the module definitions. First, add this one, which defines the appfuncs module:
Notice that appfuncs exports the package appfuncs.simplefuncs, which makes it
accessible to other modules. This file must be put into this directory:
Thus, it goes in the appfuncs module directory, which is above the package
directories.


Finally, add the module­info.java file for the appstart module. It is shown here.
Notice that appstart requires the module appfuncs.
This file must be put into its module directory:
Before examining the requires, exports, and module statements more closely, let’s
first compile and run this example. Be sure that you have correctly created the
directories and entered each file into its proper directory, as just explained.
Compile and Run the First Module Example
Beginning with JDK 9, javac has been updated to support modules. Thus, like all other
Java programs, module­based programs are compiled using javac. The process is easy,
with the primary difference being that you will usually explicitly specify a module path.
A module path tells the compiler where the compiled files will be located. When
following along with this example, be sure that you execute the javac commands from
the mymodapp directory in order for the paths to be correct. Recall that mymodapp
is the top­level directory for the entire module application.
To begin, compile the SimpleMathFuncs.java file, using this command:
Remember, this command must be executed from the mymodapp directory. Notice
the use of the ­d option. This tells javac where to put the output .class file. For the
examples in this chapter, the top of the directory tree for compiled code is
appmodules. This command will automatically create the output package directories
for appfuncs.simplefuncs under appmodules\appfuncs as needed.
Next, here is the javac command that compiles the module­info.java file for the
appfuncs module:


This puts the module­info.class file into the appmodules\appfuncs directory.
Although the preceding two­step process works, it was shown primarily for the sake of
discussion. It is usually easier to compile a module’s module­info.java file and its
source files in one command line. Here, the preceding two javac commands are
combined into one:
In this case, each compiled file is put in its proper module or package directory.
Now, compile the module­info.java and MyModAppDemo.java files for the
appstart module, using this command:
Notice the ­­module­path option. It specifies the module path, which is the path on
which the compiler will look for the user­defined modules required by the module­
info.java file. In this case, it will look for the appfuncs module because it is needed
by the appstart module. Also, notice that it specifies the output directory as
appmodules\appstart. This means that the module­info.class file will be in the
appmodules\appstart module directory and MyModAppDemo.class will be in
the appmodules\appstart\appstart\mymodappdemo package directory.
Once you have completed the compilation, you can run the application with this java
command:
Here, the ­­module­path option specifies the path to the application’s modules. As
mentioned, appmodules is the directory at the top of the compiled modules tree. The
­m option specifies the class that contains the entry point of the application and, in this
case, the name of the class that contains the main( ) method. When you run the
program, you will see the following output:
A Closer Look at requires and exports


A Closer Look at requires and exports
The preceding module­based example relies on the two foundational features of the
module system: the ability to specify a dependence and the ability to satisfy that
dependence. These capabilities are specified through the use of the requires and
exports statements within a module declaration. Each merits a closer examination at
this time.
Here is the form of the requires statement used in the example:
requires moduleName;
Here, moduleName specifies the name of a module that is required by the module in
which the requires statement occurs. This means that the required module must be
present in order for the current module to compile. In the language of modules, the
current module is said to read the module specified in the requires statement. In
general, the requires statement gives you a way to ensure that your program has
access to the modules that it needs.
Here is the general form of the exports statement used in the example:
exports packageName;
Here, packageName specifies the name of the package that is exported by the module
in which this statement occurs. When a module exports a package, it makes all of the
public and protected types in the package accessible to other modules. Furthermore,
the public and protected members of those types are also accessible. However, if a
package within a module is not exported, then it is private to that module, including all
of its public types. For example, even though a class is declared as public within a
package, if that package is not explicitly exported by an exports statement, then that
class is not accessible to other modules. It is important to understand that the public
and protected types of a package, whether exported or not, are always accessible within
that package’s module. The exports statement simply makes them accessible to
outside modules. Thus, any nonexported package is only for the internal use of its
module.
The key to understanding requires and exports is that they work together. If one
module depends on another, then it must specify that dependence with requires. The
module on which another depends must explicitly export (i.e., make accessible) the
packages that the dependent module needs. If either side of this dependence
relationship is missing, the dependent module will not compile. As it relates to the


foregoing example, MyModAppDemo uses the functions in SimpleMathFuncs. As
a result, the appstart module declaration contains a requires statement that names
the appfuncs module. The appfuncs module declaration exports the
appfuncs.simplefuncs package, thus making the public types in the
SimpleMathFuncs class available. Since both sides of the dependence relationship
have been fulfilled, the application can compile and run. If either is missing, the
compilation will fail. (You will see the results of a missing exports statement when you
answer exercise 10 in the self­test at the end of this chapter.)
It is important to emphasize that requires and exports statements must occur only
within a module statement. Furthermore, a module statement must occur by itself in
a file called module­info.java.
JAVA.BASE AND THE PLATFORM MODULES
As mentioned at the start of this chapter, beginning with JDK 9 the Java API packages
have been incorporated into modules. In fact, the modularization of the API is one of
the primary benefits realized by the addition of the modules. Because of their special
role, the API modules are referred to as platform modules, and their names all begin
with the prefix java. Here are some examples: java.base, java.desktop, and
java.xml. By modularizing the API, it becomes possible to deploy an application with
only the packages that it requires, rather than the entire Java Runtime Environment
(JRE). Because of the size of the full JRE, this is a very important improvement.
The fact that all of the Java API library packages are now in modules gives rise to the
following question: How can the main( ) method in MyModAppDemo in the
preceding example use System.out.println( ) without specifying a requires
statement for the module that contains the System class? Obviously, the program will
not compile and run unless System is present. The same question also applies to the
use of the Math class in SimpleMathFuncs. The answer to this question is found in
java.base.
Of the platform modules, the most important is java.base. It includes and exports
those packages fundamental to Java, such as java.lang, java.io, and java.util, among
many others. Because of its importance, java.base is automatically accessible to all
modules. Furthermore, all other modules automatically require java.base. There is no
need to include a requires java.base statement in a module declaration. (As a point
of interest, it is not wrong to explicitly specify java.base; it’s just not necessary.) Thus,
in much the same way that java.lang is automatically available to all programs without


the use of an import statement, the java.base module is automatically accessible to
all module­based programs without explicitly requesting it.
Because java.base contains the java.lang package, and java.lang contains the
System class, MyModAppDemo in the preceding example can automatically use
System.out.println( ) without an explicit requires statement. The same applies to
the use of the Math class in SimpleMathFuncs, because the Math class is also in
java.lang. As you will see when you begin to create your own module­based
applications, many of the API classes you will commonly need are in the packages
included in java.base. Thus, the automatic inclusion of java.base simplifies the
creation of module­based code because Java’s core packages are automatically
accessible.
One last point: Beginning with JDK 9, the documentation for the Java API now tells
you the name of the module in which a package is contained. If the module is
java.base, then you can use the contents of that package directly. Otherwise, your
module declaration must include a requires clause for the desired module.
Ask the Expert
Q
: I recall that JDK 8 had the ability to use a feature called compact profiles.
Are compact profiles a part of modules?
A
: Compact profiles are a feature that, in some situations, let you specify a subset
of the API library. They are not part of the module system. Moreover, the module
system introduced by JDK 9 fully supersedes them.
LEGACY CODE AND THE UNNAMED MODULE
Another question may have occurred to you when working through the first example
module program. Because Java now supports modules, and the API packages are also
contained in modules, why do all of the other programs in the preceding chapters
compile and run without error even though they do not use modules? More generally,
since there is now over 20 years of Java code in existence and (at the time of this
writing) the vast majority of that code does not use modules, how is it possible to
compile, run, and maintain that legacy code with a JDK 9 or later compiler? Given
Java’s original philosophy of “write once, run everywhere,” this is a very important


question because backward capability must be maintained. As you will see, Java
answers this question by providing an elegant, nearly transparent means of ensuring
backward compatibility with preexisting code.
Support for legacy code is provided by two key features. The first is the unnamed
module. When you use code that is not part of a named module, it automatically
becomes part of the unnamed module. The unnamed module has two important
attributes. First, all of the packages in the unnamed module are automatically exported.
Second, the unnamed module can access any and all other modules. Thus, when a
program does not use modules, all API modules in the Java platform are automatically
accessible through the unnamed module.
The second key feature that supports legacy code is the automatic use of the class path,
rather than the module path. When you compile a program that does not use modules,
the class path mechanism is employed, just as it has been since Java’s original release.
As a result, the program is compiled and run in the same way it was prior to the advent
of modules.
Because of the unnamed module and the automatic use of the class path, there was no
need to declare any modules for the sample programs shown elsewhere in this book.
They run properly whether you compile them with a modern compiler or an earlier one,
such as JDK 8. Thus, even though modules are a recent feature that has a significant
impact on Java, compatibility with legacy code is maintained. This approach also
provides a smooth, nonintrusive, nondisruptive transition path to modules. Thus, it
enables you to move a legacy application to modules at your own pace. Furthermore, it
allows you to avoid the use of modules when they are not needed.
Before moving on, an important point needs to be made. For the types of example
programs used elsewhere in this book, and for example programs in general, there is no
benefit in using modules. Modularizing them would simply add clutter and complicate
them for no reason or benefit. Furthermore, for many simple programs that you will
write when learning the essentials of Java, there is no need to contain them in modules.
For the reasons stated at the start of this chapter, modules are often of the greatest
benefit when creating commercial programs. Therefore, no examples outside this
chapter will use modules. This also allows the examples to be compiled and run in a
pre­JDK 9 environment, which is important to readers using an older version of Java.
Thus, except for the examples in this chapter, the examples in this book work for both
pre­module and post­module JDKs.
EXPORTING TO A SPECIFIC MODULE


EXPORTING TO A SPECIFIC MODULE
The basic form of the exports statement makes a package accessible to any and all
other modules. This is often exactly what you want. However, in some specialized
development situations, it can be desirable to make a package accessible to only a

Yüklə 83 Mb.

Dostları ilə paylaş:
1   ...   63   64   65   66   67   68   69   70   ...   82




Verilənlər bazası müəlliflik hüququ ilə müdafiə olunur ©genderi.org 2024
rəhbərliyinə müraciət

    Ana səhifə