JShell is a Java 9 tool that lets you explore in programming. JShell makes it easy to play around without the fear of disastrous consequences. Java programs often use the same old, tiresome refrain:
public class SomethingOrOther {
public static void main(String args[]) { A Java program requires this verbose introduction because
In Java the entire program is a class.
The main method is called into action automatically when the program begins running.
Anyway, retyping this boilerplate code into an editor window can be annoying, especially when your goal is to test the effect of executing a few simple statements. To fix this problem, the stewards of Java came up with a new tool in Java 9. They call it JShell.
Instructions for launching JShell differ from one computer to the next. For instructions that work on your computer, visit allmycode.com.
When you use JShell, you hardly ever type an entire program. Instead, you type a Java statement, and then JShell responds to your statement, and then you type a second statement, and then JShell responds to your second statement, and then you type a third statement, and so on. A single statement is enough to get a response from JShell.
JShell is only one example of a language’s Read Evaluate Print Loop (REPL). Many programming languages __have REPLs and, with Java 9, the Java language finally has a REPL of its own.
Here, JShell was used to find out how Java responds to assignment statements.
An intimate conversation between a programmer and JShell.
When you run JShell, the dialogue goes something like this:
jshell> You type a statement
JShell responds
jshell> You type another statement
JShell responds
For example, you can type double amountInAccount and then press Enter. JShell responds by displaying
In JShell, semicolons are (to a large extent) optional.
A semicolon was typed at the end of only one of the nine lines.
JShell responds immediately after you type each line.
After amountInAccount was declared to be double, JShell responds by saying that the amountInAccount variable has the value 0.0. After amountInAccount = amountInAccount + 1000000.00 is typed, Shell responds that the new value of amountInAccount is 1000050.22.
You can mix statements from many different Java programs.
You can ask JShell for the value of an expression.
You don’t have to assign the expression’s value to a variable. For example, type
elevatorWeightLimit / weightOfAPerson
JShell responds that the value of elevatorWeightLimit / weightOfAPerson is 9. JShell makes up a temporary name for that value. Above, the name happens to be $8. So, on the next line, when asked for the value of $8 +1, JShell gives the answer 10.
You can even get answers from JShell without using variables.
On the last line, the value of 42 + 7 is requested, and JShell generously answers with the value 49.
While you’re running JShell, you don’t have to retype commands that you’ve already typed. If you press the up-arrow key once, JShell shows you the command that you typed most recently. If you press the up-arrow key twice, JShell shows you the next-to-last command that you typed. And so on. When JShell shows you a command, you can use your left- and right-arrow keys to move to any character in the middle of the command. You can modify characters in the command. Finally, when you press Enter, JShell executes your newly modified command.
To end your run of JShell, you type /exit (starting with a slash). But /exit is only one of many commands you can give to JShell. To ask JShell what other kinds of commands you can use, type /help.
With JShell, you can test your statements before you put them into a full-blown Java program. That makes JShell a truly useful tool.
Visit allmycode.com for instructions on launching JShell on your computer. After launching JShell, type a few lines of code. See what happens when you type some slightly different lines.
You can use nested if statements in Java. __have you seen those cute Russian matryoshka nesting dolls? Open one, and another one is inside. Open the second, and a third one is inside it. You can do the same thing with Java’s if statements. (Talk about fun!)
Check out this code with nested if statements.
import static java.lang.System.out;
import java.util.Scanner;
public class Authenticator2 {
public static void main(String args[]) {
Scanner keyboard = new Scanner(System.in);
out.print("Username: ");
String username = keyboard.next();
if (username.equals("bburd")) {
out.print("Password: ");
String password = keyboard.next();
if (password.equals("swordfish")) {
out.println("You're in.");
} else {
out.println("Incorrect password");
}
} else {
out.println("Unknown user");
}
keyboard.close();
}
}
Check out several runs of the code below. The main idea is that to log on, you __have to pass two tests. (In other words, two conditions must be true.) The first condition tests for a valid username; the second condition tests for the correct password. If you pass the first test (the username test), you march right into another if statement that performs a second test (the password test).
Three runs of the code.
If you fail the first test, you never make it to the second test. Here’s the overall plan.
Don’t try eating with this fork.
The code does a good job with nested if statements, but it does a terrible job with real-world user authentication. First, never show a password in plain view (without asterisks to masquerade the password). Second, don’t handle passwords without encrypting them. Third, don’t tell the malicious user which of the two words (the username or the password) was entered incorrectly. Fourth … well, one could go on and on. The code just isn’t meant to illustrate good username/password practices.
Modify the program so that, if the user clicks Cancel for either the username or the password, the program replies with a Not enough information message.
The Java code you see here uses several API classes and methods. The setTitle, setLayout, setDefaultCloseOperation, add, setSize, and setVisible methods all belong to the javax.swing.JFrame class.
Java code for defining a frame.
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JButton;
@SuppressWarnings("serial")
public class SimpleFrame extends JFrame {
public SimpleFrame() {
setTitle("Don't click the button!");
setLayout(new FlowLayout());
setDefaultCloseOperation(EXIT_ON_CLOSE);
add(new JButton("Panic"));
setSize(300, 100);
setVisible(true);
}
}
Here’s a list of names used in the code:
setTitle: Calling setTitle puts words in the frame’s title bar. (The new SimpleFrame object is calling its own setTitle method.)
FlowLayout: An instance of the FlowLayout class positions objects on the frame in a centered, typewriter fashion. If the frame has only one button on it, that button is centered near the top of the frame. If the frame had eight buttons, five of them may be lined up in a row across the top of the frame and the remaining three would be centered along a second row.
setLayout: Calling setLayout puts the new FlowLayout object in charge of arranging components, such as buttons, on the frame. (The new SimpleFrame object is calling its own setLayout method.)
setDefaultCloseOperation: Calling setDefaultCloseOperation tells Java what to do when you click the little ×in the frame’s upper-right corner. (On a Mac, you click the little red circle in the frame’s upper-left corner.) Without this method call, the frame itself disappears, but the Java Virtual Machine (JVM) keeps running. To stop your program’s run, you __have to perform one more step. (You may __have to look for a Terminate option in Eclipse, IntelliJ IDEA, or NetBeans.)
Calling setDefaultCloseOperation(EXIT_ON_CLOSE) tells Java to shut itself down when you click the × in the frame’s upper-right corner. The alternatives to EXIT_ON_CLOSE are HIDE_ON_CLOSE, DISPOSE_ON_CLOSE, and, of course, DO_NOTHING_ON_CLOSE. Use one of these alternatives when your program has more work to do after the user closes your frame.
JButton: The JButton class lives in the javax.swing package. One of the class’s constructors takes a String instance (such as “Panic“) for its parameter. Calling this constructor makes that String instance into the label on the face of the new button.
add: The new SimpleFrame object calls its add method. Calling the add method places the button on the object’s surface (in this case, the surface of the frame).
setSize: The frame becomes 300 pixels wide and 100 pixels tall. (In the javax.swing package, whenever you specify two dimension numbers, the width number always comes before the height number.)
setVisible: When it’s first created, a new frame is invisible. But when the new frame calls setVisible(true), the frame appears on your computer screen.
The words int and double are examples of primitive types (also known as simple types) in Java. The Java language has exactly eight primitive types. As a newcomer to Java, you can pretty much ignore all but four of these types. (As programming languages go, Java is nice and compact that way.)
The types that you shouldn’t ignore are int, double, char, and boolean.
The char type
Several decades ago, people thought computers existed only for doing big number-crunching calculations. Nowadays, nobody thinks that way. So, if you haven’t been in a cryogenic freezing chamber for the past 20 years, you know that computers store letters, punctuation symbols, and other characters.
The Java type that’s used to store characters is called char. The code below has a simple program that uses the char type. This image shows the output of the program in the code below.
An exciting run of the program of below as it appears in the Eclipse Console view.
In this code, the first initialization stores the letter b in the variable myLittleChar. In the initialization, notice how b is surrounded by single quote marks. In Java, every char literally starts and ends with a single quote mark.
In a Java program, single quote marks surround the letter in a char literal.
Character.toUpperCase. The Character.toUpperCase method does just what its name suggests — the method produces the uppercase equivalent of the letter b. This uppercase equivalent (the letter B) is assigned to the myBigChar variable, and the B that’s in myBigChar prints onscreen.
If you’re tempted to write the following statement,
char myLittleChars = 'barry'; //Don't do this
please resist the temptation. You can’t store more than one letter at a time in a char variable, and you can’t put more than one letter between a pair of single quotes. If you’re trying to store words or sentences (not just single letters), you need to use something called a String.
If you’re used to writing programs in other languages, you may be aware of something called ASCII character encoding. Most languages use ASCII; Java uses Unicode. In the old ASCII representation, each character takes up only 8 bits, but in Unicode, each character takes up 8, 16, or 32 bits. Whereas ASCII stores the letters of the Roman (English) alphabet, Unicode has room for characters from most of the world’s commonly spoken languages.
The only problem is that some of the Java API methods are geared specially toward 16-bit Unicode. Occasionally, this bites you in the back (or it bytes you in the back, as the case may be). If you’re using a method to write Hello on the screen and H e l l o shows up instead, check the method’s documentation for mention of Unicode characters.
It’s worth noticing that the two methods, Character.toUpperCase and System.out.println, are used quite differently in the code above. The method Character.toUpperCase is called as part of an initialization or an assignment statement, but the method System.out.println is called on its own.
The boolean type
A variable of type boolean stores one of two values: true or false. The code below demonstrates the use of a boolean variable. This image shows the output of the program in the code below.
The Brickenchicker dectuplets strike again.
public class ElevatorFitter2 {
public static void main(String args[]) {
System.out.println("True or False?");
System.out.println("You can fit all ten of the");
System.out.println("Brickenchicker dectuplets");
System.out.println("on the elevator:");
System.out.println();
int weightOfAPerson = 150;
int elevatorWeightLimit = 1400;
int numberOfPeople = elevatorWeightLimit / weightOfAPerson;
boolean allTenOkay = numberOfPeople >= 10;
System.out.println(allTenOkay);
}
}
In this code, the allTenOkay variable is of type boolean. To find a value for the allTenOkay variable, the program checks to see whether numberOfPeople is greater than or equal to ten. (The symbols >= stand for greater than or equal to.)
At this point, it pays to be fussy about terminology. Any part of a Java program that has a value is an expression. If you write
weightOfAPerson = 150;
then 150 ,is an expression (an expression whose value is the quantity 150). If you write
numberOfEggs = 2 + 2;
then 2 + 2 is an expression (because 2 + 2 has the value 4). If you write
int numberOfPeople = elevatorWeightLimit / weightOfAPerson;
then elevatorWeightLimit / weightOfAPerson is an expression. (The value of the expression elevatorWeightLimit / weightOfAPerson depends on whatever values the variables elevatorWeightLimit and weightOfAPerson__have when the code containing the expression is executed.)
Any part of a Java program that has a value is an expression.
In the second set of code, numberOfPeople >= 10 is an expression. The expression’s value depends on the value stored in the numberOfPeople variable. But, as you know from seeing the strawberry shortcake at the Brickenchicker family’s catered lunch, the value of numberOfPeople isn’t greater than or equal to ten. As a result, the value of numberOfPeople >= 10 is false. So, in the statement in the second set of code, in which allTenOkay is assigned a value, the allTenOkay variable is assigned a false value.
In the second set of code, System.out.println() is called with nothing inside the parentheses. When you do this, Java adds a line break to the program’s output. In the second set of code, System.out.println() tells the program to display a blank line.
Java lets you define a method within a class. Imagine a table containing the information about two accounts. (If you __have trouble imagining such a thing, just look at the table below.)
Without Object-Oriented Programming
Name
Address
Balance
Barry Burd
222 Cyberspace Lane
24.02
Jane Q. Public
111 Consumer Street
55.63
In this table, each account has three things — a name, an address, and a balance. That’s how things were done before object-oriented programming came along. But object-oriented programming involved a big shift in thinking. With object-oriented programming, each account can __have a name, an address, a balance, and a way of being displayed.
In object-oriented programming, each object has its own built-in functionality. An account knows how to display itself. A string can tell you whether it has the same characters inside it as another string has. A PrintStream instance, such as System.out, knows how to do println. In object-oriented programming, each object has its own methods. These methods are little subprograms that you can call to have an object do things to (or for) itself.
And why is this a good idea? It’s good because you’re making pieces of data take responsibility for themselves. With object-oriented programming, all the functionality that’s associated with an account is collected inside the code for the Account class. Everything you have to know about a string is located in the file String.java. Anything having to do with year numbers (whether they have two or four digits, for instance) is handled right inside the Year class. Therefore, if anybody has problems with your Account class or your Year class, he or she knows just where to look for all the code. That’s great!
Imagine an enhanced account table. In this new table, each object has built-in functionality. Each account knows how to display itself on the screen. Each row of the table has its own copy of a display method. Of course, you don’t need much imagination to picture this table. Check out this table.
The Object-Oriented Way
Name
Address
Balance
Display
Barry Burd
222 Cyberspace Lane
24.02
out.print …
Jane Q. Public
111 Consumer Street
55.63
out.print …
An account that displays itself
In the second table, each account object has four things — a name, an address, a balance, and a way of displaying itself on the screen. After you make the jump to object-oriented thinking, you’ll never turn back. The code below shows programs that implement the ideas in the second table above.
In this code, an account displays itself
import static java.lang.System.out;
public class Account {
String name;
String address;
double balance;
public void display() {
out.print(name);
out.print(" (");
out.print(address);
out.print(") has $");
out.print(balance);
}
}
This code uses the improved account class.
public class UseAccount {
public static void main(String args[]) {
Account myAccount = new Account();
Account yourAccount = new Account();
myAccount.name = "Barry Burd";
myAccount.address = "222 Cyberspace Lane";
myAccount.balance = 24.02;
yourAccount.name = "Jane Q. Public";
yourAccount.address = "111 Consumer Street";
yourAccount.balance = 55.63;
myAccount.display();
System.out.println();
yourAccount.display();
}
}
In the first set of code, the Account class has four things in it: a name, an address, a balance, and a display method. These things match up with the four columns in the second table. So each instance of the Account class has a name, an address, a balance, and a way of displaying itself. The way you call these things is nice and uniform. To refer to the name stored in myAccount, you write
myAccount.name
To get myAccount to display itself on the screen, you write
myAccount.display()
The only difference is the parentheses.
When you call a method, you put parentheses after the method’s name.
The display method’s header
Look again at the code above. A call to the display method is inside the UseAccount class’s main method, but the declaration of the display method is up in the Account class. The declaration has a header and a body. The header has three words and some parentheses:
The word public serves roughly the same purpose as the word public in the first set of code. Roughly speaking, any code can contain a call to a public method, even if the calling code and the public method belong to two different classes. In the example above, the decision to make the display method public is a matter of taste. Normally, when you create a method that’s useful in a wide variety of applications, you declare the method to be public.
The word void tells Java that when the display method is called, the display method doesn’t return anything to the place that called it.
The word display is the method’s name. Every method must have a name. Otherwise, you don’t have a way to call the method.
The parentheses contain all the things you’re going to pass to the method when you call it. When you call a method, you can pass information to that method on the fly. The display method in the first set of code looks strange because the parentheses in the method’s header have nothing inside them. This nothingness indicates that no information is passed to the display method when you call it.
This morning we’re celebrating a rather special occasion over here at AnandTech: our 20th anniversary. Though in a lot of ways it seems like the years __have gone by in a flash, the calendar doesn’t lie: it means we’ve now been doing this for two decades, starting from a quaint CPU review in 1997 and branching out to oh so much more by 2017.
I’ve admittedly never been one for public sentimentality, but as the second Editor-in-chief of AnandTech and a reader for most of its history, it’s a milestone that makes me very proud. When Anand created the site in 1997 it may __have just been a hobby, but by taking it seriously and defining a core set of principles that we still work by today, AnandTech has grown to leave its mark on technology journalism and the products around it, and I intend to continue leaving our mark and helping to shape the technology ecosystem in our own nerdy way.
For the start of AnandTech’s 21st year in the technology news business, I’d like to commemorate the occasion with a look back at what we’ve done, a brief look forward at where we’re going next, and finally, thank the many readers and groups out there who have made it possible. The latter point is especially important to me; while a commemoration is all about the entity or event being remembered, none of this would be possible without everyone who has supported us over the years. So in a way, I consider this an event for you guys as well as it is for us, because you have absolutely had a part in shaping how we go about writing articles and how we interact with hardware vendors.
For that reason, I’m also happy to announce that we’re launching our largest hardware giveaway ever. Over the next 20 days we’ll be giving away piles of hardware as a thank you to our readers who have made this all possible. Even that, I feel, isn’t a big enough thank you for what you guys mean to us. But it’s a start, and it means we can do something fun for you guys on our anniversary.
But first, let’s do a little commemorating.
A Bit of History
AnandTech was founded in 1997 by a then 14 year old Anand Shimpi during his freshman year of high school. At the time the Web was still relatively young, and Anand’s Hardware Tech Page as it was then named didn’t even have its own domain name. Instead, like many other hobby websites, it was hosted on the now long-defunct GeoCities.
In true AnandTech tradition right from the start, the first AnandTech article was the AMD K6 Review. That review is admittedly a mess by modern standards – none of us came into tech writing knowing a fraction of what we do now – but it marked the start of something greater. It reflected Anand’s desire to learn more and to share what he learned with the world, a core mantra that has driven AnandTech all of these years, and still drives us today.
In the intervening 20 years, far too much has happened to even begin to recap it all. AnandTech became a business, and a successful business at that. The Web transformed how technology journalism worked; no longer were articles bound to the rules of print, a month out of date and limited in length to what would fit. Instead articles could go as deep as they needed, and be posted to the wider world as soon as they were ready. Tech news became rapid-fire, and, outside of dedicated technology journals, writers could do more than ever before.
A lot of other organizations had the same idea and covered the PC hardware space as well. Some of those sites are still with us today; many more are not. Between the Dot-com bubble popping and the Great Recession, conditions have winnowed the number of tech sites significantly. AnandTech’s surviving and thriving these events is a combination of hard work and careful planning, to do what needed to be done to survive. But more than that, it’s thanks to you, the readers, who have supported us over all of these years and continue to support us today.
Guess The Interfaces
Meanwhile the technology landscape has changed dramatically over the last 20 years. The PC market was once the growth market for hardware, and over the years we’ve reaped the benefits of improved engineering, improved manufacturing, and improved economy of scale. The $1000 PC I bought in 1997 was, even by the standards of the time, a piece of junk, pairing a Pentium MMX 200 with what ended up being a rather dodgy Socket 7 motherboard. Now one can buy a half-decent laptop for a fraction of the price, and the inflation-adjusted $1500 will buy you an awesome computer, likely with money to spare. The PC industry is still alive and well these days – and we continue to labor hard to cover it – but it’s now a stable, mature industry that doesn’t move at the pace it once did.
A number of hardware companies have folded or been acquired in the intervening years. 3dfx, Cyrix, Abit, BFG. Others have continued to burn brightly the whole time – Intel is still as powerful as they ever have been – and others still have skirted with death only to stage a dramatic comeback. We’ve watched AMD rise, fall, and then Ryzen (again), as the perpetual plucky underdog has always kept the rest of the PC industry off-balance.
The story for the second decade of AnandTech’s existence has been the mobile industry, which for most of that decade has burnt with same fire that forged AnandTech in the beginning. The rapid evolution of smartphones and tablets is nothing short of remarkable, and it has forever changed the face of computing, even for us PC enthusiasts. Mobile devices are a product of the extreme integration we’ve seen over the years, and I would be lying if I said the lack of end-user customization didn’t bother me on some level, but at the same time they’ve expanded our horizons as tech writers and significantly changed how we look at the development of new technology.
1997 was also the year of Microsoft’s famous $150 million investment into Apple, which perpetuated their rise to becoming the most valuable company in the world. The company, whose computers were based around the odd-man-out PowerPC architecture at the time, switched over to x86 in 2005 and has made a significant mark on laptop design, all the while pioneering the modern smartphone and tablet.
(As an aside, at some point early in the previous decade, I talked Anand into trying out a Mac as his desktop workstation. This didn’t go quite as planned, as Anand ended up liking it perhaps a bit too much. Sorry about that.)
A Bit of the Future
Now the technology industry is at a new set of crossroads. Like the PC market before it, the mobile market is maturing and reaching saturation. Meanwhile Moore’s Law, while still alive, is definitely reaching its end-game for traditional silicon. The technology industry has always been remarkable for its continued and sustained growth, and while I’m optimistic that it will continue, no one knows quite what that future will look like.
What I do know is that no matter what, AnandTech will be there to cover it, with the depth our readers deserve and crave. One of Anand’s criticisms of technology journalism – and what’s now one of our core principles – is avoiding the cable TV-ificaition of the Web: the focus on traffic and business over the needs of the readers, leading to shallow, 10 o’clock news-style technology reporting. I will make no effort to hide or downplay the fact that AnandTech is part of a larger business (Purch), but my vision for the site and the focus of the editors of AnandTech remains unchanged. As much as we can, we have to do right by you, the reader, by continuing to learn more about technology and sharing what we learn with you in a deep-but-accessible manner.
While I don’t have any grand plans to announce, over the coming months you’ll be seeing some new faces around AnandTech as we continue to expand and diversify. I am happy with our depth but I feel like we aren’t doing enough in total; we need more reviews, more news, and more articles discussing how things work and how the future will be shaped. So that is my goal for the mid-term, to bring you guys more of what you come to AnandTech for.
We also have some other plans on the drawing board. The site is due for an overhaul, and Ian Cutress might kill me if I don’t let him improve Bench even more. HTTPS is also everywhere, and real soon now we will be enabling it across the site. I don’t have anything specific to announce here, but we aren’t standing by idly. If nothing else, a more regular podcast would be fun to do.
Thank Yous
Making it to 20 years could not have happened without the help of many people over the years, some who are quite public and others who like to blend in and do what they do in quiet.
The biggest thank you goes of course to Anand. While he’s off doing goodness-knows-what at Apple (I don’t know guys, seriously!), the site he founded and the principles he laid out has enabled us to do everything we’ve done for the past 20 years. It’s allowed us to focus on quality and depth, and perhaps more simply than that, make a living out of learning.
But the second biggest thank you goes to the many writers AnandTech has had over the years, Anand’s early collaborators and later hires alike. It’s only by having writers specialize in their fields and become masters that we’ve been able to dive as deep as we have, and while many of those writers have moved on to other jobs, they’ve left their mark on AnandTech. Over the years AnandTech’s writers have endured a number of sleepless nights, working holidays, canceled social engagements, and uncomfortable flights, all in the name of bringing more information to you. Their efforts are what have allowed AnandTech to thrive over the years.
A thank you also goes to the hardware vendors out there, many of whom have been putting up with us for the last 20 years. We’ve killed hardware, we’ve asked uncomfortable questions, and we’ve pushed vendors to reveal more than they’d necessarily like to share. Practically speaking, hardware vendors could lock us out, but I think that we’ve found a much better path in working together. The amount of access we get to engineers to ask questions and bring these stories to life has only happened because hardware vendors see the value in the AnandTech mission. And that has been to the benefit of everyone.
AnandTech’s publisher, Purch, also deserves a thank you. And I know what you’re thinking here, but Purch has genuinely earned this. Purch has taken a largely hands-off approach and allowed AnandTech to continue to do what we need to do, even if it doesn’t necessarily make business sense at the time. As a result we’ve continued to be able to innovate and stumble at our own pace, operating with the agility that’s required to remain at the cutting edge of technology.
Finally, none of this would be possible without you, the readers. You’re our support, the reason we suffer through sleepless nights to get a 20 page article up for a 9am Eastern embargo. You’re our critics, letting us know when we’ve let you down or could do better. You’re our customers, helping to pay the bills over the years accepting our advertising and buying products through our affiliates. But most of all you’re our peers, the people we strive to share our knowledge with so that all of us can know a little more and be a little smarter.
Thank you to everyone for the last 20 years, and here’s to what I hope to be the next 20 years.
A class need not be particularly complex. In fact, you can create just the container and one class element in Python and call it a class. Of course, the resulting class won’t do much, but you can instantiate it (tell Python to build an object using your class as a blueprint) and work with it as you would any other class.
The following steps help you understand the basics behind a class by creating the simplest class possible.
1Open a Python Shell window.
You see the familiar Python prompt.
2Type the following code (pressing Enter after each line and pressing Enter twice after the last line):
class MyClass: MyVar = 0
The first line defines the class container, which consists of the keyword class and the class name, which is MyClass. Every class you create must begin precisely this way. You must always include class followed by the class name.
The second line is the class suite. All the elements that comprise the class are called the class suite. In this case, you see a class variable named MyVar, which is set to a value of 0. Every instance of the class will __have the same variable and start at the same value.
3Type MyInstance = MyClass() and press Enter.
You __have just created an instance of MyClass named MyInstance. Of course, you’ll want to verify that you really have created such an instance. Step 4 accomplishes that task.
4Type MyInstance.MyVar and press Enter.
The output of 0 demonstrates that MyInstance does indeed have a class variable named MyVar.
5Type MyInstance.__class__ and press Enter.
Python displays the class used to create this instance. The output tells you that this class is part of the __main__ module, which means that you typed it directly into the shell.
Sometimes you need to place one exception-handling routine within another in a process called nesting. When you nest exception-handling routines, Python tries to find an exception handler in the nested level first and then moves to the outer layers. You can nest exception-handling routines as deeply as needed to make your code safe.
One of the more common reasons to use a dual layer of exception-handling code is when you want to obtain input from a user and need to place the input code in a loop to ensure that you actually get the required information. The following steps demonstrate how this sort of code might work.
1Open a Python File window.
You see an editor in which you can type the example code.
2Type the following code into the window — pressing Enter after each line:
TryAgain = Truewhile TryAgain: try: Value = int(input("Type a whole number. ")) except ValueError: print("You must type a whole number!") try: DoOver = input("Try again (y/n)? ") except: print("OK, see you next time!") TryAgain = False else: if (str.upper(DoOver) == "N"): TryAgain = False except KeyboardInterrupt: print("You pressed Ctrl+C!") print("See you next time!") TryAgain = False else: print(Value) TryAgain = False
The code begins by creating an input loop. Using loops for this type of purpose is actually quite common in applications because you don’t want the application to end every time an input error is made. This is a simplified loop, and normally you create a separate function to hold the code.
When the loop starts, the application asks the user to type a whole number. It can be any integer value. If the user types any non-integer value or presses Ctrl+C, Cmd+C, or another interrupt key combination, the exception-handling code takes over. Otherwise, the application prints the value that the user supplied and sets TryAgain to False, which causes the loop to end.
A ValueError exception can occur when the user makes a mistake. Because you don’t know why the user input the wrong value, you __have to ask if the user wants to try again. Of course, getting more input from the user could generate another exception. The inner try except code block handles this secondary input.
Notice the use of the str.upper() function when getting character input from the user. This function makes it possible to receive y or Y as input and accept them both. Whenever you ask the user for character input, it’s a good idea to convert lowercase characters to uppercase so that you can perform a single comparison (reducing the potential for error).
The KeyboardInterrupt exception displays two messages and then exits automatically by setting TryAgain to False. The KeyboardInterrupt occurs only when the user presses a specific key combination designed to end the application. The user is unlikely to want to continue using the application at this point.
3Choose Run→Run Module.
You see a Python Shell window open. The application asks the user to input a whole number.
4Type Hello and press Enter.
The application displays an error message and asks whether you want to try again.
5Type Y and press Enter.
The application asks you to input a whole number again.
6Type 5.5 and press Enter.
The application again displays the error message and asks whether you want to try again.
7Press Ctrl+C, Cmd+C, or another key combination to interrupt the application.
The application ends. Notice that the message is the one from the inner exception. The application never gets to the outer exception because the inner exception handler provides generic exception handling.
8Choose Run→Run Module.
You see a Python Shell window open. The application asks the user to input a whole number.
9Press Ctrl+C, Cmd+C, or another key combination to interrupt the application.
The application ends. Notice that the message is the one from the outer exception. In the preious steps, the user ends the application by pressing an interrupt key. However, the application uses two different exception handlers to address the problem.
As part of your programming with Java, you may be looking to create randomness. Achieving real randomness is surprisingly difficult. Mathematician Persi Diaconis says that if you flip a coin several times, always starting with the head side up, you’re likely to toss heads more often than tails. If you toss several more times, always starting with the tail side up, you’ll likely toss tails more often than heads. In other words, coin tossing isn’t really fair.*
* Diaconis, Persi. “The Search for Randomness.” American Association for the Advancement of Science annual meeting. Seattle. 14 Feb. 2004.
Computers aren’t much better than coins and human thumbs. A computer mimics the generation of random sequences, but in the end the computer just does what it’s told and does all of this in a purely deterministic fashion. So, when the computer executes
import java.util.Random;
int randomNumber = new Random().nextInt(10) + 1;
the computer appears to give a randomly generated number — a whole number between 1 and 10. But it’s all a fake. The computer only follows instructions. It’s not really random, but without bending a computer over backward, it’s the best that anyone can do.
Once again, you will simply __have to take this code on blind faith. Don’t worry about what new Random().nextInt means until you __have more experience with Java. Just copy this code into your own programs and have fun with it. And if the numbers from 1 to 10 aren’t in your flight plans, don’t fret. To roll an imaginary die, write the statement
int rollEmBaby = new Random().nextInt(6) + 1;
With the execution of this statement, the variable rollEmBaby gets a value from 1 to 6.
Here, you find out how to make your ordinary JavaFX shapes bloom and glow, all with the help of two simple classes, unsurprisingly named Bloom and Glow. This table shows the members of these two classes.
The Bloom and Glow Classes
Constructor
Explanation
Bloom()
Creates a new Bloom effect with default parameters.
Glow()
Creates a new Glow effect with default parameters.
Bloom Method
Explanation
void setThreshhold(double value)
Sets the luminosity threshold. The bloom effect will be applied to portions of the shape that are brighter than the threshold. The value can be 0.0 to 1.0. The default value is 0.3.
Glow Method
Explanation
void setLevel(double value)
Sets the intensity of the effect’s glow level. The value can be 0.0 to 1.0. The default value is 0.3.
The figure shows the effect of the Bloom and Glow effects. All three of the text shapes shown in the figure are combined with a rectangle in a group. The following code was used to create the first group (shown at the top of the figure):
Rectangle r1 = new Rectangle(50, 50, 400, 100);r1.setFill(Color.BLACK);r1.setStroke(Color.BLACK);Text t1 = new Text("Plain Text");t1.setX(130);t1.setY(125);t1.setFont(new Font("Times New Roman", 60));t1.setFill(Color.LIGHTGRAY);Group g1 = new Group();g1.getChildren().addAll(r1, t1);
Similar code was used to create the second group (shown in the middle of the figure), but a Bloom effect was added:
Rectangle r2 = new Rectangle(50, 50, 400, 100);r2.setFill(Color.BLACK);r2.setStroke(Color.BLACK);Text t2 = new Text("Blooming Text");t2.setX(70);t2.setY(125);t2.setFont(new Font("Times New Roman", 60));t2.setFill(Color.LIGHTGRAY);Group g2 = new Group();g2.getChildren().addAll(r2, t2);Bloom e1 = new Bloom();e1.setThreshold(0.3);g2.setEffect(e1);
For the third group, a Glow effect was added instead:
Rectangle r3 = new Rectangle(50, 50, 400, 100);r3.setFill(Color.BLACK);r3.setStroke(Color.BLACK);Text t3 = new Text("Glowing Text");t3.setX(80);t3.setY(125);t3.setFont(new Font("Times New Roman", 60));t3.setFill(Color.LIGHTGRAY);Group g3 = new Group();g3.getChildren().addAll(r3, t3);Glow e2 = new Glow();e2.setLevel(1.0);g3.setEffect(e2);
The difference between the bloom and glow effect is subtle. To be honest, it’s barely noticeable. If you look very closely, you’ll see that the glowing text is just a tad brighter than the blooming text. (The distinction between glow and bloom is more noticeable when colors other than black and white are used.)
The ScrollBar control in JavaFX is not usually used by itself; instead, it is used by other controls such as ScrollPane or ListView to display the scroll bar that lets the user scroll the contents of a panel or other region.
However, there are occasions when you might want to use a scroll bar for some purpose other than scrolling a region. In fact, you can actually use a scroll bar in much the same way as you use a slider, as the two are very similar.
One difference is that unlike a slider, a scroll bar does not allow tick marks. But on the other hand, a scroll bar has increment and decrement buttons on either end of the bar, which allows the user to set the scroll bar’s value up or down in fixed increments.
This figure shows a version of an audio mixer, only implemented with scroll bars. As in the slider version, each scroll bar is paired with a Text object that displays the scroll bar’s value whenever the user manipulates the control.
You can use the following helper method to create each combined scroll bar and Text object:
Using JavaFX scroll bars to create a mixer board.
private Node makeScrollBar(int value){ Text text = new Text(); text.setFont(new Font("sans-serif", 10)); ScrollBar sb = new ScrollBar(); sb.setOrientation(Orientation.VERTICAL); sb.setPrefHeight(150); sb.valueProperty().addListener( (observable, oldvalue, newvalue) -> { int i = newvalue.intValue(); text.setText(Integer.toString(100-i)); } ); sb.setValue(value); VBox box = new VBox(10, sb, text); box.setPadding(new Insets(10)); box.setAlignment(Pos.CENTER); box.setMinWidth(30); box.setPrefWidth(30); box.setMaxWidth(30); return box;}
Here’s a guessing game for you to test out your understanding of Java. The computer generates a random number from 1 to 10. The computer asks you to guess the number. If you guess incorrectly, the game continues. As soon as you guess correctly, the game is over. This listing shows the program to play the game, and the figure shows a round of play.
import static java.lang.System.out;import java.util.Scanner;import java.util.Random;public class GuessAgain { public static void main(String args[]) { Scanner keyboard = new Scanner(System.in); int numGuesses = 0; int randomNumber = new Random().nextInt(10) + 1; out.println(" ************ "); out.println("Welcome to the Guessing Game"); out.println(" ************ "); out.println(); out.print("Enter an int from 1 to 10: "); int inputNumber = keyboard.nextInt(); numGuesses++; while (inputNumber != randomNumber) { out.println(); out.println("Try again..."); out.print("Enter an int from 1 to 10: "); inputNumber = keyboard.nextInt(); numGuesses++; } out.print("You win after "); out.println(numGuesses + " guesses."); keyboard.close(); }}
In the figure, the user makes four guesses. Each time around, the computer checks to see whether the guess is correct. An incorrect guess generates a request to try again. For a correct guess, the user gets a rousing You win, along with a tally of the number of guesses he or she made.
The computer repeats several statements, checking each time through to see whether the user’s guess is the same as a certain randomly generated number. Each time the user makes a guess, the computer adds 1 to its tally of guesses. When the user makes the correct guess, the computer displays that tally. The figure illustrates the flow of action.
When you look over the listing, you see the code that does all this work. At the core of the code is a thing called a whilestatement (also known as a while loop). Rephrased in English, the while statement says:
while the inputNumber is not equal to the randomNumberkeep doing all the stuff in curly braces: {}
The stuff in curly braces (the stuff that repeats) is the code that prints Try again and Enter an int . . ., gets a value from the keyboard, and adds 1 to the count of the user’s guesses.
When you’re dealing with counters, like numGuesses in the listing, you may easily become confused and be off by 1 in either direction. You can avoid this headache by making sure that the ++ statements stay close to the statements whose events you’re counting.
For example, in the listing, the variable numGuesses starts with a value of 0. That’s because, when the program starts running, the user hasn’t made any guesses. Later in the program, right after each call to keyboard.nextInt, is a numGuesses++ statement. That’s how you do it — you increment the counter as soon as the user enters another guess.
The statements in curly braces are repeated as long as inputNumber != randomNumber is true. Each repetition of the statements in the loop is called an iteration of the loop. In the figure, the loop undergoes three iterations. (If you don’t believe that the figure has exactly three iterations, count the number of Try again printings in the program’s output. A Try again appears for each incorrect guess.)
When, at long last, the user enters the correct guess, the computer goes back to the top of the while statement, checks the condition in parentheses, and finds itself in double double-negative land. The not equal (!=) relationship between inputNumber and randomNumber no longer holds.
In other words, the while statement’s condition, inputNumber != randomNumber, is false. Because the while statement’s condition is false, the computer jumps past the while loop and goes on to the statements just below the while loop. In these two statements, the computer prints You win after 4 guesses.
With code of the kind shown in the listing, the computer never jumps out in mid-loop. When the computer finds that inputNumber isn’t equal to randomNumber, the computer marches on and executes all five statements inside the loop’s curly braces. The computer performs the test again (to see whether inputNumber is still not equal to randomNumber) only after it fully executes all five statements in the loop.
JavaFX has built-in support for realistic 3D modeling. In fact, the JavaFX scene graph is three-dimensional in nature. Most JavaFX programs work in just two dimensions, specifying just x- and y-coordinates. But all you __have to do to step into the third dimension is specify z-coordinates to place the nodes of your scene graph in three-dimensional space.
JavaFX includes a rich set of classes that are dedicated to creating and visualizing 3D objects in 3D worlds. You can create three-dimensional shapes, such as cubes and cylinders. You can move the virtual camera around within the 3D space to look at your 3D objects from different angles and different perspectives.
And you can even add lighting sources to carefully control the final appearance of your virtual worlds. In short, JavaFX is capable of producing astonishing 3D scenes.
Add a 3D box to your Java world
In this step, add an object to the 3D world: In this case, a box, represented by the Box class. Here’s the code:
Box box = new Box(100, 100, 100);box.setMaterial(blueStuff);box.setTranslateX(150);box.setTranslateY(-100);box.setTranslateZ(-100);root.getChildren().add(box);
The Box constructor accepts three arguments representing the width, height, and depth of the box. In this example, all three are set to 100. Thus, the box will be drawn as a cube with each side measuring 100 units.
The box is given the same material as the cylinder; then, it is translated on all three axes so that you can __have a perspective view of the box. The figure shows how the box appears when rendered. As you can see, the left and bottom faces of the box are visible because you translated the position of the box up and to the right so that the camera can gain some perspective.
Rotate the 3D box
In this step, rotate the box to create an even more interesting perspective view. There are two ways to rotate a 3D object. The simplest is to call the object’s setRotate method and supply a rotation angle:
box.setRotate(25);
By default, this will rotate the object on its z-axis. If this is difficult to visualize, imagine skewering the object with a long stick that is parallel to the z-axis. Then, spin the object on the skewer.
If you want to rotate the object along a different axis, first call the setRotationAxis. For example, to spin the object on its x-axis, use this sequence:
Imagine running the skewer through the box with the skewer parallel to the x-axis and then spinning the box 25 degrees.
The only problem with using the setRotate method to rotate a 3D object is that it works only on one axis at a time. For example, suppose you want to rotate the box 25 degrees on both the z- and the x-axis. The following code will not accomplish this:
When the setRotate method is called the second time to rotate the box on the z-axis, the x-axis rotation is reset.
To rotate on more than one axis, you must use the Rotate class instead. You create a separate Rotate instance for each axis you want to rotate the object on and then add all the Rotate instances to the object’s Transforms collection via the getTransforms().addAll method, like this:
Rotate rxBox = new Rotate(0, 0, 0, 0, Rotate.X_AXIS);Rotate ryBox = new Rotate(0, 0, 0, 0, Rotate.Y_AXIS);Rotate rzBox = new Rotate(0, 0, 0, 0, Rotate.Z_AXIS);rxBox.setAngle(30);ryBox.setAngle(50);rzBox.setAngle(30);box.getTransforms().addAll(rxBox, ryBox, rzBox);
The Rotate constructor accepts four parameters. The first three are the x-, y-, and z-coordinates of the point within the object through which the rotation axis will pass. Typically, you specify zeros for these parameters to rotate the object around its center point. The fourth parameter specifies the rotation axis.
This figure shows how the box appears after it’s been rotated.
JUnit is a standardized framework for testing Java units (that is, Java classes). JUnit can be automated to take the some of the work out of testing.
Imagine you’ve created an enum type with three values: GREEN, YELLOW, and RED. Listing 1 contains the code:
Listing 1
public enum SignalColor { GREEN, YELLOW, RED}
A traffic light has a state (which is a fancy name for the traffic light’s color).
public class TrafficLight { SignalColor state = SignalColor.RED;
If you know the traffic light’s current state, you can decide what the traffic light’s next state will be.
public void nextState() { switch (state) { case RED: state = SignalColor.GREEN; break; case YELLOW: state = SignalColor.RED; break; case GREEN: state = SignalColor.YELLOW; break; default: state = SignalColor.RED; break; }}
You can also change the traffic light’s state a certain number of times:
public void change(int numberOfTimes) { for (int i = 0; i < numberOfTimes; i++) { nextState(); }}
Putting it all together, you __have the TrafficLight class in Listing 2.
Listing 2
public class TrafficLight { SignalColor state = SignalColor.RED; public void nextState() { switch (state) { case RED: state = SignalColor.GREEN; break; case YELLOW: state = SignalColor.RED; break; case GREEN: state = SignalColor.YELLOW; break; default: state = SignalColor.RED; break; } } public void change(int numberOfTimes) { for (int i = 0; i < numberOfTimes; i++) { nextState(); } }}
In the olden days you might __have continued writing code, creating more classes, calling the nextState and change methods in Listing 2. Then, after several months of coding, you’d pause to test your work.
And what a surprise! Your tests would fail miserably! You should never delay testing for more than a day or two. Test early and test often!
One philosophy about testing says you should test each chunk of code as soon as you’ve written it. Of course, the phrase “chunk of code” doesn’t sound very scientific. It wouldn’t do to have developers walking around talking about the “chunk-of-code testing” that they did this afternoon. It’s better to call each chunk of code a unit, and get developers to talk about unit testing.
The most common unit for testing is a class. So a typical Java developer tests each class as soon as the class’s code is written.
So how do you go about testing a class? A novice might test the TrafficLight class by writing an additional class — a class containing a main method. The main method creates an instance of TrafficLight, calls the nextState and change methods, and displays the results. The novice examines the results and compares them with some expected values.
After writing main methods for dozens, hundreds, or even thousands classes, the novice (now a full-fledged developer) becomes tired of the testing routine and looks for ways to automate the testing procedure. Tired or not, one developer might try to decipher another developer’s hand-coded tests. Without having any standards or guidelines for constructing tests, reading and understanding another developer’s tests can be difficult and tedious.
So JUnit comes to the rescue!.
To find out how Eclipse automates the use of JUnit, do the following:
Create an Eclipse project containing Listings 1 and 2.
In Windows, right-click the Package Explorer’s TrafficLight.java branch. On a Mac, control-click the Package Explorer’s TrafficLight.java branch.
A context menu appears.
In the context menu, select New→JUnit Test Case.
As a result, the New JUnit Test Case dialog box appears.
Click Next at the bottom of the New JUnit Test Case dialog box.
As a result, you see the second page of the New JUnit Test Case dialog box. The second page lists methods belonging (either directly or indirectly) to the TrafficLight class.
Place a checkmark in the checkbox labeled Traffic Light.
As a result, Eclipse places checkmarks in the nextState() and change(int) checkboxes.
Click Finish at the bottom of the New JUnit Test Case dialog box.
JUnit isn’t formally part of Java. Instead comes with its own set of classes and methods to help you create tests for your code. After you click Finish, Eclipse asks you if you want to include the JUnit classes and methods as part of your project.
Select Perform the Following Action and Add JUnit 4 Library to the Build Path. Then click OK.
Eclipse closes the dialog boxes and your project has a brand new TrafficLightTest.java file. The file’s code is shown in Listing 3.
The code in Listing 3 contains two tests, and both tests contain calls to an unpleasant sounding fail method. Eclipse wants you to add code to make these tests pass.
Remove the calls to the fail method. In place of the fail method calls, type the code shown in bold in Listing 4.
In the Package Explorer, right-click (in Windows) or control-click (on a Mac) the TrafficLightTest.java branch. In the resulting context menu, select Run As→JUnit Test.
Eclipse might have more than one kind of JUnit testing framework up its sleeve. If so, you might see a dialog box like the one below. If you do, select the Eclipse JUnit Launcher, and then click OK.
As a result, Eclipse runs your TrafficLightTest.java program. Eclipse displays the result of the run in front of its own Package Explorer. The result shows no errors and no failures. Whew!
Listing 3
import static org.junit.Assert.*;import org.junit.Test;public class TrafficLightTest { @Test public void testNextState() { fail("Not yet implemented"); } @Test public void testChange() { fail("Not yet implemented"); }}
Listing 4
import static org.junit.Assert.*;import org.junit.Test;public class TrafficLightTest { @Test public void testNextState() { TrafficLight light = new TrafficLight(); assertEquals(SignalColor.RED, light.state); light.nextState(); assertEquals(SignalColor.GREEN, light.state); light.nextState(); assertEquals(SignalColor.YELLOW, light.state); light.nextState(); assertEquals(SignalColor.RED, light.state); } @Test public void testChange() { TrafficLight light = new TrafficLight(); light.change(5); assertEquals(SignalColor.YELLOW, light.state); }}
When you select Run As→JUnit Test, Eclipse doesn’t look for a main method. Instead, Eclipse looks for methods starting with @Test and other such things. Eclipse executes each of the @Test methods.
Things like @Test are Java annotations.
Listing 4 contains two @Test methods: testNextState and testChange. The testNextState method puts the TrafficLight nextState method to the test. Similarly, the testChange method flexes the TrafficLightchange method’s muscles.
Consider the code in the testNextState method. The testNextState method repeatedly calls the TrafficLight class’s nextState method and JUnit’s assertEquals method. The assertEquals method takes two parameters: an expected value and an actual value.
In the topmost assertEquals call, the expected value is SignalColor.RED. You expect the traffic light to be RED because, in Listing 2, you initialize the light’s state with the value SignalColor.RED.
In the topmost assertEquals call, the actual value is light.state (the color that’s actually stored in the traffic light’s state variable).
If the actual value equals the expected value, the call to assertEquals passes and JUnit continues executing the statements in the testNextState method.
But if the actual value is different from the expected value, the assertEquals fails and the result of the run displays the failure. For example, consider what happens when you change the expected value in the first assertEquals call in Listing 4:
Immediately after its construction, a traffic light’s color is RED, not YELLOW. So the testNextState method contains a false assertion and the result of doing Run As→JUnit looks like a child’s bad report card.
Having testNextState before testChange in Listing 4 does not guarantee that JUnit will execute testNextState before executing testChange. If you have three @Test methods, JUnit might execute them from topmost to bottommost, from bottommost to topmost, from the middle method to the topmost to the bottommost, or in any order at all. JUnit might even pause in the middle of one test to execute parts of another test. That’s why you should never make assumptions about the outcome of one test when you write another test.
You might want certain statements to be executed before any of the tests begin. If you do, put those statements in a method named setUp, and preface that method with a @Before annotation. (See the setUp() checkbox in the figure at Step 3 in Listing 2, above.)
Here’s news: Not all assertEquals methods are created equal! Imagine adding a Driver class to your project’s code. “Driver class” doesn’t mean a printer driver or a pile driver. It means a person driving a car — a car that’s approaching your traffic light. For the details, see Listing 5.
Listing 5
public class Driver { public double velocity(TrafficLight light) { switch (light.state) { case RED: return 0.0; case YELLOW: return 10.0; case GREEN: return 30.0; default: return 0.0; } }}
When the light is red, the driver’s velocity is 0.0. When the light is yellow, the car is slowing to a safe 10.0. When the light is green, the car cruises at a velocity of 30.0.
(In this example, the units of velocity don’t matter. They could be miles per hour, kilometers per hour, or whatever. The only way it matters is if the car is in Boston or New York City. In that case, the velocity for YELLOW should be much higher than the velocity for GREEN, and the velocity for RED shouldn’t be 0.0.)
To create JUnit tests for the Driver class, follow Steps 1 to 9 listed previously in this article, but be sure to make the following changes:
In Step 2, right-click or control-click the Driver.java branch instead of the TrafficLight.java branch.
In Step 5, put a check mark in the Driver branch.
In Step 8, remove the fail method calls to create the DriverTest class shown in Listing 6. (The code that you type is shown in bold.)
Listing 6
import static org.junit.Assert.*;import org.junit.Test;public class DriverTest { @Test public void testVelocity() { TrafficLight light = new TrafficLight(); light.change(7); Driver driver = new Driver(); assertEquals(30.0, driver.velocity(light), 0.1); }}
If all goes well, the JUnit test passes with flying colors. (To be more precise, the JUnit passes with the color green!) So the running of DriverTest isn’t new or exciting. What’s exciting is the call to assertEquals in Listing 6.
When you compare two double values in a Java program, you have no right to expect on-the-nose equality. That is, one of the double values might be 30.000000000 while the other double value is closer to 30.000000001. A computer has only 64 bits to store each double value, and inaccuracies creep in here and there. So in JUnit, the assertEquals method for comparing double values has a third parameter. The third parameter represents wiggle room.
In Listing 6, the statement
assertEquals(30.0, driver.velocity(light), 0.1);
says the following: “Assert that the actual value of driver.velocity(light) is within 0.1 of the expected value 30.0. If so, the assertion passes. If not, the assertion fails.”
When you call assertEquals for double values, selecting a good margin of error can be tricky. These figures illustrate the kinds of things that can go wrong.
Here, your margin of error is too small.
There, your margin of error is too large.
Fortunately, in this DriverTest example, the margin 0.1 is a very safe bet. Here’s why:
When the assertEquals test fails, it fails by much more than 0.1.
Failure means having a driver.velocity(light) value such as 0.0 or 10.0.
In this example, when the assertEquals test passes, it probably represents complete, on-the-nose equality.
The value of driver.velocity(light) comes directly from the return 30.0 code in Listing 5. No arithmetic is involved. So the value of driver.velocity(light) and the expected 30.0 value should be exactly the same (or almost exactly the same).