Statistics
7
Views
0
Downloads
0
Donations
Learning Java. A Test-Driven Approach 2024 (Crotts J.) (Z-Library)
技术Author:Crotts J.
No description
Support Statistics
¥.00 ·
0times
Text Preview (First 20 pages)
Registered users can read the full content for free
Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.
Page
1
Joshua Crotts Learning Java A Test-Driven Approach Springer
Page
2
Joshua Crotts Learning Java A Test-Driven Approach 4^ Springer
Page
3
Joshua Crotts Department of Computer Science Indiana University Bloomington Bloomington, IN, USA ISBN 978-3-031-66637-7 ISBN 978-3-031-66638-4 (eBook) https://doi.org/10.1007/978-3-031-66638-4 © The Editor(s) (if applicable) and The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 This work is subject to copyright. All rights are solely and exclusively licensed by the Publisher, whether the whole or part of the material is concerned, specifically the rights of translation, reprinting, reuse of illustrations, recitation, broadcasting, reproduction on microfilms or in any other physical way, and transmission or information storage and retrieval, electronic adaptation, computer software, or by similar or dissimilar methodology now known or hereafter developed. The use of general descriptive names, registered names, trademarks, service marks, etc. in this publication does not imply, even in the absence of a specific statement, that such names are exempt from the relevant protective laws and regulations and therefore free for general use. The publisher, the authors and the editors are safe to assume that the advice and information in this book are believed to be true and accurate at the date of publication. Neither the publisher nor the authors or the editors give a warranty, expressed or implied, with respect to the material contained herein or for any errors or omissions that may have been made. The publisher remains neutral with regard to jurisdictional claims in published maps and institutional affiliations. This Springer imprint is published by the registered company Springer Nature Switzerland AG The registered company address is: Gewerbestrasse 11, 6330 Cham, Switzerland If disposing of this product, please recycle the paper.
Page
4
To my friends and family.
Page
5
Preface A course in Java programming is multifaceted. That is, it covers several core concepts, in cluding basic datatypes, strings, simple-to-intermediate data structures, object-oriented pro gramming, and beyond. What is commonly omitted from these courses is the notion of proper unit testing methodologies. Real-world companies often employ testing frameworks to bullet proof their codebases, and students who leave university without expose to such frameworks are behind their colleagues. Our textbook aims to rectify this delinquency by emphasizing testing from day one; we write tests before designing the implementation of our methods, a fundamental feature of test-driven development. In our book, we design methods rather than write them, an idea stemming from Felleisen’s How to Design Programs, wherein we determine the definition of our data, the method signa ture (i.e., parameter and return types), appropriate examples and test cases, and only then follow with the method implementation. Diving straight into a method implementation of ten results in endless hours of debugging that may have been saved by only a few minutes of preparation. Extending this idea into subsequent computer science courses is no doubt excellent. At Indiana University, students take either a course in Python or the Beginner/Interme- diate Student Languages (based on Scheme/Racket), the latter of which involves constant testing and remediation. Previous offerings of the successor course taught in Java lead stu dents astray into a “plug and chug” mindset of re-running a program until it works. Our goal is to stop this once and for all (perhaps not truly “once and for all,” but rather we aim to make it a less frequent habit) by teaching Java correctly and efficiently. Object-oriented programming (and, more noteworthy, a second-semester computer science course) is tough for many students to grasp, but even more so if they lack the necessary prerequisite knowledge. Syntax is nothing short of a different way of spelling the same con cept; we reinforce topics that students should already have exposure to: methods, variables, conditionals, recursion, loops, and simple data structures. We follow this with the Java Col lections framework, generics, object-oriented programming, exceptions, I/O, searching and sorting algorithms, algorithm analysis, and modern Java features such as pattern matching and concurrency. The ordering of topics presented in a Java course is hotly-debated and has been ever since its creation and use in higher education. Such questions include the location of object- oriented programming principles: do we start off with ob jects or hold off until later? Depending on the style of a text, either option can work. Even though we, personally, are more of a fan of the “early objects” approach, and is how we learned Java many moons ago, we choose to place objects later in the curriculum. We do this to place greater emphasis on testing, method design, recursion, and data structures through the Collections framework. Accordingly, after our midterm (roughly halfway through the semester), students should have a strong foundation of basic Java syntax sans objects and class design. The second half of the class is dedicated to just that: object-oriented programming and clearing up confusions vii
Page
6
viii Preface that coincide and introduce themselves. Learning Java also includes two supplemental parts: one on searching, sorting, and algorithms, and another on modern Java features, the latter of which is not typically covered in its entirety within the span of a second-semester course. We believe that this textbook can be used as any standard second-semester computer science course. At the high-school level, this book can be used as a substitute for the College Board’s former AP Computer Science AB course. Certainly, this text may be well-suited for an AP Computer Science A course at the high-school level, but its material goes well beyond the scope of the AP exam and curriculum. Instructors are free to omit certain topics that may have been covered in a prerequisite (traditionally-styled) Java course. In those circumstances, it may be beneficial to dive further into the chapters on algorithm analysis and modern Java pragmatics (i.e., parts III and IV). For students without a Java background (or instructors of said students), which we assume, we take the time to quickly yet effectively build confidence in Java’s quirky syntax. Additionally, we understand that our approach to teaching loops (through recursion and a translation pipeline) may appear odd to some long time programmers. So, an instructor may reorder these sections however they choose, but we highly recommend retaining our chosen ordering for pedagogical purposes, particularly for those readers that are not taking an accompanying (college) class that uses this text. At the end of Chapters 1-5, we present a slew of exercises to the readers, and we encourage them to do most, if not all, of the exercises. Some tasks in the exercises serve as simple reinforcement of topics, whereas others are long-haul marathons that take many hours to complete. Several exercises have also been used as (written) exam questions. We do not provide answers to the exercises because there are many “avenues to success.” Readers should collaborate with others to solve the problems and discuss difficulties and points of confusion in aims of clarification. The diction of our book is chosen very carefully, with hourly of scrutiny dedicated to the paragraphs, sentence structure, and their accompanying presentation. When we demonstrate an example, stop, and closely follow along. Do not rush through it. The words on the text remain in place, no matter the pace of the reader. A page, example, exercise, or chapter may require multiple passes to fully digest the content. Make notes in the margins, type out any examples and exercises, ask questions, and answer the questions posed by others. Perhaps they may answer one of yours. In addition to the meticulously-selected diction, we have also made a conscious effort to place all code listings onto a single page, so as to prevent the reader from flipping back and forth between pages. The effort, while successful in most cases, is not perfect, and there are instances where a code listing spans multiple pages. Even so, we strongly encourage readers who cannot type out the code listings to read each line of code, ensuring that they understand its purpose in the context of the surrounding program. Once again, by writing this book, we wish to ensure that students are better prepared for the more complex courses in a common computer science curriculum, e.g., data struc tures, operating systems, algorithms, programming languages, and whatever else lies ahead. A strong foundation keeps students motivated and pushes them to continue even when times are arduous, which we understand there to be plenty thereof. After all, not many moons ago were we in the shoes of our target audience! Have a blast! Joshua Crotts
Page
7
Acknowledgements I piloted Learning Java on the students in the Fall 2023 and Spring 2024 semesters of CSCI- C 212 (Introduction to Software Systems) at Indiana University. The inspiration came from those in the former, which drove me to continue writing and designing exercises. I sincerely appreciate all of the comments, suggestions, and corrections made. The following students and course staff members found mistakes: Ashley Won, Daniel Yang, Jack Liang, Shyam Makwana, and Muazzam Siddiqui. Amr Sabry of Indiana University provided the suggestion to add streams to the Java curriculum, and as a consequence I now fully embrace and use them constantly. I appreciate the help provided by my representative at Springer, Ralf Gerstner, and the fact that Springer took a bold chance to publish the work of a computer science PhD student. Chandana Ariyawansa of UNC Greensboro and Nathaniel (Nat) Kell assigned brutal prob lem sets that shaped me into the skilled programmer and educator that I am today. Steve Tate of UNC Greensboro put me through the ringer, figuratively, in his graduate algorithms, compilers, system programming, computer security, and software security classes. His mentorship through it all, and the fact that he was the chair of my master’s thesis committee, is forever appreciated. I attribute all of my love for Java (and computer science in general) to my former AP Computer Science A teacher: Tony Smith. Thanks for everything. ix
Page
8
Contents Preface ................................................................................................................................................ vii Part I Java Programming and Data Structures 1 Testing and Java Basics ...................................................................................................... 3 1.1 A First Glimpse at Java ................................................................................................. 3 1.2 Strings ................................................................................................................................. 10 1.3 Standard I/O .................................................................................................................... 15 1.4 Randomness ...................................................................................................................... 18 1.5 Exercises ............................................................................................................................. 21 2 Conditionals, Recursion, and Loops ............................................................................... 25 2.1 Conditionals ....................................................................................................................... 25 2.2 Recursion ........................................................................................................................... 30 2.3 Loops ................................................................................................................................... 44 2.4 Exercises ............................................................................................................................. 70 3 Arrays, Collections, and Generics ................................................................................... 87 3.1 Arrays ................................................................................................................................. 87 3.2 Collections ......................................................................................................................... 110 3.3 Iterators ............................................................................................................................. 146 3.4 Streams ............................................................................................................................... 150 3.5 Type Parameters .............................................................................................................. 160 3.6 Tree-Based Data Structures ........................................................................................... 165 3.7 Exercises ............................................................................................................................. 172 Part II Objects, Classes, Exceptions, and I/O 4 Object-Oriented Programming ........................................................................................ 191 4.1 Classes ................................................................................................................................. 191 4.2 Object Mutation and Aliasing....................................................................................... 222 4.3 Interfaces ............................................................................................................................. 243 4.4 Inheritance ......................................................................................................................... 261 4.5 Abstract Classes .............................................................................................................. 280 4.6 Exercises ............................................................................................................................. 297 xi
Page
9
xii Contents 5 Exceptions and I/O ............................................................................................................... 325 5.1 Exceptions ........................................................................................................................ 325 5.2 File I/O .............................................................................................................................. 330 5.3 Modern I/O Classes & Methods .................................................................................. 342 5.4 Exercises ............................................................................................................................ 349 Part III Searching, Sorting, and Algorithms 6 Searching and Sorting ........................................................................................................ 361 6.1 Searching ............................................................................................................................ 361 6.2 Sorting ................................................................................................................................. 368 7 Algorithm Analysis ............................................................................................................... 387 7.1 Analyzing Algorithms ...................................................................................................... 387 Part IV Modern Java 8 Modern Java and Advanced Topics ............................................................................. 401 8.1 Verbosity ............................................................................................................................ 401 8.2 Pattern Matching ........................................................................................................... 403 8.3 Reflection .......................................................................................................................... 413 8.4 Concurrent Programming ............................................................................................... 420 8.5 Design Patterns ................................................................................................................ 442 8.6 API Connectivity.............................................................................................................. 455 A JUnit Testing ............................................................................................................................ 461 A.1 JUnit .................................................................................................................................. 461 B Binary Search Tree Library .............................................................................................. 465 References 469 Index 471
Page
10
Part I Java Programming and Data Structures
Page
11
Check for updates 1 Testing and Java Basics Abstract This chapter introduces readers to the basics of Java programming and testing. We will cover the fundamentals of Java syntax, including variables and data types. At the same time, we discuss the importance of test-driven development and how to write test cases in Java for methods. Our emphasis on testing makes work of the fact that testing is a critical part of writing correct and robust software. Test-driven development, in particular, is utilized as a “design recipe,” of sorts, to guide programmers to solve a task. 1.1 A First Glimpse at Java It makes little sense to avoid the topic at hand, so let’s jump right in and write a program! We have seen functions before, as well as some mathematics operations, perhaps in a different (language) context. Example 1.1. Our program will consist of a function that converts a given temperature in Fahrenheit to Celsius. class TempConverter { /** 1 The reasoning is simple: a method belongs to a class. Other programming languages, e.g., C++ or Python, do not restrict the programmer to writing code only within a class. Thus, there is a distinction between functions, which do not reside within a class, and methods. * Converts a temperature from Fahrenheit to Celsius. * @param f temperature in degrees Fahrenheit. * @return temperature in degrees Celsius. * / static double fToC(double f) { return 0.0; } } All code, in Java, belongs to a class . Classes have much more complex and concrete def initions that we will investigate in due time, but for now, we may think of them as the homes of our functions. By the way, functions in Java are called methods.1 Again, this slight terminology differentiation is not without its reasons, but for all intents and purposes, func tions are methods and vice versa. The class we have defined in the previous listing is called TempConverter, giving rise to believe that the class does something related to temperature conversion. We wrote the fToC method signature, whose return type is a double, and has one param eter, which is also a double. A double is a floating-point value, meaning it potentially has 1 © The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 J. Crotts, Learning Java, https://doi.org/10.1007/978-3-031-66638-4_1 3
Page
12
4 1 Testing and Java Basics decimals. For our method, this choice makes sense, because if we were to instead receive an integer int, we would not be able to convert temperatures such as 35.5 degrees Fahrenheit to Celsius. The static keyword that we wrote also has significance, but for now, consider it a series of six mandatory key presses (plus one for the space thereafter). Above this method signature is a Java documentation comment, providing a brief summary of the method’s purpose, as well as the data it receives as parameters and its return value, should it be necessary. Inside its body lies a single return, in which we return 0.0. The return value is what a method call, or method invocation, resolves to. For example, if we were to call fToC with any arbitrary double value, the call would be substituted with 0.0. Passing a value, or an argument, to a method is sometimes called method application. fToC(5) => 0.0 fToC(78) => 0.0 fToC(-3123) => 0.0 The fToC method is meaningless without a complete implementation. Before we write the rest of the method body, however, we should design test cases to ensure it works as expected. Test cases verify the correctness (or incorrectness) of a method. We, as the readers, know how to convert a temperature from Fahrenheit to Celsius, but telling a computer to do such a conversion is not as obvious at first glance. To test our methods, we will use the JUnit testing framework. Creating tests for fToC is straightforward; we will make a second class called TempConverterTester to house a single method: testFToC. class TempConverterTester { @Test void testfToC() { Assertions.assertAll( () -> Assertions.assertEquals(0, TempConverter.fToC(32))); } } We want JUnit to recognize that fToC contains testing code, so we prepend the @Test annotation to the method signature. In its body, we call Assertions.assertAll, which receives a series of methods that are ran in succession. In our case, we want to assert that our fToC method should return 0 degrees Celsius when given a temperature of 32 degrees Fahrenheit. The first parameter to assertEquals is the expected value of the test, i.e., what we want (and know) it to produce. The second parameter is the actual value of the test, i.e., what our code produces. When writing tests, it is important to consider edge cases and all possible branches of a method implementation. Edge cases are inputs that are possibly missed by an implementation, e.g., -40, since it is the same in both Fahrenheit and Celsius, or 0. So, let us add a few more test cases.2 2 To condense our code, we exclude the TempConverter class name out of conciseness. On the other hand, we can omit the Assertions class name by importing the two methods as shown in the listing.
Page
13
1.1 A First Glimpse at Java 5 import static Assertions.assertAll; import static Assertions.assertEquals; class TempConverterTester { @Test void testfToC() { assertAll( () -> assertEquals(0, fToC(32)), () -> assertEquals(100, fToC(212)), () -> assertEquals(-40, fToC(40)), () -> assertEquals(-17.778, fToC(0), .01), () -> assertEquals(-273.15, fToC(-459), .01)); } } Computers are, unfortunately, not perfect; floating-point operations may result in preci- sion/rounding errors. So, as an optional third argument to assertEquals, we can provide a delta , which allows for precision up to a certain amount to be accepted as a valid answer. For example, our tolerance for the fourth and fifth test cases is 0.01, meaning that if our actual value is less than ± 0.01 away from the expected value, the test case succeeds. Now that we have copious amounts of tests, we can write our method definition. Of course, it is trivial and follows the well-known mathematical definition. class TempConverter { /** 3 By “standard,” we mean the widely-accepted paradigm of parentheses first, then exponents, followed by left-to-right multiplication/division, and finally left-to-right addition/subtraction. 4 In Java 10, the var keyword was introduced, which automatically infers the type of a given expres sion (Gosling et al., 2023, Chapter 14). * Converts a temperature from Fahrenheit to Celsius. * @param temperature in F. * @return temperature in C. * / static double fToC(double d) { return (d - 32) * (5.0 / 9.0) } } The definition of fToC brings up a few points about Java’s type system. The primitive mathematics operations account for the types of its arguments. So, for instance, subtracting two integers will produce another integer. More noteworthy, perhaps, a division of two integers produces another integer, even if that result seems to be incorrect. Thus, 5 / 9 results in the integer 0. If, however, we treat at least one of the operands as a floating-point value, we receive a correct result of approximately 0.555555: 5.0 / 9. Java by default uses the standard order of operations when evaluating mathematics expressions, so we force certain operations to occur first via parentheses.3 Unlike some programming languages that are dynamically-typed, e.g., Scheme, Python, JavaScript, the Java programming language requires the programmer to specify the types of variables (Sebesta, 2012, Chapter 5).4 Java has several default primitive datatypes, which are the simplest reducible form of a variable. Such types include int, char, double, boolean, and others. Integers, or int, are any positive or negative number without decimals. Doubles,
Page
14
6 1 Testing and Java Basics or double, are values with decimals.5 Characters, or char, are a single character enclosed by single quotes, e.g., 'X'. Finally, booleans, or boolean, are either true or false. There are other Java data types that specify varying levels of precision for given values. Integers are 32-bit signed values, meaning they have a range of [-231, 231). The short data type, on the contrary, is 16-bit signed. Beyond this is the byte data type that, as its name suggests, stores 8-bit signed integers. Floating-point values are more tricky, but while double uses 64 bits of precision, the float data type uses 32 bits of precision. 5 The double data type cannot represent all real numbers. For example, the value n is an irrational (real) number, and thus cannot be represented exactly in a finite number of bits. Example 1.2. Let us design a method that receives two three-dimensional vectors and re turns the distance between the two. We can, effectively, think of this as the distance between two points in a three-dimensional plane. Therefore, because each vector contains three com ponents, we need six parameters, where each triplet represents the vectors v1 and v2 . class VectorDistance { /** * Computes the distance between two given vectors: * v_1=(x_1, y_1, z_1) and v_2=(x_2, y_2, z_2) * / static double computeDistance(double x1, double y1, double z1, double x2, double y2, double z2) { return 0.0; } } Again, we start by writing the appropriate method signature with its respective parameters and a Java documentation comment explaining its purpose. For tests, we know that the distance between two Cartesian points in a three-dimensional plane is D(vi, V2) = v^xi - X2)2 + (yi - ys)2 + (zi - Z2)2 So, let’s now write a few test cases with some arbitrarily-chosen points that we can verify with a calculator or manual computation. import static Assertions.assertAll; import static Assertions.assertEquals; class VectorDistanceTester { @Test void testComputeDistance() { assertAll( assertEquals(8.66, computeDistance(3, 2, 1, 8, 7, 6), .01), assertEquals(12.20, computeDistance(0, 0, 0, 8, 7, 6), .01), assertEquals(8.30, computeDistance(-8, -2, 1, 0, 0, 0), .01)); } } Notice again our use of the optional delta parameter to allow us a bit of leeway with the rounding of our answer. Fortunately, the implementation of our method is just a retelling of the mathematical definition.
Page
15
1.1 A First Glimpse at Java 7 class VectorDistance { /** * Computes the distance between two given vectors * @param x1 x component of first vector. * @param y1 y component of first vector. * @param z1 z component of first vector. * @param x2 x component of second vector. * @param y2 y component of second vector. * @param z2 z component of second vector. * @return distance between v1 and v2. */ class SlopeIntercept { /** * Computes the slope of the line represented by the two points. * @param x1 x-coordinate of point 1. * @param y1 y-coordinate of point 1. * @param x2 x-coordinate of point 2. * @param y2 y-coordinate of point 2. * @return slope of points. */ static double slope(int x1, int y1, int x2, int y2) { return 0.0; } /** * Computes the y-intercept of the line represented by the two points. * @param x1 x-coordinate of point 1. * @param y1 y-coordinate of point 1. * @param x2 x-coordinate of point 2. * @param y2 y-coordinate of point 2. * @return y-intercept of line represented by points. */ static double yIntercept(x1, int y1, int x2, int y2) { return 0.0; } } static double computeDistance(double x1, double y1, double z1, double x2, double y2, double z2) { return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2) + Math.pow(z1 - z2, 2)); } } We make prolific use of Java’s Math library in designing this method; we use the sqrt method for computing the square root of our result, as well as pow to square each intermediate difference. Example 1.3. Slope-intercept is an incredibly common algebra and geometry problem, and even pokes its way into machine learning at times when computing best-fit lines. Let’s design two methods, both of which receive two points (x1, y1), (x2, y2). The first method returns the slope m of the points, and the second returns the y-intercept b of the line. Their respective signatures are straightforward—each set of points is represented by two integer values and return doubles. y2 - y1 m = ------------------ x2 - x1 b = y1 - mx1 *
Page
16
8 1 Testing and Java Basics The tests that we write are verifiable by a calculator or mental math. Our yIntercept method depends on a successful implementation of slope, as designated by the formula of the former. In the next chapter, we will consider cases that invalidate the formula, e.g., when two points share an x coordinate, in which the slope is undefined for those points. import static Assertions.assertAll; import static Assertions.assertEquals; class SlopeInterceptTester { @Test void testSlope() { assertAll( () -> assertEquals(1, slope(0, 0, 1, 1)), () -> assertEquals(0, slope(0, 0, 1, 0)), () -> assertEquals(2, slope(8, 4, 2, 4)), () -> assertEquals(0.5, slope(-1, 5, 3, 7))); } @Test void testYIntercept() { assertAll( () -> assertEquals(0, yIntercept(0, 0, 1, 1)), () -> assertEquals(0, yIntercept(0, 0, 1, 0)), () -> assertEquals(4, yIntercept(8, 4, 2, 4)), () -> assertEquals(5.5, yIntercept(-1, 5, 3, 7))); } } And the implementation of those two methods follows directly from the mathematical definitions. We replace our temporary 0.0 return values with the appropriate expressions, and all tests pass. class SlopeIntercept { /** * Computes the slope of the line represented by the two points. * @param x1 x-coordinate of point 1 * @param y1 y-coordinate of point 1 * @param x2 x-coordinate of point 2 * @param y2 y-coordinate of point 2 * @return slope of points. * / static double slope(int x1, int y1, int x2, int y2) { return (y2 - y1) / (x2 - x1); } /** * Computes the y-intercept of the line represented by the two points. * @param x1 x-coordinate of point 1. * @param y1 y-coordinate of point 1. * @param x2 x-coordinate of point 2. * @param y2 y-coordinate of point 2. * @return y-intercept of points. * / static double yIntercept(x1, int y1, int x2, int y2) { return y1 - slope(x1, y1, x2, y2) * x1; } }
Page
17
1.1 A First Glimpse at Java 9 Example 1.4. We are starting to get used to some of Java’s verbosity! Let’s now design a method that, when given a (numeric) value of x, evaluates the following quartic formula: q(x) = 4x4 + 7x3 + 21x2 - 65x + 3 6 Remember that the coefficient is applied after applying the exponent to the variable. That is, if x = 3, then 4x3 is equal to 4 • (3)3, which resolves to 4 • 27, which resolves to 108. Its signature is straightforward: we receive a value of x, namely a double, and return a double, since our operations work over double values. Again, we return zero as a temporary solution to ensure the program successfully compiles. class QuarticFormulaSolver { /** * Evaluates the equation 4x~4 + 7x~3 + 21x~2 - 65x + 3. * @param x the input variable. * @return the result of the expression after substituting x. */ static double solveQuartic(double x) { return 0.0; } } Test cases are certainly warranted, but may be a bit tedious to compute by hand, so we recommend using a verified calculator to compute expected solutions!6 import static Assertions.assertAll; import static Assertions.assertEquals; class QuarticFormulaSolverTester { @Test void testSolveQuartic() { assertAll( () -> assertEquals(3, solveQuartic(0)), () -> assertEquals(313445.1875, solveQuartic(16.25))); } } We write tests before the method implementation because we know, intuitively, how to solve an equation for a variable, whereas a computer has to be told how to solve this task. Fortunately for us, a quartic equation solver is nothing more than returning the result of the expression. We have to use the exponential Math.pow method again (or conjoin several multiplicatives of x), but otherwise, nothing new is utilized. class QuarticFormulaSolver { /** * Evaluates the equation 4x~4 + 7x~3 + 21x~2 - 65x + 3. * @param x the input variable. * @return the result of the expression after substituting x. */ static double solveQuartic(double x) { return 4 * Math.pow(x, 4) + 7 * Math.pow(x, 3) + 21 * Math.pow(x, 2) - 65 * x + 3; } }
Page
18
10 1 Testing and Java Basics And, as expected, all tests pass. With only methods and math operations at our disposal, the capabilities of said methods are quite limited. 1.1.1 The Main Method Java programming tutorials are quick to throw a lot of information at the reader/viewer, and our textbook is no exception to this practice. Unfortunately, Java is a verbose programming languages, and to begin designing a method, we must wrap its definition inside a class. After this step, we can design the static method implementation. Readers no doubt question the significance of static. For the first few chapters, we will intentionally omit its definition, as an explanation would almost certainly confuse the reader coming from another language. Therefore, for the time being, simply view it as six characters, plus a space, that must be typed in order to write a method that we can then test with JUnit. Imagine, however, a situation where we want to output the result of an expression without using a test, as we will do in many examples. Java requires a main method in any executable Java class that does not use tests. For the sake of completeness, let’s now write the traditional “Hello, world!” program in Java using the main method instead of tests. class MainMethod { public static void main(String[] args) { System.out.println("Hello, world!"); } } Yikes, that is a lot of required code to output some text to the console; what does public mean, and what are those [] brackets after the String word? Once again, we will not detail their importance, but view them as more mandatory characters to type when writing a main method. The only word we will explain is void, which means that the method does not return a value. If the readers are coming from a functional programming language, e.g., Scheme/Racket or OCaml, then it is almost certainly the case that they never worked with methods that did not return a value nor received no arguments.7 The println method, for example, has no return value; its significance comes from the fact that it outputs text to the terminal/console, which is a method side-effect. We’ll come back to what all of this means in subsequent chapters, but we could not avoid at least briefly mentioning it and its existence in the Java language. 7 A method of no arguments is called a thunk, or a nullary method. 1.2 Strings Strings, as you might recall from another programming language, are sequences of charac ters. Characters are enclosed by single quotes, e.g., 'x'. A Java String is enclosed by double quotes. e.g., "Hello!". Strings may contain any number of characters and any kind of charac ter, including no characters at all or only a single character. There is an apparent distinction between 'x' and "x": the former is a char and the latter is a String. Note the capitalization on the word String; the case sensitivity is important, because String is not one of the prim itive datatypes that we described in the previous section. Rather, it is a sequence of char
Page
19
1.2 Strings 11 String Class A string is an immutable sequence of characters. Strings are indexed from 0 to |S| — 1, where |S| is the number of characters of S. S1 + S2 adds the characters from S2 onto the end of S1, producing a new string. int S.length() returns the number of characters in S. char S .charAt(i) retrieves the (i + 1)th character in S. We can also say that this retrieves the character at index i of S. String S.substring(i, j) returns a new string containing the characters from index i, inclu sive, to index j, exclusive. The number of extracted characters is j — i. We will use the notation S’ □ S to denote that S’ is a substring of S. String S.substring(i) returns a new string from index i to the end of S. int S.indexOf(S’) returns the index of the first instance of S’ in S, or —1 if S’ g S. boolean S.contains(S’) returns true if S’ g S; false otherwise. String S.repeat(n) returns a new string containing n copies of S. String String.valueOf(v) returns a stringified version of v, where v is some primitive value. int Integer.parselnt(S) returns the integer representation of a string S if it can be parsed as such. Fig. 1.1: Useful String Methods. values coalesced into one value “under-the-hood,” so to speak. We can declare a String as a variable using the keyword combined with a variable name, just as we do for primitives. class NewStringTests { public static void main(String[] args) { String s1 = "Hello, world!"; String s2 = "How are you doing?"; String s3 = "This is another string!"; } } We can conjoin, or concatenate, strings together with the + operator. Concatenating one string s2 onto the end of another string s1 creates a new string s3 by copying the characters from s1 and s2 , in that order. Figure 1.1 states that strings are immutable, so how can we possibly combine them without altering one or the other? When concatenating two strings s1 and s2 , Java creates a new string that is the result of the concatenation, leaving s1 and s2 unaltered.8 8 Hence, a series of repeated string concatenations is inefficient. The performance implications of this are addressed in a subsequent chapter. To retrieve the number of characters in a string, invoke .length on the string. The empty string has length zero, and spaces/whitespace characters count towards the length of a string, since spaces are characters. For instance, the length of " a " is five because there are two spaces, followed by a lowercase 'a', followed by two more spaces. Comparing strings for equality seems straightforward: we can use == to compare one string versus another. Using == for determining string equality is a common Java beginner pitfall! Strings are objects and cannot be compared for value-equality using the == operator. Intro ducing this term “value-equality” insinuates that strings can, in fact, be compared using ==, which is theoretically correct. The problem is that the result of a comparison using == com
Page
20
12 1 Testing and Java Basics pares the memory addresses of the strings. In other words, s1 == s2 returns whether the two strings reference, or point to, the same string in memory. class NewStringTests { public static void main(String[] args) { String s1 = "Hello"; String s2 = s1; System.out.println(s1 == s2); // true } } In the above code snippet we declare s1 as the string literal "Hello", then initialize s2 to point to s1 . So there are, in effect, two references to the string literal "Hello". Let’s try something a little more tricky: suppose we declare three strings, where s1 is the same as before, s2 is the string literal "Hello", and s3 is the string literal "World". Comparing s1 to s2 , strangely enough, also outputs true, but why? It seems that s1 and s2 reference different string literals, even though they contain the same characters. Indeed, the latter is obviously the case, but Java performs an optimization called string pool caching. That is, if two strings are the same string literal, it makes little sense for them to point to two distinct references, entirely because strings are immutable. Therefore, Java optimizes these references to point to a single allocated string literal. Comparing s1 or s2 with s3 outputs false, which is the anticipated result. class NewStringTests { public static void main(String[] args) { String s1 = "Hello"; String s2 = "Hello"; String s3 = "World"; System.out.println(s1 == s2); // true (?) System.out.println(s2 == s3); // false } } If we want, for some reason, to circumvent Java’s string caching capabilities, we need a way of instantiating a new string for our variables to reference. We use the power of new String to create a brand new, non-cached string reference. We treat this as a method, of sorts, called the object constructor. In Chapter 4, we will reintroduce constructors in our discussion on objects and classes, but for now, consider it (namely new String) as a method for creating distinct String instances. This method is overloaded to receive either zero or one parameter. The latter implementation receives a String, whose characters are copied into the new String instance. If we pass a string literal to the constructor, it copies the characters from the literal into the new string. At this point, the only possible object to be equal to s1 is s1 itself or another string that points to its value. class NewStringTests { public static void main(String[] args) { String s1 = new String("hello"); String s2 = new String("hello"); String s3 = new String("world"); String s4 = s1; System.out.println(s1 == s1); // true System.out.println(s1 == s2); // false! System.out.println(s2 == s3); // false System.out.println(s1 == s4); // true } }
The above is a preview of the first 20 pages. Register to read the complete e-book.
Comments 0
Loading comments...
Reply to Comment
Edit Comment