Programming in Java A C Norman, Lent Term 2007 Part IA 2 Contents 1 Preface 1.1 What is programming about? . . . . . . . . . 1.2 What about good programming? . . . . . . . 1.3 Ways to save time and effort . . . . . . . . . 1.3.1 Use existing resources . . . . . . . . 1.3.2 Avoid dead-ends . . . . . . . . . . . 1.3.3 Create new re-usable resources . . . . 1.3.4 Documentation and Test trails . . . . 1.3.5 Do not make the same mistake twice . 1.4 Where does Java fit in? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 General advice for novices 7 7 8 9 9 10 10 10 10 12 13 3 Introduction 3.1 Introduction . . . . . . . . . . . . . . . 3.1.1 Books . . . . . . . . . . . . . . 3.2 Practical work . . . . . . . . . . . . . . 3.2.1 Exercises . . . . . . . . . . . . 3.3 A Cook-book Kick-start . . . . . . . . 3.3.1 Code Layout . . . . . . . . . . 3.3.2 Emacs . . . . . . . . . . . . . . 3.3.3 Drawing to a window: JApplets 3.3.4 HTML and appletviewer . . . . 3.3.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 15 18 21 24 28 33 34 37 42 43 4 Basic use of Java 4.1 Data types, constants and operations 4.1.1 Reserved Words . . . . . . 4.1.2 Basic Types . . . . . . . . . 4.1.3 Exercises . . . . . . . . . . 4.2 Operators and expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 49 49 51 65 71 3 . . . . . . . . . . CONTENTS 4 4.3 4.4 4.5 4.6 4.7 4.8 5 4.2.1 Exercises . . . . . . . . . . . . . . . Control structures . . . . . . . . . . . . . . . 4.3.1 Exercises . . . . . . . . . . . . . . . Control structures Part 2 . . . . . . . . . . . 4.4.1 Expression Statements . . . . . . . . 4.4.2 Blocks . . . . . . . . . . . . . . . . 4.4.3 Null statements . . . . . . . . . . . . 4.4.4 if . . . . . . . . . . . . . . . . . . . 4.4.5 while, continue and break . . . 4.4.6 do . . . . . . . . . . . . . . . . . . . 4.4.7 for . . . . . . . . . . . . . . . . . . 4.4.8 switch, case and default . . . 4.4.9 return . . . . . . . . . . . . . . . 4.4.10 try, catch and throw, finally 4.4.11 assert . . . . . . . . . . . . . . . 4.4.12 Variable declarations . . . . . . . . . 4.4.13 Method definitions . . . . . . . . . . 4.4.14 Exercises . . . . . . . . . . . . . . . Java classes and packages . . . . . . . . . . . 4.5.1 Exercises . . . . . . . . . . . . . . . Inheritance . . . . . . . . . . . . . . . . . . 4.6.1 Inheritance and the standard libraries 4.6.2 Name-spaces and classes . . . . . . . 4.6.3 Program development with classes . . Generics . . . . . . . . . . . . . . . . . . . . 4.7.1 Exercises . . . . . . . . . . . . . . . Important features of the class libraries . . . . 4.8.1 File input and output . . . . . . . . . 4.8.2 Big integers . . . . . . . . . . . . . . 4.8.3 Collections . . . . . . . . . . . . . . 4.8.4 Simple use of Threads . . . . . . . . 4.8.5 Network access . . . . . . . . . . . . 4.8.6 Menus, scroll bars and dialog boxes . 4.8.7 Exercises . . . . . . . . . . . . . . . Designing and testing programs in Java 5.1 Different sorts of programming tasks . . 5.2 Analysis and description of the objective 5.2.1 Important Questions . . . . . . 5.2.2 Informal specifications . . . . . 5.2.3 Formal descriptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 77 77 82 82 82 83 83 84 84 85 85 87 87 88 88 89 90 98 108 115 116 120 125 129 130 139 140 147 150 150 153 155 160 . . . . . 167 171 179 179 180 181 CONTENTS 5.3 5.4 5.5 5.6 5.7 5.8 5.9 5.10 5.11 5.12 5.13 5.14 5.15 5.16 5.17 5.18 5 5.2.4 Executable specifications . . . . . . . . Ethical Considerations . . . . . . . . . . . . . How much of the work has been done already? What skills and knowledge are available? . . . Design of methods to achieve a goal . . . . . . 5.6.1 Top-Down Design . . . . . . . . . . . 5.6.2 Bottom-Up Implementation . . . . . . 5.6.3 Data Centred Programming . . . . . . 5.6.4 Iterative Refinement . . . . . . . . . . 5.6.5 Which of the above is best? . . . . . . How do we know it will work? . . . . . . . . . While you are writing the program . . . . . . . Documenting a program or project . . . . . . . How do we know it does work? . . . . . . . . . Is it efficient? . . . . . . . . . . . . . . . . . . Identifying errors . . . . . . . . . . . . . . . . Corrections and other changes . . . . . . . . . Portability of software . . . . . . . . . . . . . Team-work . . . . . . . . . . . . . . . . . . . Lessons learned . . . . . . . . . . . . . . . . . Final Words . . . . . . . . . . . . . . . . . . . Challenging exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 182 183 185 186 186 189 190 190 191 191 194 195 197 200 201 204 205 206 207 208 208 6 A representative application 219 6.1 A Lisp interpreter . . . . . . . . . . . . . . . . . . . . . . . . . . 219 6.1.1 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . 233 7 What you do NOT know yet 8 Model Examination Questions 8.1 Java vs ML . . . . . . . . 8.2 Matrix Class . . . . . . . . 8.3 Hash Tables . . . . . . . . 8.4 Compass Rose . . . . . . . 8.5 Language Words . . . . . 8.6 Exception abuse . . . . . . 8.7 Queues . . . . . . . . . . 8.8 Loops . . . . . . . . . . . 8.9 Snap . . . . . . . . . . . . 8.10 Partitions . . . . . . . . . 8.11 Laziness . . . . . . . . . . 235 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 237 238 238 239 239 240 240 240 240 241 241 CONTENTS 6 8.12 8.13 8.14 8.15 8.16 8.17 8.18 8.19 8.20 8.21 8.22 8.23 8.24 8.25 8.26 8.27 9 Cryptarithmetic . . . . . . . Bandits . . . . . . . . . . . Exception . . . . . . . . . . Features . . . . . . . . . . . More features . . . . . . . . Debate . . . . . . . . . . . . Design . . . . . . . . . . . . Filter (Coffee?) . . . . . . . Parse trees . . . . . . . . . . Big Addition . . . . . . . . Lists in Java . . . . . . . . . Pound, Shillings and Ounces Details . . . . . . . . . . . . Name visibility . . . . . . . Several Small Tasks . . . . . Some Tiny Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Java 1.5 or 5.0 versus previous versions 9.1 An enhanced for loop . . . . . . . . . . . 9.2 Generics . . . . . . . . . . . . . . . . . . . 9.3 assert . . . . . . . . . . . . . . . . . . . 9.4 Static imports . . . . . . . . . . . . . . . . 9.5 Auto-boxing . . . . . . . . . . . . . . . . . 9.6 Enumerations . . . . . . . . . . . . . . . . 9.7 printf . . . . . . . . . . . . . . . . . . . 9.8 Scanner . . . . . . . . . . . . . . . . . . . 9.9 Variable numbers of arguments for methods 9.10 Annotations . . . . . . . . . . . . . . . . . 9.11 Enhanced concurrency control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242 242 244 245 245 246 246 246 247 248 248 248 249 250 250 251 . . . . . . . . . . . 253 253 254 254 254 254 254 255 255 255 256 256 Chapter 1 Preface 1.1 What is programming about? There are two stories you can tell yourself about what this course is going to do for you. The first is the traditional one that it is so you can learn some Java. Acquire knowledge and skills. The second, which may be more interesting, is to see this course as part of your journey as you start to become (or at least appreciate what it is to be) a Computer Scientist. This second perspective suggests that there may be something for you here whether or not you believe you are already skilled in Java, and it challenges you to look beyond the mere details to the tought patterns that link them together. In the early days of computers programming involved a full understanding of the way that the hardware of your computer worked, your program, when run, took over essentially the whole machine and it had to include everything needed to manage input and output. In extreme cases one started the process of loading code into a computer by using hand-switches to place bit-patterns directly into the machine’s memory. After a while operating systems came along and provided serious insulation from that level of extreme awareness of hardware, and high-level languages make it possible to express programs in at least semi-humanunderstandable form. But still the emphasis was on “writing a program”, which tended to be a stand-alone application that solved some problem. Libraries of pre-written sub-programs grew up, but for a very long time the ones that anybody could rely on having access to were either rather specialist or the functionality that they provided was at a rather low and boring level. There were libraries that could really help you with serious tasks (such as building a windowed user-interface) but none of them gained really global acceptance, and only a few were of any use on more than one brand of computer. The libraries that were standard with typical programming languages provided for fairly limited file 7 8 CHAPTER 1. PREFACE and terminal access input and output, modest string handling and really not a lot else. Operating systems made their capabilities available in the form of libraries that programs could call on, but overall coherent design was rare and use of these “libraries” led to inherently non-portable code. Building a new library was not part of the common experience of programmers, and indeed large-scale re-use of code was the exception rather than the rule. There has been an ideal or a dream of re-usable software components for ages, but it is only recently that it has started to become something that can be not just feasible but reasonably convenient. Java is one of the languages that encourages this move, and the whole Object Oriented Programming movement that Java forms part of provides a context. So in the old world one thought of a program as a large complicated thing that called upon facilities from a few fixed libraries that you happened to have available. Today instead of that you should often start a project with the intention of developing a set of new re-usable and general libraries that themselves build on and extend existing software components. You will design these libraries so that once they exist the program you had to write becomes a fairly simple application of them: it will do some minor customisation and link together different units within the overall structure of your libraries, but with luck it will of itself be fairly small and straightforward. If you do this well you will find that the library you have created will serve you well in future projects, or it may even become something worth circulating (or selling) of itself. With these ideas in mind you will want to make it well-structured, robust and you may even feel motivated to accompany it with some coherent documentation! So overall the mind-set for the 21st Century is that you design and write reusable components and libraries, and that writing mere stand-alone programs is a terribly old-fashioned and dull thing to do! 1.2 What about good programming? The first and utterly overriding character of a good program is that it must be fit for its purpose. Good programming must not only lead to a good program, but should do so in a way that reaches a successful conclusion reliably and without taking more time and effort than is really required. These comments may seem bland and self-evident, but they have real consequences! The first is that you can not judge a program until you know what its purpose is. Even though almost all the exercises you will do this year will be both small and will never have any part of their code re-used it will be proper for you to practise writing them as if they are much larger and more important. That will mean that you are expected to accompany the code you write with both notes 1.3. WAYS TO SAVE TIME AND EFFORT 9 about its external behaviour and how to use it and with comments that describe its internal structure and organisation. For certain sorts of library code it will make sense to use the documentation arrangements that the main Java libraries use. This involves things called “documentation comments” and a utility called javadoc that will be described later. Without this documentation you may believe that your programs meet their purpose but you do not have any basis for expecting others the agree. 1.3 Ways to save time and effort Working with computers can swallow up an astonishing amount of time. To be able to get everything you need done you will want to find ways of economising. The key to doing this effectively is to concentrate on techniques that save time in the long run. Some ideas that appear to speed things up in the short run can end up costing more later on! 1.3.1 Use existing resources You are encouraged to use code-fragments from these notes in any way you want. You can sometimes get a fresh project off the ground by extracting at least fragments from a previous piece of work you have done. The Java libraries are your friend: they contain facilities to do many of the things you will find yourself needing. In general before you ever write anything from scratch for yourself consider whether there is something that can give you a head-start. Everybody might reasonably worry that the above paragraph could be seen as an invitation to plagiarise! Do not take it that way: couple it with a very firm remark that when you use other material you should acknowledge your sources, and you should not pillage the material of those who are unwilling to make their work available to you. As far as tickable exercises for this course are concerned you are encouraged to discuss what you are doing with friends and supervisors, and collect code-sketches and fragments from them, provided that when you submit your work to the department you really understand everything in your submission and you have learned enough that (if necessary) you could then instantly and comfortable re-create your submission in a sound-proof booth visibly cut-off from further help. So rather than sit and suffer in isolation, seek web-sites, friends, demonstrators, books and code libraries to give you guidance so long as you learn from then and do not just blindly copy! 10 CHAPTER 1. PREFACE 1.3.2 Avoid dead-ends Sometimes you can start designing or writing some code and as you go things seem to get harder and harder. Something is not working and you have no idea why. You do not want to get in such a state! Clear advance planning and a well organised set of working habits are the best way to avoid the mess. If you find yourself in what feels like a dead-end then avoid (a) panic (b) a tendency to try almost random changes in the hope that things will improve and (c) temptation to work all afternoon, evening and night until you solve things. Go back and look at your plan. If necessary refine it so you can make progress in tiny steps. Explain your plan and your code to somebody else (either in person or in the form of written documentation). But do not just get bogged down: taking a break and coming back fresh can often save overall time. 1.3.3 Create new re-usable resources An ideal that this course would like to instil in you is one of creating re-usable bodies of code. This will take more care and time when you first implement them (and of course anything re-usable deserves proper documentation and testing) but that can be well paid back when you get a chance to call on it again. At a minimum this can include keeping all your working code from both the tickable and other exercises in these notes so you can use parts of them as templates in future projects. 1.3.4 Documentation and Test trails Neatly formatted code with clear comments and a well set out collection of test cases can seem slower to write then a jumble of code that is just thrown together. However long experience suggests that the jumble of code is much less likely to work first time, and that especially as your projects get bigger that early investment in good habits pay dividends. 1.3.5 Do not make the same mistake twice Especially while learning a new language, such as Java, you will make mistakes. As you design and write gradually larger and larger bodies of code you will make mistakes. Observe and appreciate these, and try to observe yourself as you uncover and correct them. Possibly even keep a small notebook of “bugs I have had in my code”. Then each time you make a mistake seek some scheme that can prevent the same one from causing significant trouble in the future. Your fingers will always leave typos in everything you write and your mind can always wander: the 1.3. WAYS TO SAVE TIME AND EFFORT 11 idea is not to avoid glitches totally, it is to build up a personal toolkit of ways to overcome them without pain or waste. CHAPTER 1. PREFACE 12 1.4 Where does Java fit in? There are those who believe that Object Oriented Design and Programming is The Answer to reliable large-scale system building, a silver bullet1 that cures the major woes of the last fifty years of over-costly and haphazard use of computers. Java is one of the major practical and widely-used languages that fall within the Object Oriented family. Key attitudes that come with this are that projects should be structured into potentially re-usable blocks (the Java class construct that you will learn about later being a major way of achieving this). These blocks should each take responsibility for just one aspect of the overall behaviour you are trying to code up. The decomposition should be arranged so that interaction between blocks is as tidy and disciplined as possible. Overall at least a rough caricature is that ML Figure 1.1: Silver Bullet stresses absolute correctness via mathematically styled Needed. structure, and encourages very concise programming styles. Java on the other hand follows a view that language constructs that support large-scale structuring of projects are the key. It also expects that having the user write out types and qualifiers explicitly will help others to read your program. ML as taught last term provides a fairly basic library, but mostly you spend the Michaelmas Term writing stand-alone programs and fragments. With Java there is heavy emphasis on a rich (and perhaps hence complicated) library that supports a very full range of computing needs. 1 Brad words. Cox in Byte magazine October 1990, pp 209–218 puts things in much these extreme Chapter 2 General advice for novices Following tradition, I provide ten items of guidance for the benefit of those who are relatively new to programming. I hope that each of these will be re-inforced during the course as a whole, but here they are collected together at the beginning: 1 Understand the task you are about to solve before starting to write a program about it. Work through methods and procedures by hand on paper etc. Plan some test cases. Identify cases that will represent boundaries or oddities. In general prepare a plan before you start going anywhere near a computer; 2 Sketch the structure of the whole of your code out informally so you have full overview before fussing about exact syntax etc. Ensure you know what you expect that the computer will do. This initial sketch can be very informal, and may be in terms of diagrams rather than anything that looks much like real programming. The key word here is “structure”. This applies with way greater force when your code starts to grow: you should always design a good way to factor your code into reasonably self-contained and independent components (each will be one “class” in your code) right from the start; 13 14 CHAPTER 2. GENERAL ADVICE FOR NOVICES 3 Write out key parts of above in the form of comments before you start the real code. Concentrate in these comments on the “what” and “why” of your code rather the details of “how”. This will really help when you show your work to somebody else because you need help! I will explain this one again: The first thing you will type into a computer when you start writing any program will be a set of overview comments that explain its strategy and structure; 4 At least for a first version of anything, favour clarity and obvious correctness over pretty well everything else. Clever tricks, worries about efficiency, generalisations etc can come later; 5 Neat consistent layout and thoughtfully named fields, methods, variables etc. are a good investment of your time. Cryptic is bad even if it saves keystrokes in the short term; 6 If a task is too big to solve in just one gulp look for ways of breaking it down into sub-tasks. As you do this think about ways you will be able to test code you write for each sub-task and work on the whole thing step by step; 7 When you try to compile your code and see a syntax error do not panic. Learn to interpret the compiler’s diagnostics. And only try to remove one error at a time: count it as a success if next time you try to compile the first error has give so you can then concentrate on the second; 8 When you have compiled your program and run it and it gives wrong answers or behaves badly do not panic. First work to understand what is wrong and only after you have found where the problem is think about ways to fit it. Do not just try random changes! Eg. confirm what your program actually does by adding assert and extra print statements; 9 Whenever you find you have to change your program review comments, consider if it will now do exactly what you want, and re-run all your test cases. Experience shows that changes (for whatever cause) can introduce new problems while you are in the process of fixing old ones; 10 If you find you are spending a seriously long time trying to make sense of anything then find help from friends or a supervisor or a book. Do not just keep building up your frustration not getting anywhere! Chapter 3 Introduction 3.1 Introduction We have been using Java as a first-year teaching language here in Cambridge since 1997-8. We teach this course following on from “Foundations of Computer Science” which used ML, and there are a number of things it is intended to do: 1. Provide all of our students with exposure to a common programming language that can be used by later courses and practical work in the CST; 2. Introduce the syntax that is (almost) common to several of the most widely used practical programming languages today (the syntax of Java has a great deal in common with that of C and C++, so having learned Java you are quite a long way to understanding those languages too); 3. Discuss the process of designing, writing and debugging programs and raise some awareness of issues of style; 4. Present the Object Oriented aspects of a programming language as means to enforce modularity in large programs; 5. Teach basic use of Java, a language that has significant relevance in the outside world today. Note that in our Part IA course “Software Engineering II” provides significant extra coverage on issues of structuring programs (especially ones that are large or developed by collaborative work in a group), and in Part IB course there is a lecture course once entitled “Further Java” and now renamed ”Concurrent Systems and Applications”: it should not be imagined that I will cover all aspects of the language or its use here! 15 16 CHAPTER 3. INTRODUCTION The nature of teaching a course involving programming in some particular language means that some features need to be mentioned well before the place where they can be fully explained, and so it will not make sense to keep the presentation in lectures totally linear and tied to these notes, but for supervision purposes the structure shown here should suffice. With each section I will have a few examples or exercises. Especially at the start of the course these will often be pretty silly, but the ones right at the end can be viewed as samples of the sort of question that might arise in the examination. Although I want some of my examples to be nice and easy I would like to have others that are interesting challenges for those who already think they know it all (ha ha). It is always very hard to judge the amount of trouble these will give you all, so if they are either too easy or too difficult I apologise. Examination questions will be set on the supposition that you have attempted a reasonable sampling of the exercises. The aim of these notes is that they should serve both as guidance to students and to supervisors, and so there is no separate supervisor’s guide. Originally I had intended that they would be structured into sixteen sections corresponding to the sixteen lectures available. As I prepared the notes I concluded that such a rigid arrangement was not tenable. Thus the lectures can be expected to cover roughly the material in these notes in roughly the same order, with an approximation to one-sixteenth of the entire notes corresponding to each lecture! It might be noted that a Java course for the Diploma students runs during the Michaelmas term. The lecture notes associated with that course may provide a presentation of Java which is different from mine and thus may complement my lectures or shed light on issues that I fail to. The course this year will be based on use of the version of Java sometimes known as “Java 5.0” and sometimes as “Java 1.5”. This should now be counted as the current and widely-used version, but if you use computers other than the main university ones here you may come across earlier releases. Please avoid them for course-related work to avoid confusion. Some members of the audience for this course will already have significant practical experience with Java. Others will have written lots of programs before but in C, C++ or Pascal, but the only thing I can properly assume here is that everybody has attended the Foundations of Computer Science course given in the Michaelmas term and hence that everybody is used to writing code in the language ML. While those who have seen Java before will undoubtedly find the first few lectures and exercises here very easy, I hope that they will find material that is new and worth-while being introduced in due course. In the first year that this course was given it was observed by one Director of Studies at a large College that some of his students who did already know Java concluded on that basis that they need not attend the lectures, but that their examination results indicated that this had not been a perfect judgement call. 3.1. INTRODUCTION Figure 3.1: Reproduced courtesy Kevin McCurley. 17 18 CHAPTER 3. INTRODUCTION 3.1.1 Books All bookshops these days seem to devote many metres of shelf-space to books that purport to teach you Java in a given small number of days, to help you even if you are an “idiot”, or to provide “comprehensive and detailed” coverage of even those parts of Java that the language definers have left deliberately vague. I believe that this is a course where it is important for every student to have their own copy of a supporting textbook/manual. But the issue of which book to buy will end up a somewhat personal choice since differing levels of detail will suit different students! Browse the following in libraries and bookshops, talk to students in higher years and seek advice from your Directors of Studies and Supervisors about what is liable to suit you. My first recommendation has as most of its pages what is in effect hard copy of the on-line detailed documentation of the Java library. As a result it is not a smooth consistent read, but I find that very many people need that information in printed form while they are getting used to navigating the library and understanding what it can do for them. Java in a Nutshell fifth edition (Feb 2005) Java Foundation Classes in a Nutshell (1999) David Flanagan O’Reilly Note that the fifth edition is due to be published as your course begins! There are two books[11, 6] that I think you might reasonably consider and that are probably easier for self study in that they do not get so rapidly enmeshed in full detail. Thinking in Java Bruce Eckel Prentice-Hall, 2002 third edition and Java Gently Judy Bishop Addison Wesley, 2003, third edition Eckel’s book is distributed (at no cost) via www.eckelobjects.com so if you are actually prefer reading computer screens to real books it counts as a great bargain! Some Directors of Studies will strongly point you towards the book[2] that was that main text for this course a couple of years ago: Objects First with Java: a Practical Introduction using BLUEJ David Barnes and Michael Kölling Prentice Hall/Pearson, 2005, second edition 3.1. INTRODUCTION Figure 3.2: Not (quite) the main course book. 19 20 CHAPTER 3. INTRODUCTION This book emphasises issues of overall program structure and design above concern for the exact details of the Java language or its libraries and so is almost exactly the antithesis of the Nutshell books! It was used as the main teaching text here in 2003-4, and you may find the BlueJ software (http://www.bluej.org) provides a useful environment within which to develop and test your code. Note that this year’s edition of both the book and the software has developed from the versions available last year. There will be plenty of other useful books, and any individual student may find a different one especially to their own taste. If selecting a book other than the one I suggest that you make sure that what you learn from it can be related to the lectures that I give. Since this year we are using a rather new version of Java beware that old editions of books may not be sufficiently up to date. Java is a “buzzword-compliant” language, and when people hear that you are learning it they will instantly pick up all sorts of expectations. Even though this course is sixteen lectures long I will not be able to fulfil all of these, and that is in part why the Computer Science Tripos has a course entitled “Concurrent Systems and Applications” in Part IB that follows on from this one. There are three issues that I should mention right here at the start of the notes, if only to protect myself and the department against misunderstandings as to our purpose: Java is for use animating Web pages: Some of the huge first flush of enthusiasm that greeted the emergence of Java was because it could be used to make rather naff animated figures dance on web pages. This was of course amazing when web pages had previously been so rigidly static, but it is not a good model for the central issues in Computer Science. This will typically not be the sort of use of Java that we try to teach you here; Java is the best programming language: The Computer Laboratory shows by its actions that it views ML as its preference for a first language to teach its students, with Java as a second one. Later on in the course we will provide coverage ranging from brief mention to detailed explanations of quite a few other languages: certainly C, C++, Lisp and Prolog. The Software engineering courses mention a scheme called just ‘Z’ that is in effect a programming language, and you will see from past examination papers that we have high regard for Modula 3. What is shown by that is that the Computer Laboratory view is that different languages may prove best for different tasks, and that the optimal choice will change as the years go by (it happens that we no longer teach our students either Fortran or COBOL, and our coverage of assembly code is present 3.2. PRACTICAL WORK 21 because it forms an important link between the concerns of hardware designers, operating system experts and compiler writers, and not because we expect students to do project work in it). At present Java is our choice for the first “traditional”-style programming language we teach: this does not mean it will automatically be the only or best choice for all future practical work and projects; Students should be taught about “programming in the large”: As this is a first year course I will be concentrating on the fundamental building blocks of program construction. This is in line with the Engineering Council “EA1” concern about introducing students to the fundamental tools, materials and techniques in their subject. I view it is self-evident that until a student can write small programs competently and painlessly it would not make sense to expect them to be able to work in groups on large projects. However in all the practical work associated with this course you should expect the assessors to demand that all code you write is well laid out, properly commented, that it displays a sensible programming style and that you are in a position to justify its correctness. In short that a generally professional approach has been taken even though many of the exercises are short and somewhat jokey toy problems. 3.2 Practical work The main environment the laboratory expects you to use for this course is PWF Linux. At the start of Term you should be given an introduction that explains how to re-boot certainly the PWF systems in Cockroft 4 or in the Intel Laboratory in the Gates Building so they run Linux. PWF workstations in other parts of the University may not have been configured with this dual-boot option, but if they have then you can use them. Although Java runs perfectly happily on Windows we want you to do much of your practical work on Linux so that by the time you come to the Operating System course later in the year you have made significant personal use of both Windows and Linux. At least for the first half of the Term we would also encourage you to use the emacs editor and build and run your Java programs using the somewhat primitive command-line driven tools javac, java and appletviewer. Use of these will be explained later. The reasoning behind this is not that it guarantees to make your Java-specific experience as comfortable as possible, but because the technologies involved are ones you need to find out about at some stage! Specifically I note that • emacs is a rich and powerful editor. You can use it in a simple way while CHAPTER 3. INTRODUCTION 22 you are beginning work, but it has extension mechanisms that allow it to morph to provide specialist support for different sorts of document, and it can provide a single environment (and set of keystrokes to learn) that covers not just editing your program but also compiling and running it, reading and sending e-mail and many other tasks. It probably counts as the most widely used general-purpose Unix/Linux editor and versions for Windows are also available. Your really simple use of it now will help those of you who choose to use if in more elaborate ways later on. • The use of the javac and java commands explicitly (as distinct from you using them implicitly through an all-encompassing specialist Java development environment) means that when you see any curious messages or complaints you know where they come from. It also introduces you to a typical model for how software is built (the edit, compile, test cycle). When you are more experienced you will no doubt move on and use integrated environments1. In some respects these help by doing things for you – but especially since you have survived the Foundations of Computer Science course last Term it now seems proper that you get to see how to do things for yourself. For reference material it may prove most convenient to use on-line documentation, and in particular the web-browsable HTML version. This is available to you in $CLTEACH/acn1/java/docs, so you can launch a browser and start looking at it by going firefox $CLTEACH/acn1/java/docs/index.html & and around the first thing you may want to do is to set yourself a bookmark on that page. There is a huge amount of documentation there. The bits I find most useful are the “Java 2 Platform API Specification” which documents (in painful detail) all of the library facilities that are provided, and the “Java Tutorial” which links to a Sun website with much helpful explanation, and which you may find a very good complement to the textbooks I have suggested. All the time I am writing any Java code at all I will have a web-browser open on the “API” section of the documentation, since it is useful to have a quick way to check details of the library very close at hand. You can obviously run PWF Linux in one of the big shared workstation areas, and there is a great deal to be said for at least starting off that way: you can compare notes with other students when you have problems. But you can also access 1 Microsoft’s Visual Studio is perhaps a definitive example: for Java you can install either Netbeans (from Sun) or Eclipse (from IBM) free of charge. BlueJ has very different objectives but may also prove useful to some. 3.2. PRACTICAL WORK Figure 3.3: Remember about RSI, posture etc, please. 23 CHAPTER 3. INTRODUCTION 24 PWF by using ssh and an “X-windows server” to access of of the lab’s PWF linux systems that are set up for remote use, eg linux2.pwf.cl.cam.ac.uk or linux.pwf.cam.ac.uk. If your own computer is set up to run Linux those will already be present for you. If you run Windows you can get good versions free of charge by installing a Unix-compatibility layer from http://www.cygwin.com, but getting everything to work nicely there may be messy enough that those of a nervous disposition would do better to work in Cockroft 4 or one of the College computer rooms where PWF Linux is directly available! It is also perfectly in order for you to install Java on your own computer. Apart from the fact that the Java development kit uses around 450 Mbytes installing it should not prove hard, it does not cost anything and performance should work well under either Windows, Linux or MacOS on any even reasonably recent pc. If you do that you must be willing to take full responsibility for installing and maintaining everything, and should take care to back up all important files. For just running small Java exercises there should not be much difference in the experience you have using your own rather than a public machine2, however if you habitually use a PWF system somewhere other than in Cockroft 4 or the Intel laboratory your own system might reduce your need to wait while you re-boot a public machine into Linux, and if you experiment with one of the integrated Java environments you nay find performance much better on your own system. If you have a Macintosh note that Java 1.5 has only very recently become available, so please double-check that that is the version you have. To fetch a Java compiler you will need to connect to http://java.sun.com/j2se where you can find the Java “SDK Standard Edition, version 5.0”, and its accompanying documentation. You should be aware that the package you have to download is around 50 Mbytes for the main kit, with the documentation being an additional large download and the “Netbeans” development environment yet more that you may want to explore but are not obliged to worry about. Sun can supply either Windows (2000/XP) or Linux versions of all of these. The Eclipse development environment can be found at http://www.eclipse.org. 3.2.1 Exercises Tickable Exercise 1 The first part of this tickable exercise is issued as part of the introduction to the use of Linux on the PWF. The task set here is thus “Part B” of the complete Tick. 2 Great thanks are due to the Computing Service for ensuring that this is the case. 3.2. PRACTICAL WORK 25 Log on to the PWF. Create a new directory and select it as your current one, eg mkdir Tick1B cd Tick1B Issue the following commands that copy two files form the Computer Lab’s teaching filespace into your new directory. cp $CLTEACH/acn1/TickBase.class . cp $CLTEACH/acn1/TickBase1.class . You should be able to check that the files are present. These two files provide a basis upon which the exercise builds. Now inspect Figure 3.4 which is documentation associated with the two files that you have just copied. A more extensive version of the same material is available on-line as www.cl.cam.ac.uk/Teaching/current/ProgJava/notes/TickDoc/ Now prepare a file that called Tick1.java containing the text // Tick 1. Your Name Goes Here public class Tick1 extends TickBase { public static void main(String []args) { (new Tick1()).setVisible(true); } public String myName() { return "Your Name"; } } Obviously you will put your own name in the places that are suggested by what I have written here! Compile your program and then run it: javac Tick1.java java Tick1 26 CHAPTER 3. INTRODUCTION Figure 3.4: Documentation of Tick 1 Part B. 3.2. PRACTICAL WORK 27 If all has gone well a window should appear, and it should have some text and a pattern on it. There is a menu that you can select. If you copy the files to your own machine you can try the print menu, but on the PWF there are technical reasons why that is not supported, and these lie outside just Java. So select the menu item labelled postscript. You should then see a dialog box asking you to choose a file name. I suggest that you select the name tick1.ps and I very strongly suggest that you use the extension .ps whatever name you actually choose. When you accept the file-name you have chosen the “select file” dialog box disappears and you can not see that anything much has happened, but the file you indicated should have been created for you. It should contain an image of the screen window in the Postscript document format. Close the little Java window, and you can send this to a printer using the command lpr tick1.ps The resulting sheet of paper is what goes to your ticker. As an optional extra you can arrange to change the colour of (some of) the text generated by adding lines roughly like the following to your Java source file: public java.awt.Color myColour() { // RED GREEN BLUE return new java.awt.Color(0.7f, 0.1f, 1.0f); } where the three floating point numbers given (note that you have to write a letter ‘f’ at their end) should each be in the range 0.0 to 1.0 and they give the proportions or red, green and blue in the colour. You can also check what happens if you present your name in different ways. For instance I tried “A C Norman” as well as “Arthur Norman”. If you wanted to keep your program in a file called say MyTick.java rather than Tick1.java you would have to change its name within the file too. Verify that you can do that. Discussion This exercise is intended to send several signals and messages about Java: • One can build new programs building on existing components that do quite a lot for you. Here you copied in the TickBase class files, but your own program then builds on them and can customise the behaviour of the provided code in various ways. Through doing this a very short fragment of code let you create a window and print its contents; CHAPTER 3. INTRODUCTION 28 • To use the software component TickBase you do not need to see its internal structure: all you need is documentation about how to use it. As part of stressing this I am not going to provide you with the source code of TickBase, but by the end of this course you will probably be able to re-create it; • Part of the way of using components like this involves the Java keyword extends, and part of the way that the code runs involves the keyword new. These are both key parts of the Object Oriented structure of Java, and you should look forward to finding out more about just what they really mean later on. • The page of documentation included as part of these notes tells you that TickBase is interested in a myName(). This documentation is in the style of the bulk of the Java on-line documentation, and was created by using a simple tool called javadoc that interpreted some special comments in the TickBase source code. However the full output from javadoc is on the web page listed a little earlier and perhaps gives a bit more of an idea of just how much complexity is involved under the surface. The lesson that I learn is that if you use javadoc for anything other than a full-scale project you will need to edit its output heavily to remove material that your audience does not really need to see. (End of tickable exercise) 3.3 A Cook-book Kick-start In this section I will try to get you started with Java. This means that all sorts of aspects of it will be described in an order that is not really logical, but is motivated by that fact that some features of the language must be described early if you are to get any programs at all written. I will not provide much justification for the recipes that I give. Later on it will be possible to re-visit these examples and understand what the various odd keywords are all saying and what options might be available, but for now you can just copy them out parrot fashion. I would like you to type in all the examples for yourselves and try them out, since that will educate your fingers into following the rules that Java imposes, and it will also (each time your fingers stray) give you exposure to Java error messages and the joys of finding and fixing mistakes. My first example in fact is an echo of the first part of Java Tick 1. A mildly silly tradition in teaching programming languages is that the first program presented should just print out “hello”. The way of doing this in Java looks like this: 3.3. A COOK-BOOK KICK-START 29 System.out.printf("Hello"); which is a call to a library function called printf3 that will display the given string. The prefix “System.out” is not part of the name of the function — it happens to be providing the instruction that that printed output should be sent to the standard output stream, ie typically straight to your screen or terminal. In essential terms the line shown above is the whole important part of your first Java program. However there is actually quite a lot more to be discussed before you can try it! The first thing is that Java is a compiled programming language, so unlike the situation you have seen in ML it is essential to place your program in a file before it can be used. In this case you should use a file called Hello.java and it is essential that the file name should start with the word Hello since that is the name that we will soon repeat within the file. The spelling should be with a capital letter as shown4 , and the file-name should be completed with the suffix .java. If you start emacs and use the menu selection “Files/Open File” you get a chance to create a new file for this project, and if you may5 notice that when you type in the string in the example it is displayed in an alternate colour (to help remind you to match your quote marks), and when you type the close parenthesis after the string the matching bracket gets flashed to help you keep that side of things under control. It is possible to make very extensive customisations of emacs. If you put a file called .emacs in your home directory it can contain directives that apply whenever emacs starts. In particular if you put a line (global-font-lock-mode t) then you will get syntax colouring enabled every time: I find this convenient. For now I suggest that you avoid putting large amounts of other clever stuff there! You will also see that the menu-bar at the top of the emacs window has entries that let you do all the things that editors ought to — and more besides. See figures 3.5 and 3.7: note that the printed form of my notes will be in black and white but the downloadable version on the lab’s web page http://www.cl.cam.ac.uk/Teaching/2005/ProgJava will show relevant information in colour. Also note that the sample programs being edited and tested in the pictures of emacs in use may be ones taken from previous years’ versions of this course. 3 Many Java texts use a function println here rather than printf. actually if you are working on a Windows system the capitalisation is not so important, but even there you are strongly advised to keep to it so that when you transfer your programs back to Unix before showing them to the assessors they still work! 5 Provided the “global font lock” options is selected. 4 Well CHAPTER 3. INTRODUCTION 30 A “Java mode” is automatically selected when you edit a file whose name ends in .java and this is the first pay-off you see from this convention. If you select a “global font lock” this can colour your code so that language keywords, strings, comments and so on are all displayed in different colours6. It also assists with indentation and provides editing commands that move around in the file in a way that understands Java syntax. A major feature of emacs is that it is amazingly customisable, and configuration files can provide it with special support for many languages and layout conventions. If you browse enough sites on the web you may find many extra options that you can install: hold back and avoid these until you have got really used to the default setup! Please! Figure 3.5: Two windows, with emacs editing a program. Your complete first Java program needs a great pile of guff that surrounds the one interesting line we have and turns it into something that can be executed. In essence two things need to be documented. The first is something that indicates the external name that the program will be known by. This will always be exactly the same as the start of the name of the file it is stored in. You may consider it silly to have to re-state information that looks as if it should already be available, but for now please suspend disbelief and accept that a program that lives in a file called Hello.java will have to contain the text 6 At a minimum this can be very helpful if you accidentally fail to close a string or comment! 3.3. A COOK-BOOK KICK-START 31 public class Hello { ... } where the ... will be filled in soon with material that includes our call to System.out.println. The second piece of information needed is an indication of where Java should start its processing, and the convention that the language imposes here is that it expects to find a procedure7 with the name main. The definition of a suitable procedure then involves the incantation public static void main(String[] args) { ... } of which the only word that is currently worth describing is “main”, which is a reminder of the historical tendency to refer to the place where a program started as being the “main program” while what are now known as functions or procedures might have been called “sub-programs”. Comments can be introduced by “//” and every good program starts with a note that explains a few key facts about it. Obviously the longer the program the more that it will be proper to put in comments at both the start and throughout your code, but note that assessors will certainly expect your name and the exercise identification to be at the head of every submission you make. Putting this all together we get the full contents of the file Hello.java as // This is the file "Hello.java" prepared by A C Norman // and the program just prints a fixed message. 1998-2006. public class Hello { public static void main(String[] args) { System.out.printf("Hello%n"); } } 7 In these notes I will use the terms “function”, “procedure” and “method” pretty-well interchangeably. Some other languages use these words to indicate refined differences — typically the term “procedure” would be something that did not return a value, while a “function” would. The word “method” comes out of ideas of so-called Object Oriented Programming and indicated a function that is defined within a “class”. Although I have not yet explained what a class is we have seen the keyword class towards the head of our Java programs. 32 CHAPTER 3. INTRODUCTION Figure 3.6: Style does matter. 3.3. A COOK-BOOK KICK-START 33 There is a yet further odd addition in what I have just shown. The %n arranges to put a newline at the end of your message. For a very short program that hardly does anything interesting that seems to have a lot of “magic” keywords. But in only a few weeks time you will know what they all mean, and why they make sense. For now just keep a file that contains the above basic sample code and copy it every time you want to start a new program so you do not have to waste time keying in all the junk repeatedly! 3.3.1 Code Layout Many people have quite strong views about code layout and indentation. That includes me! The style you will see in these notes almost always places any “}” vertically below the “{” that it matches. I try to indent anything that is within such braces by four space positions. Beyond that my guiding principle is to try to keep my code so that it looks pretty on the screen or page, is efficient in its use of the page and is as easy to navigate over as I can manage. Saving keyboard effort is not a high priority, since actually typing in programs is such a very small part of the total pain that goes into getting a complete and robust working program. The default emacs idea about indentation and brace layout is differs from mine: whichever you choose to follow please be consistent and try to make your code easy for yourself and others to read. The comment above about efficiency in the use of the page is because when reading your code it is especially convenient if all the bits you want to see fit within one screen-full of the editor’s window. Thus I count excessive splitting of constructs over multiple lines as unhelpful, just as are large swathes of blank lines. I prefer comments in blocks (which may often make up significant paragraphs) that describe the code that follows them. And the comments should be readable English in proper sentences intended to help some poor person faced with revising or updating the code to correct some imaginary bug or add a new feature. Java provides some encouragement for special comments that are introduced with the sequence “/**”8 and going on over possibly many lines until the next “*/”. These are there to support extra software tools that extract those comments and format them as separate documentation for the program. In this course I will illustrate that scheme later on. Well all the above discussion has just left us with a file Hello.java. Unlike (typical teaching use of) ML, Java expects programs to be processed by a separate compiler before they are executed. This compiler is a program that checks the syntax and types of the code you show to it, and translates from the human8 Ordinarily as well as “//” comments that just run to the end of the line you can write long comments starting with “/*”. CHAPTER 3. INTRODUCTION 34 readable9 source file such as Hello.java into a more compact10 digested binary file (called Hello.class in this case) that can subsequently be executed repeatedly without any need to re-do all the potentially time-consuming checking. To carry out this conversion you need to say javac Hello.java The javac command tends to be jolly taciturn unless it finds something in your program that offends it. It does not say anything and so after it has run you may like to use ls to verify that the file Hello.class has been created. Finally we can run it: java Hello Note that when javac was used to compile the program it was essential to quote the .java extension, while when the program was to be run you must not use the .class extension that the pre-digested version of the program was given. This level of apparent inconsistency is not at all restricted to Java, and the exact rules on matters such as this are liable to differ between different vendor’s sets of Java tools. What I describe here relates just to Sun’s SDK! 3.3.2 Emacs The editor emacs is the preferred text editor to use while taking this course. I think it may be best for most people to start by keeping two windows available on their screens, one the emacs edit window and the second a command-prompt from which they can issue the build and run commands directly. When working with an edit and a command window note that you have to go “File/Save Buffer”11 to get emacs to ensure that the file on disc is brought up to date with respect to the version you have been editing in its buffer. Provided you do this before issuing the javac command from your other window it is reasonable and most convenient to keep emacs loaded throughout your session. It is also possible to compile and run Java (or other) programs while remaining entirely within emacs, and to get any reports of syntax errors generated by a compiler to re-position the editor’s caret close to where the error was detected. But for the rather small programs you will be working with during this Part IA course all is excessive and using one window to edit and one to compile as in Figure 3.5 remains simplest. The next program to be shown is a rather simple extension of the one we have already discussed, but instead of just printing a fixed message it prints a table of squares. In a file called Squares.java you should place: 9 Well, at least it is readable if you include enough comments! for really tiny programs like this one the binary file may be bigger than the source it relates to, but for and program big enough to be interesting what I say will hold true. 11 Or the equivalent keyboard sequence, Ctrl-x Ctrl-s. 10 Actually 3.3. A COOK-BOOK KICK-START 35 public class Squares { public static void main(String[] args) { for (int i=0; i<10; i++) { System.out.printf("The square of %d is %d%n", i, i*i); } } } There are two new things here. The first is the iteration statement for (int i=0; i<10; i++) { ... } which arranges to declare a variable called i and set it first to 0, then to 1, then 2 and so on for so long as i<10 remains true. The curious syntax i++ is inherited from the C programming language and means “increment i”: a less cryptic way of achieving the same effect would be to write “i=i+1” instead. The single = sign in Java is an assignment operator and changes the value of the variable named on its left. The word int is short for “integer” and specifies the type that i should have. Type Java type int denotes integers which are explicitly limited to a range that is consistent with representation as 32-bit values. Unlike ML Java expects you to specify the type of pretty well everything you mention, and when you introduce a new variable you can change its value later using a = operation without having to worry about any special extra works like ref. The second new feature is the string argument to printf where emdedded percent signs stand for where the numeric values you want displayed need to be substituted in. The %d indicates that what you want displayed is expected to be an integer: other letters could be used when you were needing to print other sorts of item. Once again I need to make a remark this year that is to do with the transition to Java 1.5: in previous years and in many books you will see this code written as System.out.println("The square of " + i + " is " + (i*i)); where the plus signs in fact indicate string concatenation and Java is converting integers to printable form fairly automatically. I prefer the use of printf because the %d indicates very explicitly that I am about to print an integer (not some other sort of thing). It can also be extended to give me quite refined control over the layout of the table I generate. Note that when I came to want to type in the Squares program to check it I did not type it in from scratch. Instead a copied the earlier Hello program and adjusted the few lines in its middle to perform the new operations. Typically it will also be necessary to change a few comments to make them relate to the new reality, but creating new code by making incremental extensions to old is a very useful CHAPTER 3. INTRODUCTION 36 technique and can save a lot of time and effort. It also means that remembering all those boring bits is at least slightly less necessary. One further development of the Squares example will illustrate a few more Java idioms. This code (which I will put in a file Powers.java) computes powers and does so by a repeated-squaring technique that may be familiar from the MLbased course last term: public class Powers { public static void main(String[] args) { // I will use println for simple fixed text System.out.println("Table of powers"); for (int i=0; i<10; i++) // .. and printf to incorporate values within a template { System.out.printf("%dˆ%d = %d%n", i, i, power(i, i)); } } static int power(int x, int n) { if (n == 0) return 1; int y = power(x, n/2); if ((n % 2) != 0) return x*y*y; else return y*y; } } which produces the results Table 0ˆ0 = 1ˆ1 = 2ˆ2 = 3ˆ3 = 4ˆ4 = 5ˆ5 = 6ˆ6 = 7ˆ7 = 8ˆ8 = 9ˆ9 = of powers 1 1 4 27 256 3125 46656 823543 16777216 387420489 The new features shown here are the definition of a function and calls to it. Observe that the types of the arguments for the function and the type of its result are all explicitly given (as int here). The code does distinctly more arithmetic, 3.3. A COOK-BOOK KICK-START 37 where +, -, * and / stand for addition, subtraction, multiplication and division. The percent sign % gives a remainder. Numeric comparisons are written with >, <, >= and <= for the obvious comparisons, and the rather less obvious == for an equality test and != for inequality. Conditional statements appear as if (condition) statement or if (condition) statement else statement Note that the parentheses around the condition are part of the Java syntax (inherited from C) and they may not be omitted. You need to use the word return explicitly to indicate what value your procedure should hand back. It is a very common beginner’s error to get mixed up about where braces and semicolons are needed — and mix-ups on this front can cause special trouble with the else after an if statement. In doubt just remember that you can group several statements (or indeed just one) together to make a single big statement just by enclosing them (or it) in braces “{ . . . }”. The braces I have around the call to printf just after the for were put in not because they are essential (the call to printf counts as a single statement and could be the thing that the for loop performed in a repetitive way) but because I think the braces there make it easier to see just what the range of the for is. Similarly it is often good style to use braces that are in some sense redundant after the keyword if just to ensure that the structure of your code is utterly evident to any reader. The series of small examples above show enough of Java that they can form the basis for exercises that use integer arithmetic and a few recursive sub-functions. With luck they contain enough examples of usage that you can now go away and write all sorts of little programs that perform calculations with at most minor recourse to the textbook to check exact details. 3.3.3 Drawing to a window: JApplets I will therefore move onto another cook-book example which shows a different sort of Java program. The ones seen so far are refereed to as stand-alone applications. The next one will be described as an “applet”. It has an even higher load of mumbo-jumbo to surround the small bits that are its essential core, but illustrates how you can start to use Java for graphics programming and to interact with windows, mice and the like. As with my Hello program I will start by quoting 38 CHAPTER 3. INTRODUCTION Figure 3.7: emacs on Windows, with the “Global Font Lock” option for syntax colouring. the important bit of the code that lives in the middle. In this case it will arrange to keep track of where your mouse last was when you pressed its button, and will respond to new mouse clicks by drawing a straight line on the screen to join the old to new position. Since at this stage I want to make this key part of the code look as short and easy as possible I have omitted any comments — after all I am about to give an explanation here in the accompanying text! 3.3. A COOK-BOOK KICK-START 39 In the Hello program we used a function called printf by referencing it relative to some library object System.out. Here we need to suspend disbelief for a short while and image two things, one called e that allows us to call library functions that reveal the position of the mouse (getX and getY) and another called g that is analogous to System.out but which supports a function drawLine for putting a straight line up on the screen. Suppose furthermore that there are integer variables lastX and lastY that will be used to store the previous position where the mouse was clicked. It now makes sense to show the kernel of the drawing program: int x = e.getX(), y = e.getY(); g.drawLine(lastX, lastY, x, y); lastX = x; lastY = y; Look under the link Java Platform Core API on the web-browsable documentation. Clicking at the top of the screen through Index makes it almost as quick to look up getX, getY and drawLine as it would be to check for them in the index of a book. In either case you are liable to find near their documentation the explanation of other related functions, such as drawRect, drawOval, fillArc, drawString and many many more. Once one has sorted out how to use one of these in general the rest follow on naturally, so it can be useful to browse the documentation occasionally to make yourself familiar with the collection of operations that are supported. The next natural question is one of where the mysterious e and g came from, and how it could be arranged that the above code is activated every time the mouse button is pressed. Well just as a simple stand-alone application has a special function called main, one that deals with the mouse will have one called mousePressed. This gets the object e passed down to it from the system. all one needs to know is that the type used to declare this variable is MouseEvent. Access to the screen is obtained by declaring g to be of type Graphics and initialising it with the value returned by a call to getGraphics12 . These types and conventions are to some extent part of a large design that underlies the Java libraries, but at this stage the only proper way to cope with them is to copy them carefully from existing working programs and check details in the documentation. When you look at the documentation I expect your main initial response to be one close to “Wow” as you see just how many types and functions Java provides you with. Overall there is more complexity and power in these libraries than there is in the language itself. Anyway here is the full version of the mouse click handler function — not too messy provided one is happy to take the library calls on trust! 12 In this initial example I use getGraphics but often the object you want will come to you in other ways. 40 CHAPTER 3. INTRODUCTION public void mousePressed(MouseEvent e) { // I have to obtain access to a drawing context Graphics g = getGraphics(); // I also need to extract (x,y) co-ordinates from // the mouse event. int x = e.getX(), y = e.getY(); g.drawLine(lastX, lastY, x, y); lastX = x; lastY = y; } I can now give the whole file Draw.java which includes the above important function definition, but which also has the relevant junk that is needed to connect it in to the Java run-time environment. You will see that I have this time used comments from /* to */ for some of the big block comments. The arrangement with columns of vertical stars is purely a convention that I like and which makes the range of the comment clearly visible. The lines starting import arrange for convenient access to several extra Java libraries. You will find import statements at the top of most of my sample programs from now on and the exact list of things you need to “import” will seem jolly mysterious. All I can say at this stage is that you can start by copying the lines I give and that in a week or so you will understand how to check the Java on-line documentation to sort out exactly what you need exactly when. The qualifications (extends and implements) on the declaration of the Draw class ensure that this program can draw to the screen and respond to the mouse. When a file contains a class that extends JApplet the rules for it starting up are not like ordinary programs. Instead of defining main it defines the functions shown here. /* A C Norman * Draw.java * Simple applet to draw lines on a screen * in response to mouse clicks. See also "Draw.html". * / * /* * At the start of almost any Java program it will * be necessary to incant a few "import" statements to * provide Java with more convenient access to various * standard libraries. */ 3.3. A COOK-BOOK KICK-START import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Draw extends JApplet implements MouseListener { private int lastY = 0, lastY = 0; public void init() { // I need to activate the mouse event handlers. this.addMouseListener(this); } /* * Each time the mouse button is pressed I will draw a * line on the screen from the previous mouse position * (or (0,0) at the start) to where the mouse now is. */ public void mousePressed(MouseEvent e) { // I have to obtain access to a drawing context Graphics g = getGraphics(); // I also need to extract (x,y) co-ordinates // from the mouse event. int x = e.getX(), y = e.getY(); g.drawLine(lastX, lastY, x, y); lastX = x; lastY = y; } /* * The full mouse event model uses the four extra * procedures shown below. To keep this code as short * and simple as I can I will not cause them to do * anything, but the Java event handler scheme demands * that they exist. Hence these definitions of functions * that do nothing at all! */ public void mouseReleased(MouseEvent e) {} public void mouseClicked(MouseEvent e) {} 41 CHAPTER 3. INTRODUCTION 42 public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} } /* end of Draw.java */ Without a prototype such as the above to start from it could take a huge amount of reading of the manuals to find all of the Java types and functions to put together. However once you have the prototype to work from there is at least some chance that variations on the theme can be constructed by making incremental changes, and the details of these changes can be sorted out by looking in the manuals close to the place where the features that are currently used get documented. The version I have given here uses the class JApplet while you may find some books use Applet (without the initial letter J) that is an older version of something similar. 3.3.4 HTML and appletviewer The earlier examples were run using commands such as java Hello. This one is not a stand-alone application but a JApplet, and so has to be run using a thing called appletviewer. What is more it needs yet another file to be prepared: one that will let it know how large an area of the screen should be set aside for the drawing to appear in. This new file must be called Draw.html, and its contents are as follows: <HTML> <BODY> <APPLET code="Draw.class" width=400 height=400> Java is not available. </APPLET> </BODY> </HTML> This file consists of a set of nested sections, where the start of a section is a word contained in angle brackets and the corresponding end-marker is the same word but with “/” in front of it. Once again the most interesting part is in the middle where the APPLET tag is used to provide a reference to the compiled version of our program (ie Draw.class) and to specify the width and height of the window in which it is to work. The text “Java is not available” should never appear when you use this file! It is there so that it can be displayed as an error message if this HTML13 file is inspected using software that does not understand Java. For the purposes of this course the only use you will make of the file is to say 13 Hypertext Mark-Up Language. 3.3. A COOK-BOOK KICK-START 43 appletviewer Draw.html which should cause a 400 by 400 (pixel) window to appear within which you can click the mouse to good effect. The window that appletviewer should provide a pull-down menu that contains an entry quit or close than can be used to terminate the program. Observe (with quiet gloom) that appletviewer demands that you quote the .html suffix, and that inside the HTML file you have to specify the full name of your class file (ie including the .class suffix), while to run simple stand-alone Java applications you just gave the base part of the filename. Ah well! The Draw program shown here is a useful prototype, but the most glaring problem it exhibits is that if you rearrange your windows while using it so as to obscure part of what you have drawn then that bit does not get re-painted when you reveal the window again. When you move towards larger programs (ones spread over very many files) you will probably need to read up about a tool called jar and find out (it is easy!) how you can package many Java class files into a single archive, and how HTML files refer to such archives. I will not explain that in this Part IA course. 3.3.5 Exercises Tickable Exercise 2 Do both parts A and B please. Part A The following Java function is a rather crude one for finding a factor of a number n and returning it, or returning 1 if the number is prime. static int factorof(int n) { int factor = 1; for (int i=2; i*i<=n; i++) { if (n % i == 0) factor = i; } return factor; } The code works by checking each possible factor from 2 upwards, stopping when the trial factor exceeds the square root of the number being tested. This stopping condition is reasonable because if a number n is not prime √ than it must have at least one factor in the range 2 . . . n. CHAPTER 3. INTRODUCTION 44 Write a stand-alone Java program that incorporates the above code and that prints out a list of all the prime numbers from 2 to 100. Optional: Change factorof to make it more efficient by first letting it check whether 2, 3 or 5 is a factor and then instead of trying all possible factor up to the square root of n let it just try those that are 1, 7, 11, 13, 17, 19, 23 or 29 mod 30. Ie do not bother with numbers that are themselves divisible by 2, 3 or 5. This should lead to making just 8/30 = 26.66% of the number of test divisions that the original version did. Does the new version run faster, and if so by about what factor? Part B: Binomial Coefficients This exercise is a deliberate incitement to write a very inefficient program. Later on there will be an example that prompts you to write a much faster program that can computer the same answers! The binomial coefficients14 may be defined by the rules n Cr = n−1Cr +n−1 Cr−1 n C0 = 1 n Cn = 1 This definition could naturally turn into an ML function definition fun binom(n, r) = if r = 0 orelse r = n then 1 else binom(n-1, r-1) + binom(n-1, r); Write the corresponding Java code and use it to tabulate 2nCn for n from 0 to 12. If you are using Java on Unix you should go “time java Binom” to run the example and when your program has run you will get a report of how long the computation took. Keep all the output so you can compare both results and timing data with the method described later on. (End of tickable exercise) 14 There is some question as to whether I should will not confuse you too much! use the notation nCr here or n r . I hope this 3.3. A COOK-BOOK KICK-START 45 A better drawing program The drawing program as presented is very clumsy. There are a number of ways it could be improved. The suggestions made here are not the correct route towards a properly finished professional quality drawing program but may still count as useful practise with Java. 1. In the 400 by 100 window, interpret mouse clicks in the top 50 pixels as button activity that can select options. The x co-ordinate may be split into (say) 4 ranges to give four buttons. In mousePressed add: if (y < 50) { if (x < 100) .. action1 else if (x < 200) .. action2 else if (x < 300) .. action3 else .. action4 } else { normal mouse processing } 2. The crude buttons as above could select whether further regular mouse clicks drew lines (as before) or used drawOval to draw circles. Another button might select a drawing colour using g.setColor(Color.blue); // or red, black etc 3. My code, which was trying to be as short as possible, did not treat the first mouse click specially, and so all trails started at (0,0). That should be changed. 4. drawString(string, x, y) places text in a window at the given position. It could be used to label the “buttons”. I think that the code that you could potentially achieve here would be pretty good for this stage in the course! 46 CHAPTER 3. INTRODUCTION Turtle Graphics The following code shows some more new features of Java. It defines a paint method (ie function) in an applet. The appletviewer arranges that this function is invoked every time the applet’s window is uncovered or otherwise needs redrawing, and so it leads to pictures that are a lot more robust than the mouse-driven Draw program shown earlier. The code also uses a new type, double which is for floating point numbers, and some calls to the Maths library to compute sines and cosines. The odd notation (int)x indicates that the code wants to convert the floating point value x into an integer (int) so it is of the correct type to be passed on to drawLine. Put the code in a file Turtle.java and prepare a suitable associated file Turtle.html. Experiment with the code and see how the image changes depending on the values of the three variables marked. For most values of inc I seem to find that a closed figure is drawn provided N is large enough, but I have some trouble producing an explanation of why or a characterisation of exactly what values of inc will lead to this behaviour. I also find the degree of symmetry hard to explain. Generally this is an illustration of the fact that quite short programs can have behaviour that is complicated to explain! /* A C Norman * Turtle.java illustration of Turtle Graphics and the "paint" method. * / * import javax.swing.*; import java.awt.*; import static java.lang.Math.*; public class Turtle extends JApplet { public void paint(Graphics g) { // Try changing the following 3 numbers... double size = 5.0, inc = 11.0; int N = 5000; double x = 200.0, y=200.0, 3.3. A COOK-BOOK KICK-START 47 th1 = 0.0, th2 = 0.0, th3 = 0.0; for (int i=0; i<N; i++) { th3 = th3 + inc; th2 = th2 + th3; th1 = th1 + th2; double x1 = x+size*cos(PI*th1/180.0); double y1 = y+size*sin(PI*th1/180.0); g.drawLine((int)x, (int)y, (int)x1, (int)y1); x = x1; y = y1; } } } /* end of Turtle.java */ The code is really using angles in degrees (not in radians), and the variables th1, th2 and th3 hold values that are angles. As coded above some of these angles can grow to ridiculously large values, it might make sense to insert lines based on the prototype if (th2 >= 180.0) th2 = th2 - 360.0; in suitable places with a view to keeping all the angles that are used in the range −180.0 to +180.0. The import static java.lang.Math.* line makes it possible to use sin, cos and PI in the simple way shown. Note that in Java (and indeed with many window systems) the y co-ordinate starts at 0 at the top of the screen and increases as you go down. This makes sense (sort of) when the screen is containing text, in that counting lines you normally start at the top. For pictures it can be a little muddling until you are used to it, and can mean that things sometimes come out upside down the first time you try them. 48 CHAPTER 3. INTRODUCTION Chapter 4 Basic use of Java 4.1 Data types, constants and operations The first section of these notes introduced a few small but complete Java programs, but when you type them into the computer you still have to take a great deal on trust. But with those examples to use as a framework I can now start a more systematic introduction of the Java language and its associated libraries. Actually in the lectures I expect to skim over this material very rapidly: you will in reality learn about the Java data types and syntax as you write programs. However I view it as important that you have reference material in your handout that shows what everything is so that if you have trouble in your coding you have somewhere reasonably close at hand where you can check some details. However if you need a gentle path to settle into Java programming I do suggest that you try various of the example programs and exercises here so that you get used to and comfortable with a good range of Java syntax. 4.1.1 Reserved Words The first thing to do is to catalogue the words that are reserved: if you accidentally try to use one of these names as the name of one of your variables or functions you can expect most curious error messages from Java! So even though I do not want to explain what all of these mean yet it may help you if I provide a list of words to be avoided. In some cases of course the presence of a word here will alert you to the availability of some operation, and you can then look up the details in the manual. A clever editor might display words from this list in some alternative colour to help you notice any unintentional uses. An even more clever one might use different colours for the one (such as int) that name basic types, the ones such as for that introduce syntax and ones like true that are just the names of 49 50 CHAPTER 4. BASIC USE OF JAVA Figure 4.1: Start with some small examples. . . 4.1. DATA TYPES, CONSTANTS AND OPERATIONS 51 important built-in constants. abstract case continue enum float import native protected strictfp throw void assert catch default extends for instanceof new public super throws volatile boolean char do false goto int null return switch transient while break byte class const double else final finally if implements interface long package private short static synchronizedthis true try A joke about the above table of reserved words is that at present Java does not actually use them all — specifically const and goto do not represent any operation supported by Java. By prohibiting people from using these words as names for variables the Java designers have left themselves a little flexibility to extend the language in the future if they want to or are forced to, and they can perhaps give better error messages when people who are used to some other language that does include these keywords first tries out Java. There are some other words which are used for names that perform important system functions. If you unintentionally define a function with one of these names you might find that you have introduced side-effects of amazing magnitude when the system calls your function instead of the one it was expecting! Beware here, because although incorrect use of a genuine reserved word will result in a syntax error it could be that defining a function with one of the following names would have subtle or delayed bad consequences rather than a nice clean instant crash. clone notify equals notifyAll finalize toString getClass wait hashCode OK so the above information is more negative then positive, but I hope it will rescue a few of you from otherwise most mysterious behaviour when you might otherwise have tried to use one of the reserved words for your own purposes. 4.1.2 Basic Types Earlier examples used the word int to declare integer variables, and the range of values that can be represented goes from around −2 billion to around +2 billion. To be more precise the smallest valid int is −231 = −2147483648 and the largest one is 231 − 1 = 2147483647. You are not expected to remember the decimal form of the numbers, but you should be aware of roughly how big they are. Integer CHAPTER 4. BASIC USE OF JAVA 52 overflow is ignored: the result of an addition, subtraction or multiplication will always be just what you would get by representing the true result as a binary number and just keeping the lowest 32 bits. A way to see the consequences of this is to change the Powers program so it goes up to higher powers, say 20. The final section of the output I get is ... 8ˆ8 = 9ˆ9 = 10ˆ10 11ˆ11 12ˆ12 13ˆ13 14ˆ14 15ˆ15 16ˆ16 17ˆ17 18ˆ18 19ˆ19 16777216 387420489 = 1410065408 = 1843829075 = -251658240 = -1692154371 = -1282129920 = 1500973039 = 0 = 1681328401 = 457441280 = -306639989 where the value shown for 1010 is clearly wrong and where we subsequently get values that probably count as rubbish. Note both the fact that overflow can turn positive values into negative ones (and vice versa) and the special case (obvious in retrospect) where 1616 shows up as zero. Since 16 is 24 the binary representation of 1616 is clearly a 1 followed by a string of 64 zeros, and in particular the least significant 32 bits are all zero. This lack of detection of integer overflow is sometimes convenient but it is also a potential major source for getting wrong answers without even knowing it. Java provides several alternative integer-style primitive data-types which represent different trade-offs between expected speed, space and accuracy. They are: byte: 8-bit integers in the range −128 to +127; short: 16-bit integer, range −215 = −32768 to 215 − 1 = 32767; int: 32-bit integers as discussed already; long: 64-bit integers, is range is from −263 to 263 − 1 which means that almost all numbers with up to 19 decimal digits can be represented. It may be helpful to those who are not already used to the binary representation of signed values if I tabulate the representation used for the byte datatype. The wider integral types use just the natural generalisation: 4.1. DATA TYPES, CONSTANTS AND OPERATIONS Number Representation in binary −27 26 25 24 23 22 21 127 0 1 1 1 1 1 1 126 0 1 1 1 1 1 1 ... 3 0 0 0 0 0 0 1 2 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 −1 1 1 1 1 1 1 1 −2 1 1 1 1 1 1 1 −3 1 1 1 1 1 1 0 −4 1 1 1 1 1 1 0 ... −126 1 0 0 0 0 0 1 −127 1 0 0 0 0 0 0 −128 1 0 0 0 0 0 0 53 20 1 0 1 0 1 0 1 0 1 0 0 1 0 One way to understand how the negative numbers arose is to see that −1 is the bit-pattern that has the property that if you add 1 to it and ignore the final carry you get the representation that means 0. It can also help to suppose that negative number “really’ have an infinite string of 1 bits glued onto their left hand end. The representation used is known as “two’s complement”. When you write an integer literal in your Java code you can write it in decimal, octal or hexadecimal (base-16). You can also make your written integer either of type int or type long; there is no direct way to write either a byte or short. Decimal numbers are written in the utterly ordinary way you would expect. You add a suffix “L” if you want to make the value a long and you should always do this if the value is outside the range of ordinary available to an int, but you might sometimes like to do it for even small values when using them in a context where arithmetic on them should be done in long precision. Examples are: 12345 1234567890123L 10L 1000000L*1000000L 1000000*1000000 an ordinary int a long value a long but with a smallish value an expression where the L suffix matters without the L this would overflow. My belief is that hardly anybody ever wants to write a number in octal these days1 , but Java allows it, taking any number that starts with 0 as being in octal. 1 But this may be just a matter of fashion, and perhaps elsewhere in the world octal is still appreciated. CHAPTER 4. BASIC USE OF JAVA 54 Thus 037 is the octal way of writing the number 31. The L suffix can be used to specify long octal values. Observe a slight jollity. If you write the number 0 it is interpreted as being in octal. Fortunately zero is zero whatever radix is used to write it! Hexadecimal is much more useful. Each hexadecimal digit stands for four bits in the number. Letters from A to F are used to stand for the digits with weight 10 . . .15. Hexadecimal numbers are written with the prefix 0X. Note that the suffix L for long, the X in hexadecimal numbers and the extended digits from A to F can all be written in either upper or lower case. I strongly recommend use of upper case for L since otherwise it is painfully easy for a casual reader to muddle 10l (long ten) and 101 (one hundred and one). Here are some numbers written in hexadecimal 0X0 0xe 0xffffffff 0xBadFace 0x7fffffff 0x80000000 0x00010000 0x7fffffffffffffffL this is zero, not gravy powder otherwise 14 -1 as an int what other words can you spell? largest int most negative int 2 to the power 16 largest long I rather suspect that the main importance of byte and short is for when you have huge blocks of them2 where the fact that they take up less space can be of practical value. Floating point values also come in two flavours, one with a larger range and precision than the other. The more restricted one is called float. A float uses 32-bits of memory and can represent values up to about 3.4e383 with a precision of six to seven significant figures. Until you have sat through the course on numerical analysis please avoid use of it4 . The more sensible floating point type is called double and uses 64 bits to store numbers with magnitude up to about 1.7e308, with an accuracy of sixteen or seventeen significant figures. The internal representation of floating point values and the exact behaviour in all circumstances was originally taken from an International Standard referred to as IEEE 754. Some bit-patterns are reserved to represent “+∞” and “−∞” while others are values that are explicitly not representations of valid floating point values — these are known as NaNs (Not A Number). A few possibly unexpected effects arise from this. For 2 See the description later on of arrays. 3.4 × 1038. 4 In fact a number of the Java library functions require arguments of type float, so it is not possible to avoid this type. Its use is satisfactory in circumstances where the precision it supports is all that is justifiable, for instance when specifying the brightness of a colour. 3 ie 4.1. DATA TYPES, CONSTANTS AND OPERATIONS 55 instance floating point division never fails: 0.0/0.0 yields a NaN, while any other value divided by 0.0 results in an infinity. Also if u is a floating point value then it is possible for the expression u == u to evaluate to false (!) because the rules for all numeric comparison operators is that they return false when at least one of their arguments is a NaN. Another oddity is that one can have two floating point values u and v such that both u == v and (1.0/u) != (1.0/v)! This oddity is achieved by having u = -0.0 and v = +0.0. These delicacies are almost certainly exhibited by most other languages you will come across, but Java documents them carefully since it is very keen indeed to make sure that Java program will give exactly the same results whatever computer it is run on. Even if it does very delicate things with the marginal and curious cases of floating point arithmetic. Recent versions of the Java language use a keyword strictfp to indicate places where all the consequences of IEEE floating point must be honoured: specifically its use means that the results computed should be identical whatever machine run on, and will have rounding errors exactly as expected. Without strictfp and on some computers Java will deliver results that are both more accurate and are computed faster! Here are some floating point constants: 0.0 0.0F 1.3e-11 22.9e11F 22.9e11D 1e1 2. .2 2D this is double (by default) if you REALLY want a float specify an exponent (double) float not double be explicit that a double is required no "." needed if there is an "e" "." can come at end "." can come at start must be a double because of the D I would suggest that you always make your floating point constants start with a digit and contain a decimal point with at least one digit after it since I think that makes things more readable. In Java the result of a comparison is of type boolean, and the boolean constants are true and false. As in ML (and unlike the situation in C, in case you know about that), boolean is a quite separate type from int. Despite the fact that this section is about the Java primitive types and not about the operations that can be performed on data, it will make some of my examples easier if I next mention the ways in which Java can convert from one type to another. In some cases where no information will be lost (eg converting from a byte or short to an int) the conversion will often happen without you having to worry much about it and without anything special having to be written. However the general construction for making a type conversion is called a cast, and it is written by using a type name in parentheses as an operator. We have already CHAPTER 4. BASIC USE OF JAVA 56 already seen a couple of examples in the Draw program where (int)x was used to convert the floating point value x into an integer. The opposite conversion can then of course be written as in for (int i=1; i<10; i++) System.out.printf("%22.8g%n", 1.0/(double)i); where the (double) is a cast to convert i to floating point5. The format specifier6 %22.8g is for printing a floating point value in a General format using a precision of 8 significant figures and padding with blanks to make 22 characters printed in all. Until you understand exactly when automatic conversions apply it may be safest to be explicit. Java allows you to write casts for those conversions that it thinks are sufficiently reasonable. You can cast between any of the flavours of integer. When you promote from a narrower integer to a wider one the value is always preserved. When you cast from a wider integer to a narrower one the result is what you get from considering the binary representation of the values concerned, and the cast just throws away unwanted high-order bits. Casts from integers to float and double preserve value as best they can7. Casts from floating point values to integers turn NaNs into 0, and infinities into either the most positive or most negative integer. Floating point values that are too large to be an int or long also turn into the largest available integer. The exact rules for casts from floating point values to byte and short are something to look up in the reference manual in the improbable case it matters to you. There are no casts between boolean and other types. You need to use explicit expressions such as (i != 0) to map from an integer to a truth-value8. The type char can be used to declare a variable that holds a single character. To write a constant suitable for putting into such a variable you just write the relevant character within single quote marks, as in char mychar = ’A’; if (myChar == ’q’) ... It is frequently necessary to use characters that do not fit so neatly or clearly between quotes. For instance the single quote character itself, or a “character” to represent the end of a line. A set of escape codes are used for these, where instead of a single character one writes a short sequence starting with the escape character “\”. The supported escape sequences are: 5 In this case the cast is not needed: Java will do the required conversion so that it can perform the division. 6 You will see a bunch of common format specifiers just in examples here. You can look up full details in the on-line documentation, or find a medium-sized synopsis later in these notes. 7 Casts from int or long to float or from long to double can not always preserve an exact result because the floating point format may not have enough precision available. The closest floating point value to the true result will be delivered. 8 Unlike the position in C where there is not much distinction between integers and booleans. 4.1. DATA TYPES, CONSTANTS AND OPERATIONS \n \" \’ \\ \b \t \f \r 57 newline, linefeed (very commonly used) double quote mark single quote mark use when a single \ is wanted backspace tab newpage, formfeed (unusual) carriage-return Carriage returns are used in Windows text files and as line separation in some Internet protocols, but when creating simple text files you do not generally see or need to mention it: Java does any necessary conversions to you so that on Windows, Macintosh and Unix an end of line in a file is talked about in your programs as just ‘\n’. In addition it is possible to write \nnn where nnn stands for 1, 2 or 3 octal digits: this indicates the character with the specified character-code in a standard encoding. Use of octal escapes is not at all common. Furthermore Java allows inclusion of characters from an astonishingly large character set by use of a notation \u followed by four hexadecimal digits. The 16-bit number represented by the hexadecimal digits is taken as being in a set of character encodings known as Unicode. Casts between int and char give direct access to this encoding. For example \u2297 and (char)0x2297 both give the character “⊗”. In fact the Unicode escapes do not just apply within Java character literals but can be used anywhere in a Java program where you want an unusual symbol — and this means that in some sense you can have variables names with Greek, Russian and Eastern glyphs in them. Unicode gradually becoming more widely used, but most computers still do not have full Unicode fonts installed, and so exotic characters will not always be displayed properly even though within Java they are handled carefully. The following applet displays the characters that are available using the viewer it is run under. It uses a cast (char) to convert an integer to a character and some fresh library calls (eg setFont(new Font(...)) and drawString). It also illustrate something that you will probably want to retrofit to most of the little examples in these notes. It allocates a BufferedImage that it draws into, and then the paint method just displays whatever is in the bitmap. This does wonders for arranging that when you obscure bits of your window the content gets re-painted nicely! It also makes a crude modification of the earlier Draw program so that mouse clicks at various places in the window adjust the range of characters displayed. /* * * Unicode.java A C Norman CHAPTER 4. BASIC USE OF JAVA 58 * Display the Unicode characters as supported * by the current browser. */ import import import import java.awt.*; java.awt.event.*; javax.swing.*; java.awt.image.*; public class Unicode extends JApplet implements MouseListener { private boolean isFilled = false; private int fontSize = 20; // or whatever! private int page = 0; private BufferedImage p = new BufferedImage( 32*fontSize, 35*fontSize, BufferedImage.TYPE_BYTE_BINARY); public void init() { addMouseListener(this); } public void mousePressed(MouseEvent e) { if (e.getX() < 200) page++; else page--; if (page > 63) page = 0; if (page < 0) page = 63; isFilled = false; repaint(); // force screen to re-draw } public void paint(Graphics g) { if (!isFilled) fillImage(); // Note drawImage may need repeating!!! while (!g.drawImage(p, 0, 0, this)); } 4.1. DATA TYPES, CONSTANTS AND OPERATIONS 59 void fillImage() { Graphics g = p.getGraphics(); g.setColor(Color.WHITE); // background g.fillRect(0, 0, 32*fontSize, 35*fontSize); g.setFont(new Font("Serif", Font.PLAIN, fontSize)); g.setColor(Color.BLACK); // text g.drawString("page = " + Integer.toHexString(32*32*page), 0, fontSize); for (int y=0; y<32; y++) { for (int x=0; x<32; x++) { char c = (char)((32*page+y)*32+x); g.drawString(String.valueOf(c), fontSize*x, fontSize*(y+2)); } } isFilled = true; } public public public public void void void void mouseReleased(MouseEvent e) {} mouseClicked(MouseEvent e) {} mouseEntered(MouseEvent e) {} mouseExited(MouseEvent e) {} } /* end of Unicode.java */ The output from this program will depend on the range of fonts installed on the computer you run it on. PWF Linux has a range of European characters, mathematical symbols and oddments available. While preparing these notes I ran the code on my home Windows XP system where all sorts of fonts have accumulated over the years, and the image included here (figure 4.2.) is from there. I also use a program called Vmware which lets me install many “virtual” computers on my single home one: using that I installed essentially the version of Linux used on the PWF but told the Linux installer to include support for all available languages: by moving some files into a directory “jre/lib/fonts/fallback” I could get results very similar to those that I get from Windows. A message I hope you will absorb here is that Java itself provides portable support for international and special-purpose character sets you may need to configure its runtime before you can take full advantage of it. Also before you distribute applications relying 60 CHAPTER 4. BASIC USE OF JAVA Figure 4.2: Unicode characters in the range 0x3000 to 0x33ff on that you have to concern yourself with how well your customers’ operating systems will deal with the fonts! We have already seen string literals in our code, just written within double quote-marks. The associated type is String. Although the use of capitals and lower case is just a convention in Java the fact that the type is String rather than string is a hint that this does not have exactly the same status as the types int, char and so on. In fact String is the name of a quite complicated datatype (in Java we will find that this is known as a class) and this class pro- 4.1. DATA TYPES, CONSTANTS AND OPERATIONS 61 vides access to a number of string conversion and manipulation functions. We have already seen “+” can be used to give string concatenation. Java also arranges that if one argument for + is a String it will take steps to convert the other to String format so that this concatenation can take place. You can look up “Class java.lang.String” in the on-line documentation or a reference manual to see that there are standard library functions for case-conversion and all sorts of other string operations. For now the points to observe are that 1. Strings are represented by a data-type that exports functions to find the length of a string, concatenate strings and perform various conversions; 2. Strings are read-only, so if you want to change one you in fact make a new string containing the adjusted text; 3. Strings are not the same as arrays9 of characters; 4. It is not necessary to memorise every single string operation that Java provides. Java supports arrays. An array is just a block of values where one has the ability to use an integer index to select which one is to be referenced. The types for arrays are written with the array size in square brackets. An empty pair of square brackets means that the size is not being specified at that point, but it will be made definite somewhere else in the program. We saw an array declaration as early as the Hello program where the function main was passed an array of Strings. In this case the array will be used to pass down to the Java application any words given on the command line after the parts that actually launch the application: // File "Args.java" // Display arguments from command line public class Args { public static void main(String[] args) { for (String s : args) System.out.println(s) } } This introduces a new version of the for statement. It can be compiled and then run by saying 9 Which will be covered in the next section of these notes! CHAPTER 4. BASIC USE OF JAVA 62 javac Args.java java Args one two three it prints out one two three The points to notice here are that the type of argument that main was an array of strings, and the for loop will obey its body once for each string in that array. An alternative and older-fashioned way of achieving the same effect would be to find the length of the array and count, indexing into the array to extract values explicitly: for (int i=0; i<args.length; i++) System.out.println(args[i]) In this case the array held Strings, but Java arrays can be declared in forms to hold any sort of Java data. This includes having arrays of arrays, which is the Java way of modelling multi-dimensional structures. In Java a distinction is made between declaring a variable that can hold an array and actually creating the array that will live there. Declaring the variable happens just as for declaring integer or floating point variables, and you do not at that stage specify how big the array will be: { int[] a; ... has declared10 a to be ready to store an integer array (of unspecified size and currently unknown contents), much as { double d; ... says that d will subsequently be able to store double-precision floating point values. There are two ways that the actual concrete array can come into existence. The first is to combine the declaration with an initializer that makes the array and fills in its elements: 10 The syntax that I will try to use throughout these notes has all declarations written as a type followed by the name of the variable that is to be declared. Thus int[] is the type of an array able to hold integers. When you declare a variable of an array type Java allows you to put the brackets either next to the base type (as I will generally do) or after the name of the variable that is being declared, as in int myArray[];. This latter case is perhaps useful when you want to declare a bunch of variables at once, some scalars and some arrays, as in int simple,row[],grid[][];. 4.1. DATA TYPES, CONSTANTS AND OPERATIONS { 63 int[] p = {2,3,5,7,11,13,17}; ... where the values within the braces can in fact be arbitrary (integer-valued) expressions. The second way of creating an array uses a keyword new which is Java’s general mechanism for allocating space for things. The word new is followed by a type that describes the structure of the item to be allocated. { int[] fairly_big = new int[1000]; ... In this case the array contents will be left in a default state. In fact Java is very keen to avoid leaving anything undefined or uncertain, since it wants all programs to execute in exactly the same way every time and on every machine, so it demands that a fresh integer array starts off holding 0 in every element. It has analogous rules to specify the initial value of any other field or array element that has not had a value given more explicitly. Note that all Java arrays use subscripts that start at 0, so an array of length 1000 will accept subscripts 0, . . . , 999. Attempts to go outside the bounds will be trapped by the Java run-time system. Subscripts must be of type int. You may not use long values as subscripts. If you write a char, byte or short expression as a subscript Java will convert it to an int for you as if there had been a suitable cast visible. When an array is passed as an argument to a function the called function can update the members of the array, but if it creates a whole new array by something such as args = new String [5]; this will replace the array wholesale within the current function but have no effect on the calling routine. The terminology that Java uses for all this is that it will pass a “reference” to the array as the actual argument. The following example shows the creation of an array, code that fills in entries in it, a slightly dodgy illustration of the fact that two-dimensional arrays can be viewed as arrays of one-dimensional arrays and a crude demonstration of how you might print multiple values of a single line by building up a string that maps the entire contents of the line. CHAPTER 4. BASIC USE OF JAVA 64 // Array1.java // Create a 3 by 3 array, swap rows in it (!) // and print tolerably neatly. public class Array1 { public static void main(String[] args) { int [][] a = new int[3][3]; // 3 by 3 array int [] b; // array of length 3 for (int i=0; i<3; i++) // fill in all of a for (int j=0; j<3; j++) a[i][j] = i+10*j; // The next line recognises that a[i] are 1-dimensional // arrays of length 3. It swaps two of them around! b = a[0]; a[0] = a[2]; a[2] = b; for (int i=0; i<3; i++) // Print each row { String s = ""; // Build row up here for (int j=0; j<3; j++) s = s + " " + a[i][j]; System.out.println(s); // Then print it } } } which prints 2 1 0 12 11 10 22 21 20 The things to notice in the above example are firstly that variables a and b are declared with array types, but these types neither specify sizes nor imply that a genuine array actually exists, and secondly the way in which the two-dimensional array is dismembered. Observe also the syntax associated with new for allocating space for the array, and the fact that nothing special had to be done at the end to discard the space so allocated. Java will recycle memory previously used by arrays (and indeed any other structures) once it knows that they are no longer needed. This is of course just like the situation in ML. We have seen a number of other types in the sample programs. As well as String there was Graphics, Font and MouseEvent. Java 1.2 defines over 500 such non-simple types! Thus one thing you can be certain of is that I will not discuss all of them, and neither will the follow-on Java course next year. Each of these has (in some sense) the same status and possibilities as the programs you have written where you start off by declaring a new class. Each of String, 4.1. DATA TYPES, CONSTANTS AND OPERATIONS 65 Graphics and so on represents a class and its implementation might well be in Java stored in a file that starts off public class Whatever ... You have seen with the classes that you define for yourself that a class is a context within which you can define a collection of functions, and so it should be no surprise that each of the 500+ Java library classes provides a whole bunch of associated functions (eg for String we have mentioned the valueOf operation, and for Graphics we have used drawLine and drawString). There are thus literally thousands of library functions available. Their organisation into classes provides some structure to the collection, but in the end you probably have to find out about the ones you need to use by searching through the documentation. These notes will introduce a sampling of library classes and the functions they support with a view to directing you towards interesting areas of Java functionality. Very often I find that the best way to start to understand the use of a new part of the class library is to study and then try to modify some existing code that uses it. The Java designers suggest use of a convention where the names of ordinary variables and functions start with a lower case letter while class-names start with a capital. They further recommend use of words spelt in all capitals for things that should be thought of as constants (such as PI that we used earlier). The syntax associated with declaring something immutable will be covered later on once we have got through the use of other important words such as public and static which are of course still unexplained. 4.1.3 Exercises Tickable Exercise 3 The function System.currentTimeMillis() returns a long value that is the count of the number of milliseconds from midnight on 1st January 1970 to the moment at which it is executed. Thus something like long start = System.currentTimeMillis(); for (int i=0; i<13; i++) { System.out.println(binom(2*i, i)); } long timeSpent = System.currentTimeMillis()-start; System.out.println("Done in " + timeSpent + " milliseconds"); in the middle of a program can be used to record how long it takes to run. Note that this is the time as measured by a stop-watch (or hour glass), and will depend 66 CHAPTER 4. BASIC USE OF JAVA quite strongly on how many other people are using the computer at the same time. On a single-user computer it can give a tolerably reliable indication of the cost of a computation and even on a shared machine it is better than no information at all. 1. Adapt the Binomial Coefficients program suggested in the previous set of examples so that it reports the time it takes to get as far as displaying 24C12, which (I think) has the value 2704156. Your submission to the assessors should include a table of the values of 2nCn for n from 1 to 12, and the number of milliseconds that your program took to run. 2. Remove the definition of the sub-function that you used to compute the binomial coefficients, and add to your program a line that declares and create an array called c of size 25 by 25. Set the c[0][0] to 1. Now the first row of the matrix holds values of 0Cr . Now fill in subsequent rows one at a time using the rules c[n][0] = c[n][n] = 1 c[n][r] = c[n − 1][r − 1] + c[n − 1][r] so that the matrix gradually gets filled up with binomial coefficients. Keep going until the 24th row has been filled in. Then print out the values of c[2*i][i] for i from 0 to 12, and again measure how long this takes. 3. [From here on is optional] The above calculation can be done using a onedimensional array, so that at each stage in the calculation it holds just one row of binomial coefficients, ie values of nCr for a single value of n. At each stage by filling its values in in reverse order something like c[i] = 1; for (int j=i-1; j>0; j=j-1) c[j] = ... the new values can replace the old ones in such a way that nothing is overwritten too early. The for loop shown here sets j first to the value i-1, then to i-2, and so on all the way down to 3, 2 and finally 1. I could of course have written j-- or --j where here I put j=j-1! Write this version of the program using an array of length 80, and make the array contain long values rather than just int. First arrange that on every even row it prints the middle element from the part of the array that is in use, so it duplicates the output printed by the previous two examples. Then make the loop continue further and thus find (by inspection) the largest value of i such that 2iCi can be represented exactly as a Java long. The value is less than 40. (End of tickable exercise) 4.1. DATA TYPES, CONSTANTS AND OPERATIONS 67 A Numerical Monster A very fine paper called “Numerical Monsters” by Essex et al[12] explains how many calculations that you might think were straightforward have extra depth when done using finite precision computer arithmetic. One example is the function y = (x2 − 2x + 1) − (x − 1)2 In ideal arithmetic y would always be zero. If however the function is computed using floating point arithmetic as shown (and provided an overclever compiler does not do algebraic re-arrangement of the formula: the Java compiler is well-behaved in this respect) an interesting graph can emerge. For instance the graph shown here was produced using a very simple Java program and plotting for x in the range 13.0e-8<x<1.0+3.0e-8 and y from -1.5e-16 to +1.5e-16. Write your own version of a program to re-create this graph, and investigate the what it shows near other values of x. Two cases I will suggest investigating are 1.0e8<x<2.0e8, |y|<10 and 14999.99999999253<X<14999.99999999257, |y|<3.2e-8. Your challenge is to understand exactly how the finite nature of computer arithmetic leads to the precise patterns generated, and how these patterns would vary if details of the arithmetic model were altered. The Dutch National Flag Provided that at the start of your program you have written import java.util.*; The code int [] a = new int [1000]; Random r = new Random(); for (int i=0; i<1000; i++) a[i] = (byte) r.nextInt(); first makes an array of length 1000. It then creates a new random-number generator (called r), and finally calls the random number generator 1000 times to fill in entries in the array. The cast to type byte ensures that each entry in the array will CHAPTER 4. BASIC USE OF JAVA 68 end up in the range from −128 to +127. There will of course be duplicate values in the array. The task you have to achieve is to rearrange the numbers in the array so that they fall into three bands. The first band, say all the elements from 0 to m, should contain all the numbers x with x < −40. The second band (m + 1 to n) will be for −40 ≤ x < 40, while the final band (n + 1 to 999) is for x ≥ 40. This is known as the Dutch National Flag problem because its originator (E J Dijkstra) presented it in terms of values that had one of the three colours that his country’s flag11 used, rather than the numeric ranges I have suggested here. The problem would probably be easy if you could allocate three fresh arrays and copy each item from the original into one or the other of these, based on its “colour”. At the end you could then copy the three chunks back into the original array at the positions they needed to go. But this challenge involves the idea of efficiency too, and your final solution must not use any extra arrays, and it should ideally inspect or move each value as few times as it can. Note that just sorting the values in the array into ascending order would satisfy the objectives that concern where values must end up, but since the problem does not state anything at all about any desired order of the items that fall within any of the three bands a solution based on sorting is over-elaborate and too expensive. It may well be useful to try your hand at the Polish Flag problem — my encyclopaedia shows the Polish flag as having just two stripes12. Thus the Polish Flag problem is to rearrange the values in the original chaotic array so that all negative ones (say) come before all positive ones, but without any further constraint on the re-ordering apart that it should be achieved reasonably efficiently. The Mauritian Flag seems to go Red, Blue, Yellow and then Green. . . Matrix Operations Set up two 5 by 5 arrays of type double[][]. Fill in the first so that13 the element at position (i, j) has value 1/(i + j + 1.0). Fill in the other so that the the elements on the diagonal have the value 1.0 while all other elements hold 0.0. The program wanted now will be one that gradually turns the first matrix into one that has just 1.0 elements down its diagonal and zeros elsewhere. The permitted operations are 1. Multiply all the elements in a row by the same non-zero value; 2. Subtract a multiple of one row from another. 11 Red, White and Blue in that order and White 13 This form of matrix is known as a Hilbert Matrix. 12 Red 4.1. DATA TYPES, CONSTANTS AND OPERATIONS 69 and whenever one of these operations is performed on the first matrix it must also be performed on the second. The first matrix can be made diagonal by tidying up first the first column, then the second, then the third and so on. To tidy up column i you first multiply row i by 1/ai,i , since this will leave14 the element on the diagonal as 1.0. Then for every row apart from row i you subtract a suitable multiple of row i so as to make the element in column i vanish. Do this and display the elements of the second matrix, which should in fact have ended up as the inverse of the original Hilbert matrix! Encryption The following code fragment starts with a string called key and fills out an array k with repeated copies of the character codes from the key until k has 256 entries in it: String key = "My Secret Key"; int keyLength = key.length(); int [] k = new int [256]; for (int i=0; i<256; i++) k[i] = (int)key.charAt(i % keyLength) % 256; All the values stored in k have been reduced so as to be in the range 0 to 255. Observe the use of functions length() and charAt() from the String class. I have used a fixed string as the keyword here. The repeated use of the fixed numeric constant “256” in this code is a stylistic oddity. In some ways once the array k has been declared it might be nicer to use k.length to talk about the number of elements it has. I took the view when writing this that the exact size of the array is part of the core specification of the algorithm I am implementing. . . When you write this program whatever else you do please do not use your password as text in the program you write! The program you have to write here may be related to an encryption method known as RC4 that was once a trade secret of RSA15 but which was published anonymously and presumably improperly a couple of years ago. RC4 is used as the encryption technology in a large number of generally used packages and although its security may not have been proved it is widely believed to be respectable. It is also fast. 14 For now assume please that the diagonal element was non-zero so that the division behaved properly and did not end up yielding and IEEE infinity. 15 The major American encryption and security company. You may like the view consideration of the proper uses of such code as an exercise relating to the Professional Practise and Ethics course. CHAPTER 4. BASIC USE OF JAVA 70 The first part of the procedure is to create an array s of 256 integers, and initialise it. It is first set so that the value at position i is just i (i runs from 0 to 255). Now your code should scramble this up using they key k using the following process: For i from 0 to 255 do Let j be (s[i] + k[i]) reduced to 8 bits Swap the items at positions i and j in the array s The term “reduced to 8 bits” can be implemented by just taking the remainder16 when the value concerned is divided by 256. At the end of this the array s holds a working collection of scrambled data. This is used to generate a sequence of 8-bit numbers which can be combined with a message to encrypt it. Starting with variables i and j both zero the next encryption-number is obtained as follows: Increment i modulo17 256 Set j to j + s[i], again modulo 256 Swap s[i] with s[j] Let t be s[i] + s[j] modulo 256 The result is s[t] The algorithm is clearly short enough to be utterly memorable! The sequence of numbers it generates can be added to the (8-bit) character codes of a message to give the encoded version, and if the recipient knows the key that was used then decryption is just generating and subtracting the same sequence of values. It is vital that a key used with this method should never be re-used, and competent security tends to involve really careful attention to many details that do not belong in this course18 . Code the scheme described above. Print the first dozen output values from it for your chosen key. You may like to check with a friend to see if their implementation generates the same sequence as yours when given the same key — by the nature of this code there is not obviously going to be any other way of characterising the correct output! 16 In the next section we will also see that it can be achieved by writing something like (s[i] + k[i]) & 0xff. 17 “modulo” 18 But means just the remaindering operation. which will be covered later in the CST. 4.2. OPERATORS AND EXPRESSIONS 71 4.2 Operators and expressions The examples shown already have included uses of the usual arithmetic operators, both as used on integers and on floating point values. Now is the time to present a systematic list of the operators that Java provides and the way they are used in expressions. One of the critical things in any programming language is the syntactic priority of operators. For instance in normal usage the expression a + b × c must be read as if it had been a + (b × c) rather than as (a + b) × c. To stress the importance of knowing which operators group most tightly I will list things ordered by their syntactic precedence rather than by what they do. The simple arithmetic cases will be listed but not discussed at any great length. ++, --: We have already see use of ++ as a postfix operator that increments a variable. The full story is that the expression ++a has the side-effect of increasing the value of the variable a by 1 and its overall value is that incremented value while a++ increments a but the value of the expression is the original value of a. The use of -- is similar except that it subtracts one rather than adds one to the variable mentioned. These operations can apply to either integer or floating point variables; +, - (unary): Unary + does not do anything but is there for completeness. Unary - negates its (numeric) argument; ˜: The ˜ operator treats its integer operand as a binary number and negates each bit in the representation. If you look back at the earlier table that illustrated binary numbers you can check that ˜0 will have the same value as -1; !: The ! operator may only be applied to a boolean operand, and it complements the logical value concerned, so that !true is false; (type): Casts are included here to show their precedence and to point out that as far as syntax is concerned a cast acts just as a unary operator; *, /, %: Multiplication, division and remaindering on any arithmetic values. The odd case is the % operation when applied to floating point arguments. If x % y is computed then Java finds an integer value q that is the quotient x/y truncated towards zero, and then defines the remainder r by x = qy + r. If integer and floating point values both appear in the same expression the integers are promoted to the floating type before the arithmetic is performed. Similarly if integers of different lengths are mixed or if floats and doubles come together the arithmetic is performed in the wider of the two types; +, -: Both integers and floating point can be added and subtracted much as one might expect; 72 CHAPTER 4. BASIC USE OF JAVA + (string): If the + operator is used in a context where at least one argument is a string then the other argument will be converted to a string (if necessary) and the operation then denotes string concatenation. We have seen this used as a way of forcing a conversion from a numeric type to a string ready for printing. Note that the concatenation step will generally involve allocating extra memory and copying data from each of the two original strings, so it will tend to be much more expensive that the arithmetic uses of the + operator. <<, >>, >>>: Consider an integer as represented in binary. Then the << operator shifts every bit left by a given number of places, filling in at the right hand end with zeros. Thus the program fragment: for (int i=0; i<32; i++) System.out.printf("%d : %d%n", i, (1 << i)); will print out numbers each of which have representations that have a single 1 bit, with this bit in successive places. The result is a table of powers of 2, except that the penultimate line of output will display as a negative integer and the final one will be zero! There are two right-shift operators. The usual one, >>, treats numbers as signed values. A signed value is treated as if they were first converted to binary numbers with an unlimited number of bits. For positive values this amounts to sticking an infinite run of 0 digits to the left while for negative ones it involved preceding the number with lots more 1 digits. Next the value is shifted right, and finally the value is truncated to its proper width. The effect is that positive integers get 0 bits moved into the vacated high order positions while negative ones get 1s. When shifting an int the shift amount will be forced to lie in the range 0 to 31, while for type long it can only be from 0 to 63. The special right shift written as >>> shifts right but always fills vacated bits with 0. It is very useful when an integer is being thought of not as a numeric value but as a collection of individual bits; <, <=, >, >=: The usual arithmetic comparisons are available, and I have already remarked that there are a few delicacies with regard to floating point comparisons and NaNs, infinities and signed zeros; instanceof: This will be discussed later; ==, !=: Equality and inequality tests. For the primitive types they test to see if things have the same value. For other types (arrays and the object-types that are introduced later on) the test determines if the things compared are “the same object”; 4.2. OPERATORS AND EXPRESSIONS 73 &: On integer operands the & operator forms a number whose binary value has a 1-bit only where both of the inputs have a 1. For positive values, for instance a & 0xf and a % 16 will always yield the same result. Long tradition of languages where the “and” operator is significantly faster then division and remainder means that many old-fashioned programmers will make what is now maybe excessive use of this idiom. The & operator can also be applied to boolean operand, in which case is means just “and”; ˆ: Exclusive or. See below to compare inclusive and exclusive or; |: Inclusive or. Note that for integer values a&˜b | b&˜a == aˆb and the same identity holds for boolean values except that ! has to be used for the complement/negation operation rather than ˜. Here are the truth tables for inclusive and exclusive or: “|” 0 1 “ˆ” 0 1 0 0 1 0 0 1 1 1 1 1 1 0 &&: In a boolean expression such as A & B if the value of A was false there is perhaps no need to evaluate B. The simple “and” notation does not take advantage of this, but the alternate form A && B does. Apart from efficiency this can only make a difference if evaluating the sub-expression B would have side-effects. In general I think it is probably good style to use && rather than just & whenever a boolean expression is being used to control an if or similar statement, while & is probably nicer to use when calculations are being performed on boolean variables; ||: This is the version of the “or” operator that avoids processing its right hand operand in cases where the left hand one shows that the final value should be true. Its use is entirely analogous to that of &&; ?: It is sometimes nice to embed a conditional value within an expression, and Java lets you do that using the slightly odd-looking syntax a ? b : c. This expects a to be of type boolean, while the other two operands can have any type provided that they are compatible. If a is true the result is the first of these expressions, otherwise it is the second. For instance the messylooking expression (a==0 ? "zero" : "non-zero") has the value "zero" if a is zero and "non-zero" otherwise. The phrase (a || b) could be replaced by the equivalent form (a ? true : b), while (a && b) has the same meaning as (a ? b : false); CHAPTER 4. BASIC USE OF JAVA 74 =: Assignment in Java is just an operator. Thus you can assign to a variable anywhere within an expression. The value of a sub-expression that is an assignment is just the value assigned. Thus silly things like ((a=a+a) + (b=b-1)) are good syntax if not good sense. A more benign use of the fact that assignments are just expressions arises in the idiom (a = b = c = d = 0). The assignment operator associates to the right so the example means (a = (b = (c = (d = 0)))) and thus assigns zero to all of the four variables named; *=, /=, %=, +=, -=, <<=, >>=, >>>=, &=, ˆ=, |=: These operators combine assignment with one of the other operators that have been listed earlier. They provide a short-hand notation when the same thing would otherwise appear to the left and immediately to the right in an assignment. For instance a = a+3 can be shortened to a += 3 The abbreviation has slightly more real value when the assignment concerned is to some variable with a name much longer than just a, or especially if it is into an array element, since the short form only has to evaluate the array subscript expression once. This can lead to a difference in meaning in cases where evaluating the subscript has a side-effect, as in the artificial fragment int int int for [] a = ...; [] b = ...; p = 0, q = 1; (int i=0; i<10; i++) a[p++] += b[q++]; where index variables p and q step along through the arrays a and b and it is important that they are each incremented just once each time around the loop. 4.2.1 Exercises Bitwise operations on integers Investigate, either on paper or by writing test programs, each of the following operations. Explain what they all mean, supposing that the variables used are all of type int: 1. ˜a + 1; 2. a++ + ++b; 3. a & (-a); 4.2. OPERATORS AND EXPRESSIONS 75 4. a & ((1<<b)-1); 5. (a>>>i) | (a<<(32-i)); 6. a + (a<<2); 7. (int)(byte)a; 8. (a & 0x80000000) != 0; 9. (a++ != b++) && (a++ == b++); 10. (--a != --b) | (--a == --b); 11. (a < 0 ? -a : a). Counting bits in a word Write a function that counts the number of non-zero bits in the binary representation of an integer. You can first do this using a test for each bit in the style a & (1<<n) != 0. Next see if any of the examples in the previous exercise give you a way to identify a single bit to subtract off and count. Consider also the expression (c[a & 0xff] + c[(a>>8) & 0xff] + c[(a>>16) & 0xff] + c[(a>>24) & 0xff]) for some suitable array c. What does this do? This exercise and the few following it introduce a few fragments of amazingly twisted and “tricky” code. Please do not view the inclusion of these programming techniques here as an indication that you will be examined on them or that you are being encouraged to use such obscure constructions in your own programs. It is more that puzzling through these examples can refine your understanding of the interactions between the Java arithmetic operations such as + and % and the ones that work on the binary representations of numbers, ie & and >>. In a few circumstances the ultra-cunning bit-bashing might save time in a really critical part of some program and so could be really important, but almost always clarity of exposition is even more important than speed. Certainly use of these tricks will not make your programs any shorter, in that the bulk of the comments needed to justify and explain them will greatly exceed the length of more straight-forward code that has the same effect! CHAPTER 4. BASIC USE OF JAVA 76 Start off with a any positive value. Execute the following19 and discuss what value gets left in a at the end. Hint: look at the binary representation of a to start with. a a a a -= (a>>>1) & 03333333333; -= (a>>>1) & 03333333333; = ((a>>>3) + a) & 0707070707; = a % 63; And this. . . a &= 0x3f; a = ((a * 02020202) & 0104422010) % 255; And this. . . c = a & -a; r = a + c; a = (((r ˆ a) >>> 2) / c) | r; [In each case it will help if you look at the numbers in binary.] Integers used to represent fractions Consider a as a value expressed in binary but now as a positive fractional value in the range 0 to 1. This means that there will be an implicit binary point just to its left. Then 0xffffffff will be just a tiny bit less than 1, 0x80000000 will stand for 1/2 and 0x40000000 for 1/4. In terms of this representation interpret the effect of executing the following four statements one after the other. a a a a += += += += a a a a >>> >>> >>> >>> 2; 4; 8; 16; Division and Shifts If a is a positive integer than a/2 and a>>1 give the same result. What is the relationship between their values if a is negative. Carry on the analysis for division by 4, 8, 16,. . . and the corresponding right shifts. 19 Discussions with Alan Mycroft caused this and some of the other curious examples here to re-surface. For a collection of real programming oddities including some of these try searching for “Hakmem” on the World Wide Web or find “The Hacker’s Delight”[15] in a library 4.3. CONTROL STRUCTURES 77 Some Exclusive-Or operations What is the final effect on a and b of the sequence a ˆ= b; b ˆ= a; a ˆ= b; Sieve for primes Create an array of type boolean and length 1000. Set all elements to true to start with. Then set items 0 and 1 to false Repeat the following two steps 1. Find the first true item in the array. If there are none left then exit. Print out the index of the value you have found, and call it p. 2. Set each item in the array that is at a position that is a multiple of p to be false, for instance as in for (i=p; i<1000; i+=p) map[i] = false; The numbers you have printed should be the primes up to 1000. If you wanted to find the primes up to several million (for instance to count them rather than to tabulate them) it would make sense to make the array represent just the odd numbers not all numbers. It might also save significant amounts of space to represent the array as an array on int rather than boolean and pack information about 32 odd-numbers into each int. You might note that some programming languages can implement boolean arrays in that way without much user intervention — Java does not. 4.3 Control structures 4.3.1 Exercises Ambiguous If Consider the sample code fragment if (a == 0) if (b == 0) System.out.println("Both 0"); else System.out.println("Some other case"); 78 CHAPTER 4. BASIC USE OF JAVA Figure 4.3: Keep control structures simple! 4.3. CONTROL STRUCTURES 79 and wonder exactly what happens if one or other of a or b is non-zero. Write sample programs that test the actual behaviour of the real Java compiler either to discover how Java resolves the near-ambiguity in syntax that this example represents. Periodic Forests of Stunted Trees The forms explained here were investigated by J C P Miller who was a lecturer here in the Computer Laboratory. Their study leads on into that of error correcting codes and so is perhaps less detached from the serious technical side of computer science than one might think. A root-line for a forest is a periodic binary sequence. Since it is hard to draw things that repeat indefinitely it is useful to display such sequences by showing one or two cycles and than agreeing that the ends of display should be treated as being joined up to make a circle. Here is a sample root-line: . X . . X X X . . X . . X X X . . X . . X X X . A forest grows from a root-line by the simple rule that a branch grows when and only when exactly one cell in the row beneath it is present (this is an exclusive OR operation). I am drawing lines upwards20 following this rule from the root-line I showed above yields ? . . . . . . . . . . . . . . . . . ? ? X X X X X X X X X X X X X X X X X X ? ? X . X . X . X . X . X . X . X . X . X ? ? X . . X X . . X X . . X X . . X X . . X ? ? . X X X . X X X . X X X . X X X . X X X . ? ? X X . X . . X . X X . X . . X . X X . X . . ? ? . X . . X X X . . X . . X X X . . X . . X X X ? and the pattern seems to have died. In many cases after a number of rows the original row will re-appear21. Triangular clearings appear on the way. The challenge is to understand when a pattern will die and when it will repeat, how the vertical repetition period relates to the original horizontal one, and how large the largest clearings will be. Write a Java application or Applet that takes a command-line argument or otherwise accepts information from the user in the form of a string of X and . characters. It should then display the generated forest. 20 If you print things to System.out it would probably be easier to print with growth downwards. If you draw things to the screen in an Applet putting it either way up is easy. Observe that I have drawn question marks to show where the pattern depends on data beyond the initial segment of pattern. If you can allow for the (infinite) repetition of the base-line you do not need these. 21 You will see that I have only drawn a finite section of the infinite repeating base-line, and so the forest is drawn as getting narrower as you grow it upwards. However it should be understood to be of infinite width and so patters can re-occur exactly. CHAPTER 4. BASIC USE OF JAVA 80 Life The world consists of an infinite sheet of graph paper. Each square may at any one time be either black or white. Every square has eight neighbours Every so often all squares simultaneously follow the following rules: 3 4 5 2 6 1. A square that is black at present remains black if it has two or three black neighbours. Otherwise it turns white; ⊙ 1 8 7 2. A white square becomes black if it has exactly three black neighbours. These rules define a behaviour which was invented by John Conway (who at the time was in the Mathematics department here) and which is known as Life. One starts off with a board that has a small number of black seed points and display the position as the generations go by. There are many astonishingly complicated things that can happen and people have designed starting positions that illustrate them. The challenge here is to make the computer run the rules and display the world-state. A useful starting configuration to try has just five black cells arranged as at the head of this paragraph. It explodes and seethes for quite a long while before the situation stabilises. One thing to note is that all decisions about the next generation are expected to be taken simultaneously, so any program that updates the world incrementally is liable to get wrong answers. A further problem is that the the ideal playing surface for Life is infinite, while computers tend not to be. Two resolutions to this are usually considered. One places an immutable wall of white cells as a border around the world so that all activity is contained within them. The other scheme often used is to use a finite playing area but considers its left hand column to be adjacent to its right most one and its top to be adjacent to its bottom row. This amounts (depending on how you think of it) to playing Life on a torus or to ensuring that all initial positions are replicated in a periodic manner across an infinite plane. The easiest program for this will set up two boolean arrays. The first holds the current generation, while the second will be filled in with the next. My version of a program that does this, complete with code to set up the initial pattern that I have suggested and to draw the board positions in an applet window is around 75 lines long. I used a 200 by 200 board and kept the outermost rows and columns permanently blank. That means that when accessing neighbours I can read from them without going outside my array. Clearly the first exercise here is to reproduce something like that. 4.3. CONTROL STRUCTURES 81 There are then three follow-up challenges. The first looks back to the optional part of the binomial coefficient tickable exercise: can you get away with just one boolean array rather than two, possibly keeping a boolean vector to store just one row of backup information but mostly updating the world in place. To do so would save around half of the memory that the simple program uses. The second challenge observes that representing the playing area as arrays of type boolean is probably wasteful. This would be a typical application where packing 32 cells into an int and using lots of bitwise and, or and shift operations to deal with them would be common practice. It would of course be possible to achieve this by having nice abstract procedures to reference the bit Figure 4.4: Gosper’s Glider Gun. at position (x, y) in an array even though the array was being represented in a packed way. But it would perhaps give big speed savings to look for ways to exploit the fact that bitwise operations on integers can handle 32 bits all at once and to try to use this to compute new values for several cells at the same time. Finally, and given that this program tends to run a little slowly, one looks at where the time goes. Much of it will be wastage on parts of the board that are totally white and hence where nothing is going to happen. Try to speed your code up by avoiding as much of such wasted as is reasonable. Eight Queens Count the number of ways of placing eight queens on a chess board so so that no pair are in the same row, column or diagonal as each other. This is a classical puzzle to go in an introduction to programming and there are lots of clever tricks that can be used. It is the sort of thing that most supervisors will have come across before so I will not provide a fully worked through solution here, but I might observe that the search might well be done by a recursive function that when called at depth n will try to place a queen on row n of the chess-board. 82 CHAPTER 4. BASIC USE OF JAVA Permutations In Java arrays can be passed as arguments and newly created arrays can be returned as results. Write a function that accepts an array of strings as its argument, and which hands back and array whose elements are themselves arrays of strings giving all possible permutations of the input. For instance if I use curly brackets to denote arrays here one might like {"a", "b", "c"} to turn into {{"a", "b", "c"}, {"a", "c", "b"} {"b", "a", "c"}, {"b", "c", "a"}, {"c", "a", "b"}, {"c", "b", "a"}}. As with several of the other Java exercises I might suggest that you design and test an ML version first. 4.4 Control structures Part 2 There are two aspects of syntax that I will put off until a yet later section. One if the syntax associated with the word class that we have seen wrapped around every program we have written. The other is the matter of the “.” that appears between or possibly within so many names, eg System.out.println and g.drawString. A few other bits of syntax will just not be covered in this first course, although you may find traces of them in the grammar and discussion of them in textbooks — and possibly also in next year’s “Concurrent Systems and Applications” course. But I will talk through each of the important components of the syntax and give at least one illustration of each. 4.4.1 Expression Statements Certain sorts of Java expression can be used as a statement — all that is necessary is to stick a semicolon on the end of it. The cases permitted are where evaluating the expression might have a side-effect. Thus an assignment expression, a function call or a pre- or post-increment expression can be used. As an example, consider the statement x++; which just increments x. An example such as 1+2+3; is not considered valid: it would calculates the value 6 and then throws it away! 4.4.2 Blocks Several statements can be placed one after the other to make a single large statement. Braces { . . . } are used around the statements to group them. In various earlier languages the keywords begin and end were used instead of braces, but Java prefers the version where you type in fewer key-strokes. If you see a block 4.4. CONTROL STRUCTURES PART 2 83 with semicolons in the semicolons are just parts of expression statements and nothing special to do with the fact that there is a block there. Again some earlier languages differed by using semicolons between statements in a block rather than as termination of expression statements. Blocks can be nested any way you want. You may insert extra braces to stress the grouping of any collection of statements you feel deserves that, in much the same way that extra parentheses can always be used to emphasis the grouping within expressions. I think there are enough examples of blocks throughout these notes that I do not need to give a special one here. 4.4.3 Null statements If you insert a stray semicolon into a Java program it (mostly) does not matter much, since a semicolon alone can be interpreted as an empty statement that does nothing. The most striking example of the use of a null statement is in something like if (a > 7); else System.out.println("Gotcha"); where the if needs a statement before its else part but no real action is needed. If you really need such a place-holder I would suggest that the following is clearer and flags your unusual intent more clearly. if (a > 7) {/*NOTHING*/} else System.out.println("Gotcha"); Better yet rearrange your code to make tests happen in a positive sense: if (a <= 7) System.out.println("Gotcha"); 4.4.4 if It takes a little while to get used to the fact that the condition tested by if is written in parentheses. Some people prefer a style where the statement after an if is always written as a block, even if it is only a single statement, so that the range that the if controls is made very explicit. This point of view has some sense behind it, especially if the statement after the if is more than half a line long. The control expression used by if must be of type boolean and so equality tests are written as in a==0 and not a=022 . Using a single rather than double equals sign is a common slip. 22 Which would be an assignment and would have type inti. CHAPTER 4. BASIC USE OF JAVA 84 4.4.5 while, continue and break A while loop executes its command repeatedly for so long as the guarding expression remains true. Its syntax is very much like that of if. Within the iterated command you can embed a statement “break;”, and execution of that will cause a premature exit from the loop. The command continue;23 can be useful if the iterated command is a long block, and it causes the loop to proceed at once to its next cycle. Both break; and continue; are very convenient at times, but it is often good style to avoid them when reasonably convenient so that the boolean expression at the top of the while loop represents a total statement about the circumstances in which it will loop and when it will terminate. The following sample shows a fairly typical while loop. Look back at your Discrete Mathematics notes for explanation of why it computes a highest common factor and to give clues to a reason for carrying out the extra computations. You may also like to code up an extended Euclidean algorithm as a function that calls itself (say in ML rather than Java) and observe that use of while loops does not always lead to the shortest or most transparent code. int a = 72, b = 30; int u = 1, v = 0; while (b != 0) { int q = a / b; int r = a - q*b; a = b; b = r; int t = u - q*v; u = v; v = t; } // Here a is the HCF. What are u, v? Note that break can be used to exit other loops, and it is also used with switch statements, which will be described soon. 4.4.6 do Sometimes the most natural way to write a loop puts the test of a termination condition at the end of the loop rather than at the start. This circumstance is supported by the do statement, although I find it much less useful than while. In fact I will often express do 23 Observe that the syntax for each of these command includes a semicolon, The identifier mentioned in the full grammar is something I will not discuss here. 4.4. CONTROL STRUCTURES PART 2 85 { ... } while (xxx); by writing it instead as while (true) { ... if (!xxx) break; } since I think that do puts the details of what the loop is about rather too far down the page. Anyway that also gave me a chance to include an example of a break statement for you! The issues of programming style here could give rise to a variety of discussions. A good policy is to try rather hard to make it very clear and obvious just when each loop you write is going to terminate, and indeed to make it clear (in comments as necessary) why you know it will eventually terminate. 4.4.7 for Iteration with for has been seen in several examples. What is shown in the Java syntax is that each of the three components within the parentheses and separated by semicolons is optional. The most extreme case is when none are present: for (;;) { ... } means just the same as while (true) { ... }. In for (A;B;C) the expression A is an initializer evaluated just once at the start of the loop. B is a boolean expression and is used just as in a while statement to determine when to terminate. Finally C gets evaluated between each cycle of the loop, and it often increments some variable. The idiom for (i=0;i<N;i++) executes its command N times counting from 0 to N-1. The alternative way of writing things is for (i=1;i<=N;i++). It loops the same number of times but is maybe slightly less commonly used. Of course with the second version the variable i starts at 1 not 0: this typically makes it less suitable for use as an array subscript because in Java subscripts start at 0. 4.4.8 switch, case and default There are occasions when one wants to dispatch to many different code fragments based on the value of some expression. This could be achieved by writing a chain of if .. else statements, but often switch provides a much neater way of expressing things. The construction starts with switch (Expression). The expression given must be of type char, byte, short or int. Note that long is not allowed. The 86 CHAPTER 4. BASIC USE OF JAVA switch-header is followed by a block enclosed in braces, and within this block there can be special switch labels. The usual sort reads “case Constant:” and control arrives just after the colon if the integer value of the switch expression agrees with the constant after case. It is often useful to specify what action should be taken if none of the cases that have explicit coverage happen, and for this a label “default:” can be set. Case (and default) labels do not disturb the usual sequential execution of statements, and so unless something special is done after one case is processed control will proceed to the next one. This is usually not what is wanted. A break; can be used to exit from the entire switch block. Many programmers would count it is good style to put an explicit comment in whenever a break is not being used, to show that its omission was deliberate and not an accident. If no explicit default label is given but a switch is executed in such a way that none of the cases match it just acts as if there had been a default label just before its final close brace. It is generally a good thing to use switch whenever you have more than three or four options to select between, in that it tends to be much clearer and easier to understand than length strings of nested if statements. In the following rather silly example it is imagined that the user has provided the function show Observe that the case labels do not have to be in any special order, and that a single statement can be attached to several labels. switch ((int)n) { case 2: show("the only even "); // drop through case 3: case 5: case 7: case 11: case 13: case 17: show("prime\n"); break; case 4: case 9: case 16: show("square\n"); break; case 8: show("cube\n"); break; default:show("dull or too large\n"); // now just drop off the end } 4.4. CONTROL STRUCTURES PART 2 87 4.4.9 return When a function has done all it needs to it will want to return a result. This is achieved using the return statement. Function definitions (see later) always indicate what type of result is required. They may have used the keyword void to indicate that no result is needed. Such is the case with main. For void functions one just writes “return;”, while in all other cases the syntax is “return Expression;”. 4.4.10 try, catch and throw, finally Real programming languages need to be able to implement code that can recover from errors and handle unusual cases tidily. The handling scheme in Java uses the throw statement to raise exceptions. Throw statements specify an object which should generally24 be of type Exception25 . The effect is that control exits from the current block or procedure and any enclosing ones, all the way until a suitable handler is found. If no such handler is present the computation is terminated. The system has a number of built-in exceptions it will generate. For instance an attempt to divide by the integer 0 raises an exception of type ArithmeticExpression. Various functions that read from files can raise exceptions to indicate that the file did not exist, the current user did not have permission to read it or an attempt was made to read data beyond its end. Handling exceptions involves prefixing a block with the word try and adding on the end of it one or more clauses that describe what to do in unusual cases. A clause that starts catch (Argument) is followed by another block which gets obeyed if the system detects an exception whose type matches that declared for the Argument. A single try may be followed by catch handlers for several different types of exception. try { z = 1/0; } // raises an exception! catch (ArithmeticException e) { ... } After all catch clauses you can put the keyword finally followed by another block. The intent here is that this block will get executed come whatever, and it will usually be used to tidy files or data-structures that the program might otherwise have left in a mess. A typical scheme to provide robust access to files would go something like <open the file for reading> try 24 I do not want to give the full and precise rules here! be more precise of some type derived from Exception. 25 To CHAPTER 4. BASIC USE OF JAVA 88 { while (true) <read-more-from-file> } catch (<end-of-file-exception>) { // whole file read here. Good! <success code> } finally { // must tidy up even if some failure // other than end-of-file intruded <close the file> } Later on I will give concrete examples that fill in the function calls and so on in this framework. Some programmers view catch and throw as neat and convenient language features to be used wherever they fit. Certainly the file-handling example above makes very good use of them. Others, and I tend to fit into this category, would like to see them used rather sparingly in code since they can result in all sorts of loops and functions terminating unexpectedly early and therefore undermine attempts to make absolute statements about their end results. 4.4.11 assert A statement of the form assertExpression; will evaluate the expression (which really ought not to have any side-effects. If its value is false and if some magic flag was supplied when the Java launcher was run then an exception is raised. Assertions can have a second expression that can be used to give more details of what you thought had gone wrong. It is proper style to include them at places in your code where there is some reasonably cheap consistency check that you could apply and when used well they are a huge aid to testing and debugging. If you run your program normally the assertions will not be checked, and furthermore having them in your source file will not hurt performance enough to notice. If however you run the java command with the extra flag -ea the extra checks will be done. Usage such as java -ea:CheckThisClass SomeClass will arrange to check just the assertions in the named class. 4.4.12 Variable declarations Variable declarations can occur anywhere within a block. They are also allowed in the first component of a for statement. The scope of a variable that is declared 4.4. CONTROL STRUCTURES PART 2 89 within a block runs from the declaration onwards until the end of the block. A declaration made in a for statement has a scope that covers the remainder of the for statement, including the end-test and increment expressions as well as the iterated block. In fact the scope of a declaration appears to include the initialiser for that variable, but if you try to use the variable there you should expect at least a warning message. So things like { int x = x+5; ... } should not be attempted! A local consists of a type, then the name of the variable being declared, and optionally one or more pairs of square brackets (to denote the declaration of an array). Any initializer follows an “=” sign, and for arrays the initializers are written in braces so that many individual values can be given so as to fill in the whole array. A local variable declaration can be preceded by the word final, and this marks the variable that is being declared as one that will not subsequently change. A convention is that constants should be spelt entirely in upper case, as have the examples PI and PLAIN that have been seen so far. Here is an example: final double E = 2.718281828459045235; E = 1/E; // NOT valid because of "final" 4.4.13 Method definitions A function definition starts with some optional qualifier words. The available words are public abstract protected final private native static synchronized and if present these can be written in any order. I will explain what they mean later on. Next comes the type of result the function will return, which is either an type or the special word void to indicate “no result”. Next is the name of the function that is being declared, followed by a list of formal arguments (in parentheses). A formal argument must be given a type, and may be preceded by final if the body of the function will never update it. The grammar shown earlier indicated that pairs of square brackets may be written after the formal parameter list, but this should not be used in any new code26. If the execution of the function can cause an exception to be raised and this exception is to be caught somewhere then the fact must be mentioned by following the list of formal parameters by the 26 It is a concession to some earlier versions of Java where it could be used for functions that returned arrays. CHAPTER 4. BASIC USE OF JAVA 90 keyword throws and then a list of exception types. Finally there is a block (ie some statements within braces) that forms the body of the function that is being defined. For the moment you will still have to take the qualifiers public and static on trust. They relate to the construction of the class that the whole file defines. 4.4.14 Exercises Concerning 3n + 1 Take any number n. If it is even then halve it, while if it is odd replace it with 3n + 1. Repeat this process to see what happens in the long run. For various very small integers you will find that you end up in a cycle 1 → 4 → 2 → 1 . . . but it is not at first clear whether this is the ultimate fate when you start from an arbitrary integer. Write a program that generates the sequence starting from each integer from 1 to 1000. If the sequence ends at 1 record the number of steps it took to get there. If you have taken over 10000 steps on some particular sequence then stop and report just that value: after all maybe the sequence starting from that seed goes on for ever, either by diverging to infinity or by finding a cycle different from the one that includes 1. If on the way you generate an odd number larger than (Integer.MAX VALUE-1)/3 you should also stop the calculation there since otherwise you would suffer from integer overflow and subsequent work would be nonsense. The constant Integer.MAX VALUE is another Java built-in constant useful in cases such as this. Arrange that you only print anything when a new record is broken for the length of a sequence or when you would reach integer overflow. For each recordbreaker display the seed, the number of steps taken before 1 is reached (or the fact that an overflow occurs) and the largest value in the sequence concerned. Tickable Exercise 4 As you start this exercise note that ticks 1, 2, 3 and 4 are probably fairly easy. Tick 5 is going to be a somewhat larger piece of work so as soon as you have finished this one you might like to look ahead and get started on it! In ML a function called quicksort could be defined as fun select ff [] = [] | select ff (a :: b) = if (ff a) then a :: select ff b else select ff b; 4.4. CONTROL STRUCTURES PART 2 91 fun quicksort [] = [] | quicksort (a :: b) = quicksort (select (fn p => p < a) b) @ [a] @ quicksort (select (fn p => p >= a) b); The idea is to use the first element of the input list as a pivot. One then selects out first all the remaining values that are less than this pivot, and all the values that are at least as large. Recursive calls sort the two sub-lists thus generated, and a final and completely sorted list is obtained by concatenating the various parts that have been collected. The ML version is very elegant and shows some of the important ideas behind the Quicksort algorithm. However it misses out several other things that are important in the real Quicksort method, mostly issues concerning use of memory. In this exercise you are to implement a version of Quicksort in Java. You should write a procedure with signature27 void quickSortInner(int [] v, int i, int j) which will sort that part of the array v that has subscripts from i to j. It will be up to you to decide if these limits are inclusive or exclusive. The procedure should work by first seeing if the sub-array it has to work on is empty. If so it can return without doing anything! Otherwise it should take the first (active) element of the array as a pivot and rearrange28 the remaining items so that the array gets partitioned at a point k such that the pivot has been moved to position k, all items to the left are smaller than the pivot and all items to the right are at least as large as it. It should do this re-arrangement without using more than a few extra simple variables: ie it is not acceptable to create a whole fresh array and copy material via it. quickSortInner can then call itself recursively in a way suggested by the ML code to complete the sorting process. You should also define a function called just quickSort that takes only one argument — the array to be sorted. Remember that the .length selector can tell you how large the array is. To show that your code works you should demonstrate the following tests: 1. Create an array of length 10 and show the effect of sorting it when its initial contents are (a) the numbers 1 to 10 starting of in the right order to begin with, (b) 1 to 10 in exactly the opposite order to begin with, (c) ten numbers generated by nextInt() from the random number package (d) ten numbers all of which are zero; 27 The signature of a function is just the specification of the types of its arguments and result. the National Flag exercise. 28 Remember CHAPTER 4. BASIC USE OF JAVA 92 2. Measure the time taken to sort various length vectors of random data where you should use lengths 16, 32, 64, . . . up until the sorting run takes several seconds. For each test compute the quotient of the time taken and the value N log(N) where N is the number of items being sorted. Optional part for those who are keen: Read the Java documentation for the Array.sort(int []) method that Java 2 provides. Write code to time it and compare the results with the code you wrote yourself. When measuring times work with arrays long enough that each test takes several seconds. Observe that the fact that the Java libraries provide you with sorting methods (see also Collections.sort) means that most Java users will never need to implement their own Quicksort: you are doing it here as an exercise and because it is good for Computer Scientists to understand what goes on inside libraries, since next time around it may be their job to implement libraries for some new language. As a further optional extension to this exercise consider the following and adjust your code accordingly, then repeat all your tests: 1. The ML quicksort partitions items by comparing them with the value that happened to be first in the list. In the plausible cases where the original data is already in ascending or descending order this leads to excessive cost. Selecting as the “pivot” the median of the first, last and middle element from the array being sorted29 does better; 2. It is probably best to stop quickSort from recursing once it gets down to sub-arrays of length 3 or 4. The end result is that it almost sorts the array, but a final pass of bubble-sort can finish off the job nice and fast. Is this born out in your code? 3. The partitioning code here can be delicate! Unless you are careful it can escape beyond the bounds of the array, or it can get muddled about whether the two final values in the middle of the array need exchanging or not. Simple implementations can be made safe by making all the end-conditions in your loops composite ones rather like while (k>=i && v[k] > pivot) ... while if we could get away with it it should be faster to go something more like while (v[k] > pivot) ... 29 Always supposing there are at least 3 items in the list. 4.4. CONTROL STRUCTURES PART 2 93 Investigate how well you can trim down your inner loops while retaining code that always works! The Part IB course on Data Structures and Algorithms and the textbook by Cormen et al[9] are where this level of detailed study really belongs! (End of tickable exercise) Highest Common Factors Implement code to compute Highest Common Factors using the Euclidean Algorithm. Extend it to use the extended algorithms that at the end will allow you to solve equations of the form Au + Bv = 1 Tickable Exercise 5 The work called for here will be done in sections, and it is expected that while working towards the tick you will be able to design, code and test each section before moving on to the next. The idea involves creating a package of routines that can compute with (univariate) polynomials. For the purposes of this exercise a polynomial (a0 + a1x + a2 x2 + . . . + anxn )/b will be represented as an instance of a the class: class Poly { private String variableName; private long [] coeffs; private long denominator; ... constructors and methods as needed } where variableName holds “x”, the array called coeffs stores the coefficients a0 to an and the long denominator holds the value shown as b above. Because Java lets you enquire as to the length of an array it is not necessary to store the degree n explicitly. In this representation common factors should be cancelled out between numerator and denominator, and the highest degree coefficient an should never be zero. In this exercise all polynomials will be in terms of the same variable, x, so the variableName should always be set to "x" and it will not play much of a part in any of the calculations! Step by step carry out the following tasks, testing what you have done as best you can as you go: CHAPTER 4. BASIC USE OF JAVA 94 Create simple polynomials: Write functions that can create the “polynomial” that represents just a given integer, a given fraction and the simple polynomial “x”; Debug-quality printing: Write code that takes a polynomial and displays its coefficients. For this part of the exercise it is not at all important that the display format you design be tidy or that it respects line-lengths. So for instance you may generate output such as (1*xˆ0 + 0*xˆ1 + -3*xˆ2)/2 with various unnecessary symbols in there. The object is to be able to see your polynomials clearly enough that you can test and demonstrate what comes next! Special-case multiplication: Write code to multiply a polynomial by an integer, to divide it by an integer, and to multiply it by x. Note that in the first two cases you will need to do calculations (involving greatest common divisors30) to reduce the coefficients to lowest terms. In the latter case the result will be of one higher degree than the input and so will be represented with a coefficient vector one item longer. These routines should not alter their input, but should create new polynomial data to represent the results; Addition and Subtraction: Take two polynomials and create another that represents their sum (or difference). This involves more fun with ensuring that the result is over a common denominator, and subtracting two polynomials can lead to a result of lower degree if the leading terms cancel (as can adding if the leading terms start off as similar but with opposite signs); Multiply: If you have one polynominal of degree m and one of degree n then their product is of degree m + n. Write code that computes it; Differentiate: in fact differentiation of a polynomial by its variable is rather an easy operation (and so would be integration, which you would need in an optional extra to this exercise). If the polynomial contains an original terms ai xi then the derivative contains just (iai )xi−1 ; Proof of pudding part 1: Let P0 = 1, P1 = x and from there on define a sequence using the recurrence relationship Pn = ((2n − 1)xPn−1 − (n − 1)Pn−2 )/n 30 Otherwise known as highest common factor. 4.4. CONTROL STRUCTURES PART 2 95 Using your polynomial manipulation program calculate and tabulate the values up to (and including) P12 ; Proof of pudding part 2: Now instead define Pn = 1 dn 2 (x − 1)n 2nn! dxn (This is known as Rodrigues formula, in case you wondered, and the polynomials you are computing are Legendre Polynomials) Using this recipe compute and display values up to P12 . The two sequences of polynomials you have computed ought to match! Testing to destruction: extend your tables until the values computed by the two recipies for Pn are incorrect because of some internal integer overflow, and report where your program first displays a result that is certainly incorrect; Optional extra (a): Write code that evaluates a polynomial at an integer value of its variable, ie at x = n. Write code that computes the (indefinite) integral of a polynomial with respect to its variable. Combine these two to allow you to evaluate definite integrals. Display a table showing values of Z 1 x=−1 Pi (x)Pj (x) for i and j running from 0 to 5 (say); Optional extra (b): Let y be one of the polymonials (Pn ) that you have just computed. Evaluate (1 − x2)y′′ − 2xy′ + n(n + 1)y Tabulate this for various small values of n. Note: the examples worked with here are Legendre polynomials, and provide an example taken from Sturm-Liouville theory. Optional extra (a) shows that they are orthogonal over the range from −1 to +1 and this in fact makes them useful for producing certain sorts of good numerical approximations to functions. Optional extra (b) is showing you that these polynomials are solutions of a differential equation: many other interesting sequences of functions satisfy recurrence formulae, have orthogonality properties and are solutions to differential equations! Abramowitz and Stegun’s book of tables[1] is probably the easiest place to suggest you look to find out more. (End of tickable exercise) CHAPTER 4. BASIC USE OF JAVA 96 Pollard Rho integer factorisation In previous years this was Tickable exercise 5. There are in fact a few delicacies with regard to integer overflow (which do not greatly damage it as an exercise but which could raise questions about it). You may still like to try it! The explanation of this exercise is quite long, and it maybe looks messy, but I can assure you that the code that has to be written is tolerably short and managable once you have sorted out what needs doing. Randomised factorisation: Implement the following algorithm that (possibly) finds a factor of an integer that it is given: A single trial that looks for a factor of N is performed by selecting a random positive number R and computing S = R % N. This is a number between 0 and N − 1. If the number is 0 deem this trial a failure. Next compute the highest common factor of S and N. If this is 1 then again the trial is deemed a failure. However if the HCF is not 1 then it is a factor of N and because it is also a factor of S it must be less than N. This counts as success! The complete factorisation algorithm works by running a number of trials. √ If for a number N the first N trials all fail then we will pretend that N is prime. Otherwise a factor of it has been found, and dividing this into N gives us its co-factor. Smaller factors of each of these can then be sought using the same technique. Use this procedure to try to factorise the numbers 2i − 1 for i from 2 upwards, stopping when your program starts to take more than a second or so to run. The Birthday problem: Suppose we have a sequence of numbers all of which are less than N, and these values are generated in some way such that each number xn is some fixed function of xn−1. A concrete example would be if xn = (x2n−1 − 1) % N. For most N and for x0 = 2 this sequence31 is in fact pretty unpredictable. Any such sequence must eventually repeat a value, and once it has it necessarily continues in a loop. If consecutive values behave well enough as if they are random up to this point then the expected length of the sequence 31 There is a significant delicacy here: when you compute x2n−1 its value can be almost as large as even though the remaindering will rapidly bring it down to a smaller range. This can lead to integer overflow and a particularly un-wanted effect is that a value you generate may end up unexpectedly negative (when N 2 is outside the valid range of integers). I suggest you mostly ignore this here (!) and at most take an absolute value to ensure that the sequences you generate consist of positive numbers. Also there is not much special about starting with x0 = 2 and other randomish starting values might work just as well. N2 4.4. CONTROL STRUCTURES PART 2 97 before a repeat is related to the problem of how many people you have to have in a room before you should expect to find that two of them share a birthday. In this case the room is on a planet in a galaxy far far away, where the length of the year is N, and the statistics suggest that we need around √ N of our aliens. For this exercise you are to imagine one algorithm that detects a cycle and implement a second and much better one. The algorithm you just have to imagine guarantees to find a cycle as soon as it arises. It allocates a big array and stores values in this array as they are generated. As each one is generated it also checks through the ones already seen to see if the new value has occurred before, and if so declare the loop detected. This method is easy to visualise but it needs an array as long as the longest potential loop, and the search means that before finding a loop at step n it has done about n2 /2 comparisons with old values. This is slow. The second method, which you should implement, records the value of xi each time i reaches the next power of 2 and compares newly generated values against this one stored value. It argues that if there is a loop then eventually the loop will be totally traversed between consecutive powers of 2, and thus will be detected. Furthermore this will be at worst a factor of two beyond the place where the first repeat happened. Having coded the second loop-detection algorithms try it on sequences generated by xn = x2n−1 − 1 mod N for various N and verify that for a reason√ able proportion of values of N and x0 a loop is found after very roughly N steps. Pollard Rho: This builds on the previous two parts, so please do not start it until you have completed them. But then re-work the loop detection code so that instead of comparing each new xn with a saved value x2k using an equality test compute the HCF of N and xn − x2k . Stop if this is not 1, ie if a factor of N has been found. In the case when xn = x2k the method has failed: you may either give up in that case or try starting again with a different value for x0 . Implement this using the Java long type. If the number N is composite it is probable (although not guaranteed) that this will find a factor of N √ 4 within around N trials, and will thus be able to find a factor any Java long value quite rapidly. Of course if N starts off as a prime this scheme will never manage to find a factor of it! To test this you should probably create numbers that are known to be composite by multiplying together two int-sized values. 98 CHAPTER 4. BASIC USE OF JAVA Optional: The scheme above does not of itself find a complete decomposition of an integer into prime factors — it just splits composite numbers into two. A complete fatorisation method needs to extend it with first a filter so that numbers that are prime are not attacked, and secondly with recursive calls that try to factor the two numbers that Pollard Rho split our number into. Investigate the Java BigInteger class that provides arithmetic on long integers and which also provides a test for (probable) primality. Re-implement your code to use BigInteger rather than long and to use isProbablyPrime to avoid trying to factor when it is futile. Thus produce code that can produce complete factorisations of reasonably large numbers. How many digits long a number can you factorise in say 20 seconds? Some of you no doubt consider yourselves to be Java experts! You may like to arrange that the calculation x2n−1 − 1 mod N is computed exactly even when N is almost as large as a Java long can be, and that overflow does not interfere. An easy way to do this is to use the Java library big integer support, but what I would prefer here would be code expressed entirely in terms of use of long arithmetic. 4.5 Java classes and packages What has been described thus far should provide a foundation for understanding the small-scale structure of Java programs. If you have understood it you are equipped to write programs that have up to (say) half a dozen sub-functions and that are limited to living in a single source file. So far the data that Java can work with has been limited to the primitive types int and so on, together with arrays of them. The time has now come to discuss the Java idea of a class. This is used both to support the construction of user-defined data-structures and to impose an order on programs that are large enough that they should properly be spread across several source files. A discussion of Java classes will include an explanation of what all the “.” characters are doing in the sample programs seen so far. All of this counts as “Object Oriented Programming”. One of the aspects of programming language design that has proved to be especially important is that control of the visibility of names. This whole issue tends to look rather frivolous — a distraction — while your programs are only a page or two long but it makes a critical difference to big (and perhaps especially collaborative) projects. There are several interlocking reasons to want to keep name-spaces under control. One as so that a large chunk of code can be given a cleanly defined interface consisting of the functionality that it makes visible to the outside world. Everything not so exported is then deemed private to the group who maintain that body of code, and they may change internal parts of their design 4.5. JAVA CLASSES AND PACKAGES Figure 4.5: Classes and Packages make Java “modern”. 99 100 CHAPTER 4. BASIC USE OF JAVA with complete confidence that this can not hurt anybody else. A second reason for keeping name-spaces well controlled is so that different parts of a large program are free to re-use the most obvious names for their functions and variables, secure that this can not introduce unexpected clashes. Related to both of these is the fact that when trying to understand code limits on the visibility of names can allow you to concentrate on just the range in the code where something is relevant. Java controls access to names at three levels. At the finest grain it has scope rules that are much like those of most other programming languages. If a local variable is declared within a block that variable can only be referenced using code textually within that block. Java understands the idea that a re-declaration of a variable in an inner block would create a different variable with the same name, and that within the inner block the new variable would shadow the old one, as in int func(int a) { { int a = 4, b = 5; // ???? for (int a=0; a<10; a++) b++; // ???? System.out.printf("%d %d%n", a, b); } return a; } and it views this as something that could be codified and that to a computer has a totally logical interpretation. But that it is a potential cause of real confusion to human programmers so it should be prohibited! Thus the above example will be rejected by the Java compiler and all the interesting computer-science discussion of exact rules about scope can be set aside. You may like to note, however, the variables do not clash in any way if their scopes do not overlap, so the following is valid: int func(int a) { { int i = 4; for (int j=0; j<a; j++) i++; System.out.println(a + " " + i); } for (int i=0; i<10; i++) a *= 2; for (int i=0; i<a; i++) a--; return a; } The scopes associated with each declaration of i are disjoint. The other two aspects of Java name-space control are more interesting. The important words used here are class and package. 4.5. JAVA CLASSES AND PACKAGES 101 All names of Java variables and procedures32 live in some class. In general you have to gain access to the class before you can use its members33. A member of the current class can be referred to just by giving its unqualified name, but in other cases you need to have access to an object of the required class and refer to the member using a dot “.” as a selector on it. This is what was happening in cases such as g.drawLine where g was a variable of type Graphics and drawLine was a member of that class. When a class is defined the user can arrange which of its members can be referenced by other classes in this manner, so that internal details of the class can not even be accessed using this sort of explicit naming. The word public flags a component of a class that should be universally visible while private marks one that should not. Classes thus contribute in two ways to the avoidance of confusion over names. Firstly they mean that most references to things outside the current class will include a dot selector that indicates fairly explicitly what context the name is to be taken from, and secondly they can arrange that some names are kept totally local to the class within which they are used and can never be accessed from anywhere else. It is perhaps worth reminding you at this stage of the qualifier final that can turn a variable declaration into the definition of a constant. There are further refinements in the control of name visibility and use that Java provides, and the keywords protected, abstract and static relate to some of them: these will be discussed later on. Classes themselves have names, and so a scheme is needed to structure the name-space that they live in. A collection of classes can be placed in a “package”. When classes are declared only those that have been given the public attribute34 are visible outside the package. Furthermore since the idea is that any other Java code35 can access the public classes of a package there is a somewhat curious linkage between package names and the filing system on your computer. This linkage is mediated by a thing called the “class path” which can list the places that Java should search to find the compiled code if you refer to a class defined in some package. You can expect that any reasonable default Java setup will have your class path set up for you already so that you can access all of the standard Java libraries and so that code in the current directory can be used. The full names of classes generally contain dots. Various names starting with the component java are reserved for the system, and ones starting with sun are for use by Sun 32 From now on I will increasingly move towards the Java notation and call these “methods”. will see later that in some cases, when the name has been declared as static, one can refer to the item via the name of its containing class. But in the more general case it will be necessary to have an instance of the class and access the item via that. 34 Making a class public is a similar idea to making a member of that class public, but of course we are talking now about a different level in the structure of a program. 35 Ideally anywhere in the world! 33 We CHAPTER 4. BASIC USE OF JAVA 102 Computers, who designed Java. The various further parts to package names are intended to group packages into hierarchies. For instance every package whose name starts with java.awt is to do with the Java Abstract Window Toolkit, which is the part of Java that provides facilities to pop up windows on your display. The package java.awt.event is the sub-part of this that contains classes relating to events — we have seen an example where these could be caused by the user clicking with the mouse but there are others. The Java documentation contains a list of all the predefined packages that are part of the Java core, and then lets you browse the complete set of classes defined in each. Each class of course provides a number of variables and methods: the number of standard library methods is huge! Specifying full names in the package hierarchy could become very tedious, so Java provides a user-configurable way of setting up shorthand forms of reference. Recall that various sample programs we have seen began with a collection of import statements import javax.swing.*; import java.awt.*; import java.awt.event.*; This adjusted Java’s name resolution scheme so that the class MouseEvent (say) could be referred to by that short name. Without the import it would still have been possible to write the same program but it would have been necessary to use a fully spelt-out “java.awt.event.MouseEvent” to name the class, and that would involve knowing exactly which of the standard parts of the Java library MouseEvent belongs in. The “*” in the import statements show tells Java to support short names for all the classes in the packages named. It is also possible to put a single class name in an import statement. This could be useful if only one member of a package was to be used and you did not want to risk confusion with other names from that package. Some Java programmers take the view that import with a “*” introduces risk of giving them access to classes other than ones the know about, and so always spell their imports out fully, despite that being a little more verbose. Note that the syntax of Java only allows the wild-card “*” to be placed right at the end of an import where it means “all the classes in this package”. If you issue import statements that attach to two or more packages that define identically named classes then Java will refuse to get muddled: it just insists that you use fully spelt-out names for the classes that could otherwise be ambiguously resolved. This is probably safer than a scheme where the first (or possibly the last?) import statement took precedence. Java comes with around 200 and huge numbers of pre-defined classes, and so getting to know them all is a big job. You are not expected to do so especially 4.5. JAVA CLASSES AND PACKAGES 103 as future Java releases will add yet more, and it is probable that when you work on any big Java project you may find yourself using substantial third-party class libraries. But it does make a lot of sense for you to have a good overview of what is available to you so that when relevant you can use existing well-tested library code rather than starting to write something of your won. As well as being a way of organising the name-space all Java classes count as data-types. When something’s type is a class it is usual to refer to the thing as an “object”. Thought of in terms of objects, a class defines a data structure that contains fields that are the variables defined in it and happens also to be able to contain definitions of functions that will access these fields. If the variables were declared public then any code anywhere can access them and so there do not really have to be any (explicit) methods defined within the class. There will in fact always be a few implicitly defined ones that are to do with the creation and deletion of objects. Taking a minimal approach36 to class definition I can now set up a definition that would let me represent binary trees where each node in the tree contains an integer: // Compare the ML version, which would be // datatype Tree = // nullTree | // makeTree of int*Tree*Tree; // or some such. class BinaryTreeOfIntegers { public int value; public BinaryTreeOfIntegers left; public BinaryTreeOfIntegers right; } Comparison with the ML version reminds us that it is important to be able to have some way of telling when the left and right children of such a tree do not really exist. In ML that was achieved with an explicit alternative constructor, which I called nullTree. In Java any variable which has a class37 as its type can either hold a proper instance of that class (ie an object) or it can hold the special value null. This value is provided as a keyword in Java. Ie the word null is hardwired into the Java language and not just some curious pre-defined variable. It also has the odd property that the same value may be used with any sort of class 36 The class I define here would work, but it misses out on exploiting a lot of structuring and security features that classes can provide, and so is just a minimal start. 37 Or array. CHAPTER 4. BASIC USE OF JAVA 104 or array variable to set the variable to a state where it “does not hold an any object at all”. Once a class has been defined it will be useful to declare variables using it and create objects to go in them. Here I will create a rather small tree using the above class definition: ... BinaryTreeOfIntegers a1, a2, a3; // a1, a2, a3 are all un-initialised here, and // Java complains if you try to compile a program // that relies on the values of variables that // might not have been given a value. a1 = new BinaryTreeOfIntegers(); a2 = new BinaryTreeOfIntegers(); a3 = new BinaryTreeOfIntegers(); a1.value = 1; a2.value = 2; a3.value = 3; a1.left = a2; a1.right = a3; // the next 2 lines are not needed in that // null is the default value given to a field // that would hold an object. a2.left = a2.right = null; a3.left = a3.right = null; ... Note (but do not worry about, for now) the parentheses after the class name following new. And also observe how dreadfully clumsy all this is. Note that Java provides default initialisers for instance variable in classes and elements in arrays, but not for local variables within methods. The default values used are zero for numeric fields, false for booleans, ‘\0’ for characters and null for all references. Anybody who is a C or C++ programmer is liable to have a question to ask at this stage, but those who have mostly seen ML should see all this as reasonable. You can also see “.” being used as a selector to access the components of a class object. The C programmers can read my footnote38! Java objects are created in much the same way as Java arrays are, using new, and there is no need to take 38 In C or C++ one would distinguish rather carefully between a structure and a pointer to the structure. And in C terms all Java class variables hold pointers. However in Java it is not really useful to think this way since all Java operations have been designed to prevent any explicit tricks involving pointers. Please try to think of Java objects as more in the style of ML data. In C you the explicit visibility of the difference between a structure that is directly at hand and one that is referred to via a pointer leads to a distinction between the use of “.” and “->” to access 4.5. JAVA CLASSES AND PACKAGES 105 any special action when you have finished with one. The Java run-time system is expected to tidy up memory for you. However grossly excessive object creation can either consume time or utterly run you out of memory. The first loop in the following code does not do anything very useful with the objects it creates, and it discards them all rather rapidly. It may be a bit inefficient. The second loop creates a million objects and chains them all together so that none of space concerned can be recycled. At one million you may get away with this, but if you tried to do this a few hundred times more your computer’s memory would not be able to keep up with the demands of the program and an exception would be raised to report this fact. for (int i=0; i<1000000; i++) { BinaryTreeOfIntegers x = new BinaryTreeOfIntegers(); x.value = i; // x is implicitly discarded here } BinaryTreeOfIntegers w; for (int i=0; i<1000000; i++) { BinaryTreeOfIntegers x = new BinaryTreeOfIntegers(); x.right = w; // chain on to w w = x; } There are very few cases in Java where it would be considered good style to define a class that only had variables defined within it39. Mostly an attempt will be made to collect almost all of the methods that work with the class as part of it. Very frequently the variables in the class can then be made private, and the public methods provide a clean and abstract interface to everything. There is something of a convention about providing and naming methods to access the data stored in an instance of a class: methods that update variables have names starting with set, ones that retrieve boolean values start is while others that retrieve information start with get. Here is the previous example expanded to follow these conventions, and adjusted so that the case of boolean variables can be illustrated: components. Again Java does not need this and so only has one notation, even though in some sense it uses dot where a C programmer would naturally reach for an arrow. 39 The most plausible good case I can think of is when all the variables are marked as final so they are constants and the class is just used to encapsulate the name-space within which these constants are defined. CHAPTER 4. BASIC USE OF JAVA 106 // Compare the previous Java version where // the variables were public but there were // no methods. class BinaryTreeOfBools { private boolean value; private BinaryTreeOfBools left; private BinaryTreeOfBools right; public void setValue(boolean n) { value = n; } public void setLeft(BinaryTreeOfBools t) { left = t; } public void setRight(BinaryTreeOfBools t) { right = t; } public boolean isValueTrue() { return (value==true); } public BinaryTreeOfBools getLeft() { return left; } public BinaryTreeOfBools getRight() { return right; } } For small classes this just adds way too much extra verbiage and feels silly. However for a large and compilicated class with many other methods having a regular and predictable naming can be a real help. It also provides a way that you can give read-only access to some variables or you can check the sanity of values to be assigned to others, ending up with much finer-grained control over access than even use of the public and private qualifiers give you. The term “bean” is sometimes used for Java classes that follow this set of conventions, and some programming tools exploit it. Because it makes small programs so much bulkier I will not use this style in every example in these notes, but you can notice that many of the Java library classes clearly have and you might think about it again when you move on to writing large classes for yourself. Here is a sample Java class that might be useful within other programs and that illustrate methods that actually do something useful. It is a start at code that will enable Java code to work with complex numbers. An odd-looking programming style that it illustrates is one where to combine two complex numbers, say a and b, one will call a method associated with one of them, passing the other as argument. Thus the sum of the two values will be requested as a.plus(b). It is not possible (in Java) to redefine or extend the basic “+” operator to make it “add” objects from some new user-defined class, hence use of a method name such as add is 4.5. JAVA CLASSES AND PACKAGES 107 necessary here40. public class Complex { private double x, y; // define setX, setY, getX, getY here if you want. public Complex(double realPart, double imagPart) { x = realPart; y = imagPart; } public double modulus() { return Math.sqrt(x*x+y*y); } public Complex plus(Complex a) { return new Complex(x + a.x, y + a.y); } public Complex times(Complex a) { return new Complex(x*a.x - y*a.y, x*a.y + y*a.x); } } This would be placed in a file Complex.java and compiled using javac in the usual way to make a file Complex.class. Because I have not put in a package statement this class will live in a default package, and when other Java programs run and they want a class called Complex they might manage to find this one if its class file is still in the current directory. The Complex class illustrates one new concept. Observe the method definition that uses the name of the class as its own name and which does not specify a separate return type: public Complex(double realPart, double imagPart) { x = realPart; y = imagPart; } It has no return statement in it. A method whose name matches that of the class is a constructor and you will typically use it with new to create fresh instances of the class thing concerned. If you do not specify an explicit constructor function then a default one is supplied — it has no arguments and does not leaves all variables in their default state. It is valid to have several constructors provided that the types 40 In contrast the language C++ does allow you to extend the meaning of all the operators that are denoted by punctuation marks. Many people believe the conciseness and elegance that can be achieved that way is more then balanced out by the potential for severe confusion. CHAPTER 4. BASIC USE OF JAVA 108 of their arguments are different. Observe here how the methods that are members of the class all have access to the private variables, but no code outside the class will have. Sometimes when referencing a variable it is useful to stress that you are talking about one in the current instance. The keyword this always refers to the object from which you invoked a method, and so the constructor and the plus methods above could have been written out in a way that some would consider clearer: public Complex(double x, double y) { this.x = x; this.y = y; } public Complex plus(Complex a) { return new Complex(this.x + a.x, this.y + a.y); } Explicit use of this can be used to avoid mixups if the name of a formal parameter for a method matches the name of a variable in the class. Consider the following and the muddle that would arise without the use of this, but also note how much nicer it is to select names that avoid any hint of a clash. public Complex plus(Complex x) { return new Complex(this.x + x.x, this.y + x.y); } 4.5.1 Exercises Complete the Complex class The class as shown here does not support division, and does not have an equality test. If you define a method called toString() in it then will be called to “print” the number when you use “+” to concatenate it with a string. Finish off the Complex class adding in these and whatever other facilities you feel will be generally useful. Polar Complex Numbers The Complex represents complex numbers in Cartesian form, ie as x + iy. But the internal variables x and y that it uses are both private so nobody outside the class can tell this! An alternative representation of complex numbers would store a number as a pair (r, θ) where the complex value concerned had modulus r and argument θ. In other words one would have z = reiθ . At the cost of computing a few arc-tangents and the like it is possible to create a re-worked complex 4.5. JAVA CLASSES AND PACKAGES 109 class that has exactly the same external behaviour as the original one but which stores internal values in polar form. The constructor function and addition become messier, multiplication becomes easier and the modulus function becomes utterly trivial. Implement and test the polar version of the class. Wolves and Caribou On a certain island there live some wolves and some caribou. In year n there are obviously wn wolves and cn caribou. What happens the next year depends. . . • Wolves hunt, and the total number of dinners they get is proportional to wn kn . The number of baby wolves is automatically proportional to the number of dinners their (potential) parents are able to eat over and above the amount needed to keep the parents active. The wolf minimum feed intake and reproductive capability may be modelled as wn+1 = wn + k1wn (cn − k2) • In each year the stock of caribou would increase by a factor k3 were it not for the depredations of the wolves, since each dinner for a wolf is one less member of the herd. Thus cn+1 = k3 cn − k1 wn cn • Baby wolves eat, hunt and reproduce as from year n + 1, and there are no losses of caribou other than as described above. In particular we do not have to worry about over-grazing etc. At the beginning of time the island is stocked with a herd of 100000 caribou, and a medium-sized pack of ravening wolves. Over a number of years various things could happen. Either wolves or caribou or both could die out, or the populations could stabilise. For some values of the constants and initial wolf population various of these do indeed occur. For instance if at the start there are twice as many wolves as caribou the next year there will only be wolves left (one should adjust the equations given so that negative populations get turned into zero ones), and the year after that the wolves all expire of hunger. In fact for many configurations the populations do not stabilise, but they do often get locked into stable cycles that last several years. This improbable situation has been observed by real naturalists not only in the situation described here but with regard to disease spread (mumps and children say) and other natural systems. Write java code to investigate. 110 CHAPTER 4. BASIC USE OF JAVA Packages and jar files Make a sub-directory called (say) ex251 and put some Java source files there. Put package ex251 at the top of the files. Now compile the code, eg saying javac ex251/*.java. By unless you explicitly set a classpath Java looks for classes that are in a given package by using the package name as if it described a chain of sub-directories down from the current directory. So now set up several different packages and create files that illustrate the use of protected and others that fail to compile because you have not made allowance for suitable cross-package visibility. Now look up about jar files and prove to yourself that you can take a complete Java program (consisting of many classes) and consolidate it into a single (jar) file that can then let anybody else run it in a simple and convenient way. These activities are not essential for any of the example programs that you have to write for this year’s Java course, but starting to investigate and practise now will put you in a good position for some of next year’s work, and particularly the Group Project. I am also aware that this exercise is asking you to read ahead in these notes. . . Tickable Exercise 6 The following definition of a paint method uses the rudimentary Complex class as shown earlier in this section. The Mandelbrot set can be drawn by considering the sequence defined by z0 = 0 and zn+1 = z2n + c where both z and c are complex numbers. For most values of c eventually values of zn become large. If one counts and finds the smallest n such that |zn | > K for some suitable K then that n will depend on the value of c that was used. The well-known pictures arise by using different colours to display the values of n associated with values of c = x + iy as x and y vary. Because drawing this involves a significant calculation for every single point within the applet’s window it can be painfully time-consuming. To arrange that the screen looks more interesting this 4.5. JAVA CLASSES AND PACKAGES 111 code arranges to draw a crude blocky version first and then gradually refine it into the correct high-resolution image. You may have seen some web browsers do similar things to give better apparent responsiveness when loading and displaying pictures from web sites! The code draws a part of the Mandelbrot set centred around (midX, midY) and with width range, these referring to the values of the constant c in the iteration. If the value of z has not grown large within LIMIT steps it is supposed that it never will. The code illustrates use of the Color class. Colours are sometimes specified in terms of the amounts of Red, Blue and Green that go to make them up. Printers will tend to think in terms of Cyan, Magenta and Yellow41 while in yet other circumstances one uses Hue (running through the colours of the rainbow), Saturation (eg white through pinks up to a full-blooded rich red) and Brightness (all colours fading to black at zero brightness, just as all wash out to white (or grey) at zero saturation). Insert this program in a suitable framework and investigate other areas of the display by altering the relevant variables. You should be aware that if you increase the screen size or LIMIT the code can become very time-consuming. Indeed it might very well be sensible while testing to decrease the finest resolution used to say 8 rather than 1. And because the paint method computes the whole picture each time it is called any disturbance of the screen is liable to provoke a complete re-calculation (at great cost). I find that the appletviewer does not exit until the end of a call to paint() and so even quitting from it can involve an amazingly long delay! public void paint(Graphics g) { // I Paint first in crude 16*16 blocks and then // in finer and finer tiles. This is so that // SOMETHING appears on the screen rather rapidly. for (int resolution=16; resolution>=1; resolution/=2) { double midX = -0.25, midY = 0.85; // Adjust these double range = 0.004; // Adjust this int screenSize = 400; // Match .html int s2 = screenSize/2; for (int y=0; y<screenSize; y+=resolution) for (int x=0; x<screenSize; x+=resolution) { int n = 0; int LIMIT = 250; // Maybe adjust this? Complex z = new Complex(0.0, 0.0); Complex c = new Complex((range*(x-s2))/s2 + midX, (range*(y-s2))/s2 + midY); // Important loop follows. 41 Printing inks favour analysis in terms of subtractive colours rather than additive ones. CHAPTER 4. BASIC USE OF JAVA 112 while (n++ < LIMIT && z.modulus() < 4.0) { z = z.times(z); // z = z * z; z = z.plus(c); // z = z + c; } // Draw in black if count overflowed if (n >= LIMIT) g.setColor(Color.black); // ... otherwise select a colo(u)r based on // the Hue/Saturation/Brightness colour model. // This gives me a nice rainbow effect. If // your display only supports 256 (or fewer) // colours it will not be so good. else g.setColor(Color.getHSBColor( // cycle HUE as n goes from 0 to 64 (float)(n % 64)/64.0f, // vary saturation from 0.2 to 1.0 as n varies (float)(0.6+0.4* Math.cos((double)n/40.0)), // leave brightness at 1.0 1.0f)); // screen coords point y downwards, so flip to // agree with norman human conventions. g.fillRect(x, screenSize-y, // posn resolution, resolution); // size } } } Complete the program based on the above and test it. Next check Graphics.getClipBounds and Rectangle.contains in the Java documentation. Adjust the program so that when paint is called it first finds the clip rectangle associated with the re-paint operation. This is a rectangle on the screen such that only points within this area need to be re-displayed. Arrange that the loop on x and y that at present re-computes the colour for every point on the whole screen just loops round doing nothing for points outside the clipping rectangle and so only does the expensive operations for points inside it. Try the new version, and in particular move other windows to obscure small parts of it and then move them away so you can see the effect of the partial re-draw operations. Note that the above program will display best if your screen is set up to support lots of colours. On a display with either 16-bit colour (65536 colours) or truecolour (24 or 32 bit) and at high resolution the effect is fairly stunning. If only 256 colours are supported the shapes will remain nice and wiggly but the delicate shading will be lost. While preparing these notes I have adjusted the program 4.5. JAVA CLASSES AND PACKAGES 113 to display a 1200 by 1200 image at best-possible resolution in 16-bit colour, and although it takes utterly ages for the screen to refresh I think it is almost worth it! The program that I give has a bug that you can see if you watch carefully when it re-paints at the various different resolutions. It relates to the fact that in Java the x-co-ordinate increases from left to right (as expected) but the y-co-ordinate is zero at the top of the screen and largest at the bottom. Identify and correct the behaviour that I count as a defect. Optional: Add a BufferedImage to make the re-painting of the screen cleaner. I might like to be able to reset the view to some standard one at the click of a mouse, and to be able to drag with the mouse to select a sub-part of the current picture for zooming in on. Those who are feeling keen can investigate these possibilities. There is also quite some incentive to find ways of speeding up drawing of the images here! (End of tickable exercise) Fractions Create a class similar to the Complex one but that implements rational numbers, is fractions. You will probably want to make the internal representation a pair of long values rather than just int, and keep everything reduced to lowest terms by cancelling out highest common factors. Series for tan(x) It may be well known that 1 2 17 7 62 9 tan(x) = x + x3 + x5 + x + x +... 3 15 315 2835 but fewer people are happy about being able to predict what the next few coefficients in the expansion are. However if we have a computer program able to compute with rational numbers it is in fact easy to generate as many more coefficients as are desired. The coefficients satisfy a recurrence formula t0 = 0 t1 = 1 1 n−1 tn = ∑ titn−i−1 n i=0 Use this to confirm the series as I have tabulated it and display the next few terms. The result here may be derived from the fact that the derivative of tan(x) is 1 + tan2 (x), and is also related to (but rather harder than!) the discussion of “generating functions” in the probability course. CHAPTER 4. BASIC USE OF JAVA 114 Complex elementary functions Perhaps you already did this when making your complete version of the complex numbers class. . . It might be useful to be able to construct complex numbers either by specifying real and imaginary parts or by giving argument and modulus42. Failing any better scheme you could distinguish between the two constructors by adding declarations public static final int CARTESIAN = 0; public static final int POLAR = 1; in the Complex class and then having the constructor take an extra argument that specifes which option is being used. It would then also make sense to provide data access methods that make it equally easy to access the number in polar or cartesian interpretation. If that is done it becomes reasonably easy to support complex versions of several of the elementary functions. Observe the identities: p √ iθ/2 reiθ = re iθ log(re ) = log(r) + iθ exp(x + iy) = exp(x)eiy sin(z) = (exp(iz) − exp(−iz))/2i cos(z) = (exp(iz) + exp(−iz))/2 pq = exp(q log(p)) The expressions for sin and cos can be inverted to find ways of writing the inverse trigonometric functions as messy complex logarithms. And it may be seen that the neatest way of using these formulae to implement complex-values versions of the elementary functions really does benefit from being able to slip very comfortably between the cartesian and polar views of the values. Implement it all. I should observe carefully that the code you have just written is liable to be a very long way from the last word in elementary function libraries, for the following reasons, which are given in descending order of importance: 1. Several of the complex-valued elementary functions have branch-cuts. For instance the square root function has a principal value which is discontinuous as you cross the negative real axis, and the various inverse trig functions will also have cuts. Your code can not automatically be assumed to implement these cuts in the way that will be considered proper by experts in the field. Probably the most readily accessible description of which cuts are desirable is in Common Lisp, the Language by Guy Steele[21]; 42 Ie by giving the polar version. 4.6. INHERITANCE 115 2. Your implementation will probably suffer from arithmetic overflow (and hence give back answers that are infinities or NaNs) substantially before the desired result would overflow. For instance the identity given for cos computes an intermediate result that is twice as big as the final answer, and hence can suffer in this way thereby returning incorrect answers; 3. In many cases the naive use of the formula given can lead to serious loss of numerical accuracy when values of similar magnitude are subtracted one from the other. For instance this problem would arise in the calculation of sin(z) for z near zero; 4. Direct use of these formulae will not even give an efficient set of recipes for the desired functions! however the numerical analysis to address these problems is certainly beyond the scope of this course. Binary Trees Start from the BinaryTreeOfIntegers class sketched above and extend it so that as well as defining variables in the class it provides a set of methods to work with them. The methods you introduce should arrange that any binary tree built is always structured so that all integers stored in the left sub-tree that hangs off a node are smaller than the integer in the node itself, while all integers in the right tree are greater (or equal). You should provide a constructor that creates such a tree out of all the integers in an integer array, and another function that first counts the size of a tree, then allocates an array that big and finally copies all the integers back into the array so that they end up in ascending order. I would fairly strongly suggest that you design and implement the key parts of this in ML before you move on to the Java version. Your code is an implementation of tree-sort: you should compare it with quicksort for clarity, amount of code that has to be written, robustness (ie are there any truly bad sorts of input it can be given) and performance. 4.6 Inheritance There is one more major feature of the Java class mechanism. It provides yet further refined control over name visibility and it can often be a huge help when organising the structure of large projects. It is called inheritance and the idea of it is to allow the user to define new classes as variants on existing ones. When this happens the new class starts off with all the components and methods of the one 116 CHAPTER 4. BASIC USE OF JAVA upon which it is based, and it counts as having defined a sub-type. It can however define extra variables and/or methods and implement more specialised versions of some of the methods already present in its parent class. This is what has been happening every time we have used the word extends, and so for instance every applet we have written has defined a new class extending the library class Applet. This library class implements all the major functionality for getting a window to appear, and to get the visual effects we wanted all that was needed was to provide our sub-class with its own version of a paint method. There seem to be three interlocking reasons why inheritance is important when large programs are to be written: 1. Class libraries can be provided in forms that implement all the generic behaviour of really quite complicated programs, but by making a new program that inherits from such a class and that overrides some of its methods lots of flexibility is left for the programmer to create a system that does exactly what they want. Prior to languages that supported inheritance there was a severe conflict between having libraries that contained large enough components to give large time-savings and those that were adaptable enough to be realistically useful; 2. Class inheritance serves a linguistic purpose in Java. If you start from a single base class it is possible to derive several other classes from it. All these count as specialisations of the original one, and a variable capable of holding a member of the base class can therefore automatically refer to instances of any of the derived ones. This is how Java can support data-structures that can have several variants. Furthermore the name-visibility rules in Java can use the way in which inheritance groups classes into families to further refine access to class members. 3. It often becomes possible to implement a set of basic classes first, and test them, and then leave those alone (and hence stable) while deriving new classes that add extra functionality. This both provides a respectable strategy for organising system development, and means that there is a significant chance that the basic classes that are developed will be useful in the next project; I will try to illustrate these three points in turn. 4.6.1 Inheritance and the standard libraries The richest and most valuable place where this happens in the libraries relate to applications that pop up windows. Examples given before show user code being 4.6. INHERITANCE 117 derived from a class called Applet. One of the things that has been seen about Applet and hence any class derived from it is that the method paint has a special status, in that it is invoked whenever the screen needs to be refreshed. The fact that by deriving a new class you get an opportunity to write your own paint method and that in your new class your own definition takes the place of a standard one (which probably does nothing much!) is obviously critical. If you could not alter the re-painting behaviour of an applet the whole structure would lose its point. If you look at the documentation for the Applet class you will find that it is listed as having around a couple of dozen associated methods. Each of these will define a default behaviour for an Applet and each can be replaced43 in a derived class if some special behaviour is needed. However these two-dozen methods are very far from being the whole story. For instance paint is not listed among them. This is because Applet is descended from java.awt.Panel which in turn is derived from java.awt.Container which itself inherits from java.awt.Component and java.lang.Object. Each of these super-classes define (often many) methods of their own. The lower-down ones sometimes replace a few of the higher level methods with more specialised versions, but they also tend to provide lots of new methods of their own. Thus in this case the paint method is defined as an aspect of a Container, and is only part of Applet via inheritance. The end effect is that something that is as easy to get started with as an Applet in fact comes complete with perhaps hundreds of bits of pre-defined behaviour almost any of which can be adjusted by the simple expedient of overriding some method. Sometimes of course this arrangement whereby library facilities are structured into hierarchies of classes means that the very simplest thing one might want to do involves explicit construction of objects from various classes in a way that looks less smooth. To print simple text as the output from a simple Java stand-alone application one can invoke System.out.println. The long name is because System is a class (its full name is java.lang.System), and out is then a variable in that class. The field out has as its type PrintStream and the class PrintStream provides a method called println. It is possible to reference the variable out just by giving its class (without having to have a variable whose type is that class) because it was defined as being static. The recipe as typed in by the programmer is not too bulky but the full explanation of why it works is a bit clumsy. “Simple” input is if anything worse. There is a static variable System.in which is of type InputStream, and for an application to accept input from the keyboard one needs to use it. However the class InputStream only provides the most basic reading functions, and various derived classes are needed if flexible, efficient and convenient reading is to occur. A suggested protocol for a single 43 The fuller story is that any member of a class that has been marked as final can not be redefined in a derived class. The use of final thus provides the designer of a class with a way to guarantee some aspects of class behaviour even in derived classes. CHAPTER 4. BASIC USE OF JAVA 118 integer from the standard input ends up something like BufferedReader in = new BufferedReader( new InputStreamReader(System.in), 1); int n; try { n = Integer.parseInt(in.readLine()); } catch (IOException e) { n = -1; } catch (NumberFormatException e) { n = 0; } System.out.println("I got: " + n + "...."); This creates an InputStreamReader out of System.in, and then builds from that a BufferedReader where here I have indicated that a buffer size of 1 should be used. For reading directly from the keyboard a ridiculously small buffer size means that the program gets characters as soon as they are available. If the “, 1” was omitted the BufferedReader would use some default buffer size and you would have to have keyed in that many characters before anything ever happened! The BufferedReader class then provides a readLine method, and the string that it returns can be interpreted as an integer by the static method parseInt in the Integer class. Both readLine and parseInt may raise exceptions if anything goes wrong, and so a proper program should be prepared to handle these. The above tends to look very heavy-handed because “real” programs will generally want to decode much more complicated input than just the single number shown above, and will really need to put in the catch clauses so that they can respond cleanly to erroneous input. Even the buffering control is really quite important — direct keyboard input may need to be unbuffered so that interaction works well while input of large amounts of input from a file may be much faster if buffering is used. Java in fact provides another rather larger class than BufferedReader which may be useful in many applications that want to accept free-format input. This is the class java.io.StreamTokenizer44 which can help you read in a mixture of numbers and words. Here is a demonstration: import java.io.*; 44 Actually I think that StreamTokenizer is very useful while you are getting started, but although it can be customised quite substantially it is not flexible enough for most really serious uses. In the Compiler Construction course in Part IB you may learn about a package called JLex that is harder to set up but which provides enormously more power and flexibility. 4.6. INHERITANCE ... StreamTokenizer in = new StreamTokenizer( new BufferedReader( new InputStreamReader(System.in), 1)); in.eolIsSignificant(true); // see newlines in.ordinaryChar(’/’); // ’/’ is not special in.slashSlashComments(true); // ’//’ for comment try { int type; // The next line loops reading tokens until end of file. while ((type = in.nextToken()) != StreamTokenizer.TT_EOF) { switch (type) { // There are a number of predefined "token types" in // StreamTokenizer, so I process each of them. case StreamTokenizer.TT_WORD: System.out.println("word " + in.sval); // If the user says "quit" then do so. NB "break" only // exits the switch statement here. if (in.sval.equalsIgnoreCase("quit")) break; continue; // in.sval and in.nval get set when string or numeric // tokens are parsed and contain the value. case StreamTokenizer.TT_NUMBER: System.out.println("number " + in.nval); continue; // the method lineno() tells us which line we are on. case StreamTokenizer.TT_EOL: System.out.println("start of line " + in.lineno()); continue; // quotes and doublequotes contain strings. case ’\’’: // drop through case ’\"’: System.out.println("string " + in.sval); continue; // Other characters end up here. Eg +, - etc. default: 119 CHAPTER 4. BASIC USE OF JAVA 120 System.out.println("sym " + (char)type); continue; } break; // here if "quit" typed in } } catch (IOException e) { System.out.println("IO exception"); } The level of complexity here seems much more reasonable! The initial code that sets up a StreamTokenizer is not very different from that which set up the simpler buffered stream before, and is clearly a small overhead to pay to be able to have Java split your input up into words and numbers. The StreamTokenizer provides methods that allow you to customise its behaviour so that it can recognise one of several possible styles of comments and accept various string delimiters. The calls in.eolIsSignificant(true); // see newlines in.ordinaryChar(’/’); // ’/’ is not special in.slashSlashComments(true); // ’//’ for comment illustrate a little of this. The first call tells the tokenizer that newlines should be returned to the caller. By default they are counted as whitespace and so not passed back. The second call makes a single / into an ordinary character, where by default it introduces a comment if followed by a second / or a *. The final line enables recognition of comments that are started by //. As always you need to browse the full documentation to discover what all the other options are! Two lessons emerge. The first is that the bigger and more powerful classes in the Java libraries may really save you time if you find out how to use them, while direct use of very low level facilities may end up feeling pretty clumsy. The other is that these high level facilities are often very flexible, but if you need some feature that they do not support you may have to drop down a level. For instance StreamTokenizer does not know how to handle numbers expressed in hexadecimal or octal, and it always reads numbers in type double which is not good enough if what you needed was a long value. 4.6.2 Name-spaces and classes When you derive one class from another it is sometimes desirable if the methods and fields of the base class are visible in the derived one, but in other cases it may not be. This aspect of name visibility needs to be considered in conjunction with the consequences of classes falling into different packages. Java confronts all this by defining four levels of name visibility within classes: 4.6. INHERITANCE Figure 4.6: Classes and inheritance are a sort of magic. 121 CHAPTER 4. BASIC USE OF JAVA 122 private: is the most restrictive one. A method or variable that has been declared as private can be referenced from within the class in which it is defined, but not from anywhere else. In particular code that is in another class can not see it regardless of whether the other class is in the same package as or was derived from the original one; package: relaxes things so that code in any class that is in the same package can reference a value. This is the default arrangement, and is indicated by not using any of the other visibility qualifiers. Note that the keyword package is used at the head of a file to specify which package that class will reside in, and it is not valid in method or variable declarations; protected: When a name is declared as protected it becomes visible in derived classes even if they are in other packages. Because during this first course you will probably not be creating new packages yourself this case will mostly be relevant where a library class has some protected members and you derive a few class from it. Your class will probably be in the default package but despite that you will be able to access the members involved; public: is the final case, and it makes names generally available regardless of packages and inheritance. It seems tidy to document the other possible qualifiers for declarations here, even though they are not concerned with name visibility. Indeed their consequences are rather mixed, and since this is a first Java course it is not essential to be fully comfortable with them all. final: When a variable is declared final nobody will be allowed to assign a new value to it. When a method is final then it can not be overridden in any derived class. In both cases the effect is to make the definition in its visible form the one that can be relied upon everywhere else; static: The default situation for items defined within classes is that the items only come into existence when an object of the class-type is created. This makes obvious sense for data fields. For instance after the declaration class IntList { public int head; public IntList tail; } it is clear that the only context in which the head and tail fields can be used is in association with an object of type IntList as in 4.6. INHERITANCE 123 int sum(IntList x) { int r = 0; while (x != null) { r += x.head; x = x.tail; } } For consistency the same access rule is then applied to member functions (ie methods) in a class. If however an item in a class has been declared static it is as if a single globally allocated instance of the class gets created automatically, and the field can then be referred to relative to just the class name. For instance (a nonsense code fragment!) class MyConsts { static final double ZETA2 = 1.6449340668482264365; static final double CATALAN = 0.91596559417721901505; static int square(int x) { return x*x; } } ... double a = MyConsts.CATALAN Myconsts.ZETA2 + (double)MyConsts.square(1729); ... abstract: Sometimes it is useful to define a base class not because it is useful as such, but because the various other classes that get derived from it might be. Consider the ML declaration datatype option = A of int | B of double; One way of producing a Java equivalent would be to start by defining a rather vacuous class called Option and then deriving from it two new classes one to correspond to each of the two cases in the ML version: abstract class Option {} class OptionA extends Option { int a; } 124 CHAPTER 4. BASIC USE OF JAVA class OptionB extends Option { double d; } The base class here only exists to be extended, and it would be silly to create an object that was of that type45. The qualifier abstract prevents anybody from creating objects of the base class. It marks things that must be inherited from before meaningful use can be made of them. In cases such as this it is often useful to discriminate as to which derived class a particular instance belongs to. The instanceof operator can be used to do this. Again my illustrative code is artificial: Option x = new OptionA(); // or maybe OptionB? ... if (x instanceof OptionB) ... else ... It is very often neater and easier to define different overridings of a common (abstract) method in the two derived classes so that the correct behaviour is achieved for each. If that is done46 the if statement and use of instanceof could be replaced by a simple call to the method concerned. It is of course not essential to make a base class in such examples abstract, but doing so prevents any possible embarrassment if some code created an instance of it in its raw and useless form, so it is generally considered to be good style. native: If a method is defined as native then Java somehow expects there to be an implementation of it that was coded in some language other than Java. This can be used by system builders to interface Java code down to lower level and perhaps machine-specific system calls, but will not be discussed further in these notes. synchronized: related to Java code where several threads of computation may be active at once. Although the very basic aspects of this will be covered in this course a proper treatment needs to wait until you have had a Part IB course on concurrent systems. interface: The keyword interface is not a modifier for use in class definitions but a keyword whose use is very much like that of class. An interface 45 Of course objects of type OptionA and OptionB are also of type Option, so what I mean is it would be silly to go new Option(). 46 A similar stylistic issue arises in ML where user of pattern-matching in function definitions can often reduce the number of explicit if statements that have to be written. 4.6. INHERITANCE 125 can be declared much as an abstract class is. Classes can be defined to extend other classes, but a restriction that Java applies is that a new class can only be an extension of a single parent class. Interfaces provide an approximation to being able to extend several parent classes — a new class can specify that it implements one or more interfaces. When a class indicates that it will implement an interface it has to contain (concrete) definitions of all the (abstract) methods that the interface specifies. At (very) long last we have covered all the magic that arose in the initial Hello.java program and can see what each keyword present there was indi- cating. 4.6.3 Program development with classes In Java, as in other Object Oriented languages, the whole shape of a large program needs to be designed in terms of terms of the packages and classes that will be built. It is worth putting particularly careful thought into the way in which hierarchies of classes will be derived from one another via inheritance. There are two application areas that were pioneers in illustrating the benefits and strengths of object oriented programming (which is what this is). It can thus be worthwhile considering examples of these as some of the earliest ones you work with when getting used to the idiom. The first application area was that of simulation47, while the other was graphics and especially the display of geometric figures in windows. The following example, which is taken from Java in a Nutshell and shows how use of several classes rather than just one may allow the programmer to keep distinct aspects of their task separate. But doing this the size of unit that has to be debugged is reduced, and the possibility of re-using parts of the code later on in another project is increased. The example supposes that a graphical design and modelling package is being written. Within it it will keep data-structures that represent circles, squares and other shapes. For much of its time it will work on these busily computing their areas, their circumferences, whether they intersect and similar properties. It may also adjust their sizes and positions. As well as performing all these calculations the complete package will also have a user interface that can draw the objects. There will be options to control the colour of each individual circle (and so on) as well as to determine whether the items are drawn just as outline figures or as filled-in shapes. Without use of inheritance and thus without serious use of the Java class mechanism the code would probably have to consist of a single class, say called Shape, which would contain a master variable indicating what sort of shape was involved, 47 Indeed the way that object-oriented C++ developed from the simpler language C was initially specifically for use in this area. 126 CHAPTER 4. BASIC USE OF JAVA Figure 4.7: See also the “Software Engineering” courses. 4.6. INHERITANCE 127 then other variables that could be used to specify the exact parameters of that shape (eg its radius if it was a circle). The method functions such as area would need to dispatch on the type of the figure and do different calculations in each case. Further code would arrange to be able to draw pictures to represent the data. All the geometric and graphical parts of the code would be in the same class and thus the same source file — something which would not cause trouble in tiny cases but would become clumsy for a fully elaborated version. With inheritance it would be natural to start with a basic class (again I will call it Shape) which will probably be abstract. Its purpose is to allow the program to declare variable of type Shape and then store circles, squares, stars and all other possible sorts of shape in that single sort of variable. The methods declared for Shape can be given as just declarations, rather than as full definitions: public abstract class Shape { public abstract double area(); public abstract double circumference(); } which makes these methods available in any object of class Shape but expects that concrete variants on the class will provide the real implementations. For each sub-class of Shape a new class could then be derived: class Circle extends Shape { protected double radius; public Circle() { radius = 1.0; } public area() { return Math.PI*radius*radius; } public circumference() { return 2.0*Maths.PI*radius; } public double getRadius() { return radius; } } Note that this can introduce new public members that are not relevant for general Shape quantities, but which do make sense when you know you have a Circle. Next an interface would be set up, defining the methods relevant for draw48 ing things on the screen: 48 Java is an American language, and so the character of being Red, White or Blue is Color rather than Colour. Given that the library uses this spelling it seems best to swallow nationalistic pride and adopt it elsewhere in the code. . . 128 CHAPTER 4. BASIC USE OF JAVA public interface Drawable { public void setColor(Color c); public void draw(Graphics g); // etc etc. } Now it is reasonable to derive a new class for a version of each sort of shape but in a form that supports the drawing operations: class DrawableCircle extends Circle implements Drawable { Color c; public void setColor(Color c) { this.c = c; } public void draw(Graphics g) { ... // whatever, maybe g.drawOval(...); } // etc etc. } It is now possible to use the drawing methods as well as the data manipulation methods in one of these ultimate data-structures. Often when producing a derived class and overriding a method the newly extended method needs to use the corresponding operation from its parent class. For instance if a class defines a method that is used to initialise its variables then a derived class may add extra variables that need initial values too, but it would be clumsy to insist that it also had to repeat all the code to setup the variables in the base class. And indeed if some of those were private or protected it might not be able to. The solution is hidden in the keyword super. This is a bit like this in that it always refers to the current object, but it views it as a member of the immediate parent class. Thus code like class SubClass extends MyClass { private int variable; public void init() { super.init(); // init as a MyClass variable = -1; // finish off as SubClass } } and the word super is only of relevance when extending a class and overriding methods. In the case of some library classes and methods the documentation will 4.7. GENERICS 129 explain to you that you must use it, see for instance the method paint in the class Container. 4.7 Generics The material here is now for Java 1.5 and I expect my coverage of it to grow over the next year or so. This year I will do hardly more than just mention it and let Part IbB coverage consider filling in the gaps. This seems especially reasonable since textbooks that catch up with this are still somewhat rare. In ML you got used to having types that were polymorphic. For instance a sort function that took a predicate and a list might have had type (α ∗ α → bool) ∗ αlist → αlist to indicate that the elements of the input and output lists had the same type and the ordering predicate was compatible with that. A particular feature of ML to recall is the availability of parameterised types such as αlist. In Java instead of saying “type” we will say “class”, and instead of saying “polymorphic” we say “generic”. A generic class is established by putting type variables within angle brackets. You can then use the type variable within the class as if it were a regular type name: small class MyClass<E> { E myMethod(E arg1, int arg2) { MyClass<String> newvar = ... ... } } With your ML experience of polymorphism you can now probable see at once how to use this capability to write implementations of various generic data structures (trees, lists and the like) and provide useful functions that traverse, search or sort them. In fact that Java libraries have done a great deal of that in their so-called Collection classes. In ML polymorphism is all-or-nothing. If you have a type-variable α it can stand for absolutely any ML type. To improve security you may sometimes like to have a way of expressing more limited flexibility (eg generic over all sorts of numbers, but not over non-numeric data). Java provides a capability using a type wildcard written as question mark, and can limit the range of the wildcard using notation like CHAPTER 4. BASIC USE OF JAVA 130 public void sum(List<? extends Number> arg) { for (n:arg) { ... } } Here the sum method takes an argument that is some sort of List49 but it insists that the polymorphism that List provides has been used in a way that means you know that all the objects in the list are some subclass on Number. You will use generics every time you use the Java Collection Classes. You can use it in your own code too. There is a fair amount more that I could say about exactly how it interacts with the type-hierarchy that class inheritance provides and when a generic class is a sub-class of another, but I believe that the details there do not belong in a first Java course! 4.7.1 Exercises Objects everywhere The Java libraries make extensive use of classes in hierarchies (and also a more modest number of interfaces). The arrangement in the basic set of classes is that everything is ultimately descended from a base class called Object. The most immediate consequence is that an object of any class from the basic libraries may be stored in a variable of type Object. It is exactly as if whenever you define a new class and do not give an explicit extends clause as part of its definition Java just sticks in “extends Object” for you. Of course when you extend some other class it in turn will somehow have Object as an ancestor-class so this way as previously stated every instance of any class is an Object. A few basic methods are defined for Object, of which perhaps the most interesting at present is getClass which returns an thing from the class Class. If x is any Object then x.getClass().getName() is a string that is the name of the class of x! The general parts of the Java libraries that allow you to investigate the classes that Objects belong to and then retrieve lists of the variables and methods that they provide are referred to as Reflection: as it were a Java program can look at itself as if in a mirror. Check the documentation and write Java code that accepts an Object and prints out as detailed and as readable description of it as you reasonably can. Note that Object underpins the polymorphism of Java generics, but now that generics are available programmers will use Object directly much less than they used to. 49 A Collection class that does just what you expect! 4.7. GENERICS 131 Primitive is second class? The ability to treat things as “Objects” does not (directly) extend to the Java primitive types. To work around that the libraries contain classes with names that are rather like those of the primitive types except that they are capitalised. Ie Boolean, Character, Byte, Short, Integer, Long, Float and Double. As of the most recent revision of Java you will find that the compiler arranges to convert between int and Integer (and the other primitive types and their associated wrapper classes) when it believes that that will help you. The conversion naturally involves some run-time cost so it is perhaps advisable to be aware when it happens. The sort of circumstance where it is especially convenient that this happens is when you want to store a primitive object (eg an integer, floating point number or character) in a Hashmap or a Vector (or indeed any of the collection classes). It was then natural for the Java designers to set methods associated with these to implement a wide range of basic conversions and tests on the values, as in Integer.doubleValue and Double.isNaN (and many more). The numeric types the classes Integer etc do not inherit directly from Object but via a class called Number Eg Number a, b; a = 2; // new Integer(2); b = 11.7; // new Double(11.7); can be written as shown, but behaves as if the constructors in the comments have been used. If the Number objects are used in a context where primitive numbers are needed (eg you try to perform arithmetic using them) the values will be unpackaged for you. Write a class that defines lists of Numbers, with suitable set of facilities for constructing such lists and a method sum that can add up the values in a list returning the result as a double. You may need to use “x instanceof Integer” to sort out which flavour of number is present in some particular node. Some text output using Objects Since Object is an almost universal type it can be used to pass arbitrary data to a function. This is in fact what happens with printf, but one extra thing happens there. If a method is declared with three dots after the type at the end of its argument list, as in PrintStream printf(String format, Object... args) { // whatever definition you need } 132 CHAPTER 4. BASIC USE OF JAVA indicates that the final argument to printf will actually be passed as an array of Object values. But the calls to it will just appear to permit a variable number of arguments, and each argument will be converted to (if a primitive type) or interpreted as (if a class type) Object. While this scheme can be used in your own code to support variable numbers of arguments, and it can also be used with more restrictive types than Object it will almost always count as poor style since it can easily reduce type-safety and cause confusion if you mix it with method overloading. But where it is useful it really helps make code concise. Without it instead of writing int i=1, j=2; System.out.printf("%d, %d", i, j); you would need to wrap i and j up in the type Integer explicitly, and create an array to pass the multiple arguments explicitly. int i=1, j=2; myOwnMethod("%d, %d", new Object [] {new Integer(i), new Integer(j)}); But note very well that within the code that implements things such as printf everything has to work understanding that the concise calls are in fact mapped by the java compiler onto the clumsy looking code that packaged up primitive types and makes an array. Now seems a good time to provide a summary of more of the formatting options available with printf, and also to note that the method String.format does exactly the same job of layout but returns a formatted string rather than doing any direct printing. We have already seen "%d" for laying out integers, and know that "%n" generates a newline. Within a format string the character "%" introduces a format specifier. After the percent sign a number of optional elements can appear: • An argument index followed by a dollar sign ($). Without one of these the values to be converted are taken one at a time from the arguments provided. An index such as (2$) tells the formatter to use the second argument now, even if that is out of order. Often you may want to display the same data several times, eg in different formats. In that case (<) is very useful: it tells the system to re-use the argument most recently dealt with; • Some flag characters. Just what is valid here will depend on just what sort of layout is being performed, but various punctuation marks as flags can, for instance, force left-justification of text within a field (-), ensure that numbers are always displayed with an explicit sign (+), include leading zeros (0) or be more fussy about the actual types of arguments (#). You need to check fine details in the documentation when you use flags! 4.7. GENERICS 133 • a field-width, written as an unsigned non-zero integer. You should expect that if this is specified that the output from the conversion will have exactly that number of characters; • A dot followed by a integer precision. Eg (.4). For some conversions this sets an upper limit on the number of characters to be generated. For floating point conversions it controls either significant figures of the number of digits after the decimal point. • (and finally!) a character (or in some cases a pair of characters) that indicated just what sort of conversion is to be performed. Perhaps the more important cases are the letters s, each of which is discussed briefly below! The full set of format letters and options can be found in the online documentation, but key cases are s, S: This takes any value at all and tried to convert it] to a string. If the argument implements the Formattable interface then its formatTo method is used to do the conversion, otherwise its smalltoString method is used. When you define a class of your own you may often wish to override or define one or both of these methods so that you can easily print instances of your class. Many of the Java library classes implement these methods in ways that at least try to be helpful. If you write a capital S the material that is displayed is forced into upper case. Similar effects apple for other use of upper case format letters; d: This is the case most often seen in these notes so far, and prints an integer. But you can also display BigInteger values with this (and the x) format; x, X: Integers can be displayed in hexadecimal rather than normal decimal notation this way; c: character e, f, g, E, F, G: Floating point and their display involve lots of complication! The “e” formats always use scientific notation with an explicit exponent. The “f” formats use the specified precision as the number of digits to display after the decimal point (eg it is a good thing to use for printing pounds and pence with "%.2f"), while “g” tries to select between those two formats to select one that will be natural and will take up as little space as possible. %: If you want to print a percent sign you will need to write two in a row! CHAPTER 4. BASIC USE OF JAVA 134 n: Unix and Windows have different ideas about what constitutes a “newline”. The format code %n makes allowance for that for you. tx: Java provides an amazingly rich range of ways of formatting times and dates. YOu can use these formats when printing objects of type Long, Calendar or Date. I think there is too much to list here, but a very few of the options available are tY year displayed as 4 digits, eg “2005”; tA full name of day of the week, eg “Monday”; TA as above, but upper case: “MONDAY”; ta short name of day: “Mon”; tM minute within the hour, as 2 digits; tm number of the month as 2 digits, counting January as number 1; tT time formatted for the 24-hour clock as "%tH:%tM:%tS"; tD date formatted as "%tm/%td/%ty". A bigger exercise There are twelve shapes that can be made by joining five squares together by their edges to get a connected unit. It is possible to pack these shapes (the pentominoes) into a six by ten50 rectangle in a number of ways. Here is one such packing, which will also serve to show you the shapes of all the pieces: 50 also into a five by twelve or three by twenty. 4.7. GENERICS 135 The object of this exercise is to find other solutions to the puzzle. The suggested strategy is to represent the 10 by 6 board using 60 of the 64 bits in a long. You can them treat these as if they are arranged as a rectangular array, and then a single long value can represent a possible position of a piece. In this representation the twelve pieces can be described by the array: final long [] rawPieces = { 0x000001f, 0x0100407, 0x000040f, 0x0300403, 0x0401c01, 0x2008007, 0x0201c02, 0x0000c07, 0x0301808, 0x0000c0e, 0x000100f, 0x0301802 }; where the values look pretty ugly but are at least all in quite a small table. A bulkier but perhaps cleaner way to set up the initial table of shapes would be to use a function such as: long piece(String line1, String line2, String line3) { return (row(line1) << 2*boardWidth) | (row(line2) << boardWidth) | row(line3); } long row(String line) { long r; for (int i=0; i<line.length(); i++) { r <<= 1; if (line.charAt(i) == ’X’) r |= 1; } } ... piece("X ", "XXX", " X "); The init method for the applet should start by setting up a table first of all the twelve pieces normalised so that they are down in one corner of the board, and then a larger table showing each piece in every location on the board that it could possibly be. Doing this will involve writing code that reflects and rotates pieces — not especially nice when using bits packed into a long — and which avoids setting up entries that are redundant because of symmetry. The code that is involved in getting this far is quite messy enough to keep you busy for a while. CHAPTER 4. BASIC USE OF JAVA 136 The overall structure of the code that searches for solutions might then be // // // // // // // search(i) looks for ways of placing piece i on the board. The array entry maps[i][j] is a bitmap showing the j-th place that piece i could bit, and the variable "board" shows which parts of the board have already been filled. There are 12 pieces, known as 0 to 11. void search(int i, long board) { if (i == 12) { // Here a solution has been found ... record it somehow ... return; } for (int trial=0; trial<maps[i].length; trial++) { if ((maps[i][trial] & board) == 0) { // no overlap with existing pieces // so put this in and next try to // fit in piece i+1. search(i+1, board | maps[i][trial]); } } } The first challenge would be just to count the solutions, and so the place in the above which is incomplete could be replaced by a single statement that incremented a variable. But since it is easy to use fillRect to draw filled-in rectangles in Java it would seen natural to try to draw some of the solutions and that would mean doing something distinctly harder. The search function I have sketched tries the twelve pieces one after each other, and at each stage considers each piece at ever position on the board where it would still fit. A different search strategy would be to scan the board at each stage and find the first vacant square. The program would then identify and try every piece that could be used to fill in that square. I believe that this second search strategy is rather closer to the one most people would use than my original one was. A curve to plot Use Java to plot a picture of the following curve as t varies from 0 to 2π: x = cos(t)(1 + cos(40t)) y = sin(t) + cos(t) sin(40t) 4.7. GENERICS 137 Find a copy of A Book of Curves, E. H. Lockwood, Cambridge 1963, and in a similar style re-create variants on as many of the pictures as you can. Reading hexadecimal numbers We have seen various ways of decoding numeric input, eg Integer.parseInt and the whole set of joys associated with StreamTokenizer. You can note from the full documentation that there is a two-argument version of parseInt that allows you to specify what radix the input string was supposed to be in. You may also like to check details of the class smallScanner which has a method nextInt that can also accept an argument indicating what radix to read in. Now imagine that these facilities did not exist, or that for some strange reason you could not use them. Implement your own functions that can be given strings as arguments and which will make it possible to convert the strings into int and long values, allowing for the possibility of octal or hexadecimal specifications. Displaying floating point numbers In versions of Java prior to 1.5/5.0 the functionality of printf was not available. This exercise is to re-create some of it thereby getting a chance to feel what work is involved in making worthwhile extensions to the existing libraries. The method Double.toString allows you to generate a printable representation of a floating point number. However compared to the floating point layout flexibility available in many other languages it seems pathetically simple-minded. A typical programming language will provide for three ways of printing floating point values: F format: here numbers are written as illustrated in the following examples -1.000 1234567890000000000.0 0.000000005656 and even if the values are very large or very small their magnitude is indicated by having suitable numbers of leading or trailing zeros. It is typically possible to specify how many digits will be printed following the decimal point, and to indicate the width that the whole number will be padded to with either leading or trailing blanks. E format: For very large or small numbers it may be convenient to use scientific notation. So with E format an explicit exponent will always be displayed: CHAPTER 4. BASIC USE OF JAVA 138 -1.0e000 1.234568E018 5.656000e-009 Observe that there is always exactly one digit before the decimal point (sometimes a scaling option is provided to allow the user to specify a different number of digits before the point), and the exponent is always present and probably always displayed in a way where the largest possible exponent value could be fitted in. A “precision” specifier can indicate how many significant digits are to be shown, and the number will be padded with zeros or rounded to meet that requirement. Numbers close to 1.0 tend to look a bit ugly this way! Again it is useful to be able to place the number in a fixed-width field, either right or left-justified. G format: Large numbers are best shown in E format while modest size ones do best in F. So G is a composite scheme that looks at the value of a number and decides which of the other two formats would lead to the most compact representation, and it then uses that. It is roughly what Java’s Double.toString method provides, but again we would really like options to indicate precision and field width. Implement functions which convert Java double values to strings in each of the above formats. I might suggest that you start by using toString to do the basic conversion and then let your code restrict its worry to unpicking that string and re-formatting the characters. If you decide you want to do the numeric to string conversion from scratch you should be aware that preserving numeric accuracy is quite hard! Write a test-suite that compares the strings your code generates with the ones that String.format produces. Then worry about NaNs, infinities, careful rounding and the like! Double as bit-patterns Double.doubleToLongBits takes a double as an argument and returns a long. The long is the internal IEEE-format bit-pattern that represents the double The matching function longBitsToDouble accepts a long and manufactures a double in the same dubious sort of way. Investigate whether there is any double value x in Java so that (double)Double.doubleToLongBits(x) == x 4.8. IMPORTANT FEATURES OF THE CLASS LIBRARIES 139 Continued fractions Any positive number can be expanded as a continued fraction as in x = x0 + 1 1 x1 + x2 +... where the values xi are called partial quotients and are all positive integers. If the original number is rational the continued fraction terminates at some stage. Otherwise it goes on for ever, and can be viewed as providing an alternative to the usual decimal expansion of numbers. Instead of writing a value as say 1.414213562. . . the partial quotients would be listed [1, 2, 2, 2, . . . ]. Gosh in fact for this number it looks as if the continued fraction is astonishingly regular! The sequence of partial quotients in the expansion of a number are easy to compute - the first is just obtained by casting the number to an int. The rest can be obtained from the reciprocal of what you get by subtracting that value from the original number. Write code to do this and tabulate the first dozen partial quotients you get in the expansions of the following numbers: √ 3 √ ( 5 + 1)/2 √ 7 e = 2.71828 . . . π 4.8 Important features of the class libraries The coverage thus far has shown the use of some small parts of the Java libraries, but has also missed a great deal out. In this course I will not have anything like enough time to describe everything that is available. However there are a few bits of functionality that either seem to be generally useful enough or sufficiently fun to be worth covering. The little bits of explanation given here are thus to be viewed as a sampler of what Java can do for you. If you can work through all these demonstrations and navigate the documentation of the classes that they introduce you should have got a reasonably broad idea of the system, and in looking up the documentation details while working on these cases you will as a side-effect be noticing what other classes are present. I will only give the most basic possible demonstrations of the things illustrated here. Full competent use of them can only come with serious work on rather larger bodies of code. I will also totally ignore 140 CHAPTER 4. BASIC USE OF JAVA several of the newer parts of the Java class libraries, or to be more precise, I will leave fuller details of some of these facilities and of the other ones to next year’s “Concurrent Systems and Applications” course and/or your own private study. 4.8.1 File input and output The character input and output shown so far has used the pre-defined “standard” streams System.in and System.out. Obviously in many real applications it is necessary to access named files. In many programming languages there is a logical (and practical) distinction made between files that will contain text and those while will be used to hold binary information. Files processed as binary are thought of as sequences of 8bit bytes, while ones containing text model sequences of characters. In Java this distinction has two main manifestations, one of which is somewhat frivolous but can matter on an every-day basis in the UK while the other is of wider importance but will not impinge on immediate coursework: 1. Windows and some internet protocols use a pair of characters, carriagereturn and line-feed, to mark the end of a line. Unix and Linux use a single character (newline). In text mode Java makes whatever adjustments are needed so that external files adhere to platform conventions, but your Java code sees just the character ’\n’. 2. In many parts of the world (and in particular in the Far East) text documents need to cope with alphabets that involve many thousands of symbols. Unicode is designed to be able to cope with these, but there can be a variety of ways of encoding text as streams of bytes. When working in such an environment Java can be configures so it knows how to pack and unpack Unicode using various of the major encoding conventions. But obviously it will only even try to do this when it knows that the programmer wants data to be viewed as character-based rather than binary. Java uses names typically based on the word Stream for binary access, and Reader and Writer for text. So when you read the documentation expect to find two broadly parallel sets of mechanisms, one for each style of access! Java input and output can seem clumsy to start with because almost all of the functions involved are able to throw exceptions, and it is expected that Java code using them should be prepared to handle these. This is in fact good because experience with earlier languages indicates that most programmers do not find it easy or natural to put error-checks after simple I/O operations, even though logically almost any of them could fail. For instance writing a single character 4.8. IMPORTANT FEATURES OF THE CLASS LIBRARIES 141 to a file could unexpectedly fail if the disc on which the file lived became full51, or it it was on a floppy disc and the disc was removed from the drive or had a scratch, or if there was a hardware glitch in a disc interface. Different but equally delicate issues arise with output that goes directly to a printer, across a network to a remote file-system, or with input from a semi-reliable device such as a bar-code scanner. The Java use of exceptions encourages all programmers to consider I/O failure right from the start. There is one final complication about Java input and output that ought to be mentioned up front. One use of Java is in applets to be embedded within web pages, and hence sometimes fetched from remote web-sites. It could be bad if code from an untrusted site could read and write all your files! So Java introduces the idea of a security manager and can classify code as either trusted or untrusted. Untrusted code will not be permitted to access the local filing system. The short form way around this is to make everything you do an application not an applet: security restrictions are then (by default) not imposed. If you do need to make applets that access disc or do other things that default controls lock out you need to impose security on an application then you will need to find out about the creation of custom Security Policies and signed Java code. I will not describe that here. Java provides a rich and somewhat elaborate set of capabilities, but perhaps a good place to start will be simple reading of text files. The class FileReader does almost everything you are liable to need: here is a minimal demonstration that shows that you can use a method called read to read characters, and that it returns the integer -1 at end of file. import java.io.*; public class ReadDemo { public static void main(String [] args) throws IOException { Reader r = new FileReader("filename"); int c; while ((c = r.read()) != -1) { System.out.printf( "Char code %x is \"%<c\"%n", c); } r.close(); } } 51 Or the user’s quota expired. 142 CHAPTER 4. BASIC USE OF JAVA There are a significant number of things about this small bit of sample code that deserve further explanation, and by trying to be minimal the code is not really very good: an improved version is given soon. Firstly note that FileReader is in the java.io package so we have an import statement to make use if it easy. Next observe that almost all input and output functions can raise exceptions, and this code just admits defeat and notes that its main method might therefor fail. I view it as bad style to do this and strongly believe that exceptions should be handled more locally. Now FileReader is a subclass of Reader, which is the general class that reads from character streams. So I create a FileReader using a constructor that takes a file-name as its argument but store what I get as just a Reader . This helps stress to me and remind me that the rest of my code would be equally valid if using some other sort of Reader, such as one that gets its input from a pipe, from a string, from characters packed in an array, from a network connection, by running a character decoder on a stream of bytes or otherwise. The way I do things here supposes that the data in the file concerned is encoded in the standard local character-set that Java has been set up for. For reading files imported from elsewhere in the world you have to do things a more complicated way! The read method hands back either the numeric code for a character, or the value -1 to denote end-of-file. It perhaps seems odd that it returns an int not a char, but doing so allows it to hand back -1 which does not stand for any normal character. You can of course case the int to a char any time you want to! After having read the file you are expected to call the close method. If you fail to do this for an input file you may just leave some machine resources cluttered and unless you try to open and read very many files without closing any of them you will probably not feel any pain. However for output files it may sometimes be that the last bit of your data is not actually sent to the file until you do the close. You should get into the habit of ensuring that every file you open does get closed. A much improved version of the same code can be arrived at by handling the possible exceptions. You may note that FileNotFoundException is a subclass of IOException which is why the throws clause above was sufficient, but which also allows us to see how the improved code is more precise. When you get an exception out of Java it can often be useful to print it, in that it is liable to carry some text that explains further what went wrong. I use finally to guarantee that the close method of the Reader will always be invoked. import java.io.*; public class BetterReadDemo { public static void main(String [] args) 4.8. IMPORTANT FEATURES OF THE CLASS LIBRARIES 143 { Reader r; try { r = new FileReader("filename"); } catch (FileNotFoundException e) { System.out.printf(e); return; } int c; try { while ((c = r.read()) != -1) { System.out.printf( "Char code %x is \"%<c\"%n", c); } } catch (IOException e) { System.out.printf("Reading failed (%s)%n", e); return; } finally { r.close(); } } } Output to a file is somewhat similar, and if you only ever want to write individual characters and simple strings then FileWriter will suffice. However you may like to be able to use println and printf when writing data to your file, and they come in a class called PrintWriter. Unlike the class FileWriter, PrintWriter hides all exceptions so you do not need to catch them, but you can check for error using the checkError method and you still need to ensure that close is called. import java.io.*; public class PrintDemo { public static void main(String [] args) { try { PrintWriter w = new PrintWriter("filename"); try CHAPTER 4. BASIC USE OF JAVA 144 { w.printf("Hoorah%n"); assert !w.checkError(); } finally { w.close(); } } catch (FileNotFoundException e) { System.out.println("Sorry!"); } } } This time you must make w a PrintWriter and not just a Writer to gain access to printf and so on. If errors arise on a PrintWriter the flag marking them persists so you do not need to use checkError after every single print statement – every so often and once when you have generated all that you want to end up in the file will suffice. Although I have used assert here I probably feel that error checking should be done always an that something along the lines of if (w.checkError()) throw new IOException("failure on PrintWriter"); might well be better policy. The long-winded but more flexible way to access files is to start by creating an instance of java.io.File. An object of this type can be created using either a constructor that takes a single String that names the file (as a complete path, if necessary), or with a two-argument constructor where one argument specifies the directory to look in and the other the file-name within that directory. A File object supports methods exists, canRead and canWrite and also one called isFile, which test for a “normal” file, ie one that is not a directory or any of the exotic things that in Unix masquerade as sort-of-files. You can pass a File rather than a string when opening a FileReader or FileWriter. Other methods available via the File class include ones to check the length of a file52, rename it, create new directories, list all the files in a directory and delete files. You can also create a file by giving just a local name (eg such as "java.tex") and call getAbsolutePath to obtain a fully-rooted file-path that identifies it. The exact result you get will clearly be system-dependent, and on one computer I tried that I got back 52 The length reported is liable to count in bytes, reported differs from system to system. and so for text files it can well be that the length 4.8. IMPORTANT FEATURES OF THE CLASS LIBRARIES 145 "e:\UNIV\notes\java\java.tex" while on another it was "/home/acn1/javanotes/java.tex". The fact that all these facilities are so conveniently supported may make Java one of the more useful programming languages for writing file-management utilities. Once again if you look at Java code and compare it against other languages for very tiny tasks and where previously you would have missed out all error handling Java can look clumsy — but when you look at more realistic and wellengineered examples it starts to feel much nicer. Binary data access are useful for cases when your data really is raw data and not composed of characters. There classes called java.io.FileInputStream and (of course) FileOutputStream that take a File or a string as an argument and create streams. They of course throw exceptions if the files can not be opened as requested. Once a file has been opened you should in due course call the relevant close method to tidy up. Earlier examples have shown an extra layer of Java constructor arranging to buffer input in the expectation that that may speed it up. I have done that here too. Putting these together we might arrive at something like this to copy a file in binary mode: String fromName = "source.file"; String toName = "destination.file"; File fromFile = new File(fromName), toFile = new File(toName); if (!fromFile.exists() || !fromFile.canRead() || toFile.exists() || !toFile.canWrite()) { System.out.println("Can not copy"); return; } InputStream fromStream = new BufferedInputStream( new FileInputStream(fromFile)); try { OutputStream toStream = new BufferedOutputStream( FileOutputStream(toFile)); try { for (;;) { toStream.write(fromStream.read()); CHAPTER 4. BASIC USE OF JAVA 146 } } catch (EOFException e) {} // Use exception to break out of for loop finally { toStream.close(); } } catch (IOException e) { System.out.printf("IO error " + e); } finally { fromStream.close(); } This code is in fact not yet complete! It needs yet more try blocks to guard against FileNotFoundException cases where the two streams are created. But it illustrates how the EOFException can be used to stop processing at end of file, and demonstrates very clearly that in real file-processing applications most of what you write will be to do with setting everything up and arranging to handle exceptions, while the central interesting bit of the code may be as short as just for (;;) { toStream.write(fromStream.read()); } Overall it may seem pretty grim, but in large programs the complication will still remain at the level of the dozen or so lines shown above, rather than growing out of control. It is also probable that the visible pain is because writing high quality file-manipulation code is in fact nothing like as easy as earlier programming languages have tried to make it out to be! There is a potential down-side in Java being so very insistent that you catch all these errors, in that it can encourage a style of cop-out that just wraps all your code in try { ... } catch (Exception e) {} where the block is set up so it catches all sorts of Exception not just the very special ones that you know are liable to arise, and rather than doing anything it just ignores the error. This very much defeats the purpose Java is trying to achieve! If you are (quite reasonably!) in a rush some time at least go: 4.8. IMPORTANT FEATURES OF THE CLASS LIBRARIES 147 try { ... } catch (Exception e) { System.out.printf("Exception: %s%n", e); System.exit(1); } so that the exceptions you catch are reported and make your program stop. The above example used BufferedInputStream which should not have any effect at all on what your program actually does, but may have an impact on performance when you work with big files. For binary data there are more interesting classes that you could use just as easily: ones to compress and and decompress data, encryption and checksumming capabilities. For text data you can use LineNumberReader in place of BufferedReader and it will keep track of which line you are on in your input. See the classes FilterInputStream and FilterReader in the documentation for further details. 4.8.2 Big integers The Discrete Mathematics course had an extended section where it discussed highest common factors, modular arithmetic and eventually the RSA encryption scheme. To refine your understanding of all that you could quite properly want to code it all up. To make any pretence at all of reasonable security this means that you need to do a lot of arithmetic on integers that are between 1024 and 1536 bits long. This sort of range of values is about what is required because there is a serious possibility that numbers smaller than that might be factorisable by the best current algorithms and fastest current computers. An implementation of RSA will also need to generate a couple of primes, each with around half that number of bits. Java has thought of that and it provides a class java.math.BigInteger which does essentially everything you could need! And note that printf lets you print these big values easily. In this class there are half a dozen constructors. The more obvious ones construct big integers from strings or byte arrays, and a valueOf method allows you to create a big integer from a long. The two interesting constructors create random big numbers. They both accept an argument that is an object from the class Random which actually gives them their randomness. One creates an arbitrary n-bit number while the other creates an n-bit number which is (almost certainly) prime. For the second of these it is possible to tune the degree of certainty that a prime has indeed been found by giving a “certainty” argument that tells the con- 148 CHAPTER 4. BASIC USE OF JAVA structor how hard to work to check things. I might suggest that a value of 50 would be sufficient for all reasonable purposes. I should provide a rather heavy health warning here. If you use the Javaprovided Random class to help you create private keys or other values of cryptographic significance you will be throwing away almost all the security that the RSA method could give you, since this random number generator comes too close to having a predictable behaviour. Specifically there is a chance that to arrange to get the same “random” values that you do it may suffice for somebody to run a similar Java program having reset their computer so that their run appears to happen at the exact time of day that yours did. This may be hard but is nothing like as hard as factorising 1536-bit integers. If you ever wanted to use serious encryption you must instead use java.security.SecureRandom. Anybody really serious about security would think at length before trusting even the things in java.security: how is it possible to tell that they do what they are supposed to and that they do not include secret weaknesses? And even if they are honest it is astonishingly easy to lose all the security you thought you had by some apparently minor clumsiness in how you use your cryptographic primitives. A course on security later in the Tripos gives much more information about all of this! Note that some of the functionality in the Java security and encryption packages may be missing or limited unless your installation has provided some level of assertion that you are not a national of a country that the US Government does not like and that you are not a terrorist. But as is the way of any such attempt at blocking access to technology, there are easy to find drop-in replacements not hampered by (so many) export license issues. You might still be aware that good encryption is viewed by some as something with significant security implications and that it should not be given any opportunity to cross international borders until you at the very very least know what all the rules are! Java itself provides ways that those who satisfy the correct eligibility conditions can use the standard Java libraries and obtain unlimited security, via the installation of special “JCE policy files”. Once you have made suitable objects of class BigInteger the library provides you with methods to add, subtract, multiply and divide them, to even raise one big number to a huge power modulo another number (what a give-away about the expected use of this class!). The function that computes what the Discrete Mathematics notes called a Highest Common Factor is here known as a Greatest Common Divisor (gcd), but the change of name does not hide any change of behaviour53. 53 The java_ security package provides easy to use functions for generating keys and computing message digests and digital signatures. There is a standard extension to Java that supplies encryption and further functionality: this part may be subject to export regulation and has to be fetched and installed as a separate item from the main SDK. 4.8. IMPORTANT FEATURES OF THE CLASS LIBRARIES 149 import java.math.*; import java.util.*; ... Random r = new Random(); // inadequate! // use the SecureRandom class instead!!! // Create two big primes p and q BigInteger p = new BigInteger(768, // length in bits 50, // only 1 in 2ˆ50 prob of non-prime r); // random number source BigInteger q = new BigInteger(768, 50, r); // form their product, n, which can be public BigInteger n = p.multiply(q); // compute phi = (p-1)*(q-1) BigInteger bigOne = BigInteger.ONE; BigInteger pMinusOne = p.subtract(bigOne); BigInteger qMinusOne = q.subtract(bigOne); BigInteger phi = pMinusOne.multiply(qMinusOne); // select a random exponent whose HCF with phi // is 1. BigInteger e; do { e = new BigInteger(1536, r); } while (!phi.gcd(e).equals(bigOne)); // now (n, e) is the public key ... // Set up a message to encrypt BigInteger msg = new BigInteger("12341234"); // silly message // Encrypt with public key. One line of code! BigInteger k = msg.modPow(e, n); ... The code is clearly about as short as it possibly could be. Again let me warn you that cryptographically satisfactory random number generators are hard to come by, and that such issues as managing the secure distribution of public keys and keeping private ones truly private mean that security involves very much more than just these few lines of code. But Java is clearly making it easy to make a start on it. How do you know that the Authorities and not bugging your computer while you run the above code? How do you know that no traces of the secret information remain anywhere when you have logged off or even powered down the computer? The Computer Lab’s security group has a fine track-record of demonstrating that CHAPTER 4. BASIC USE OF JAVA 150 even apparently safe computing habits leak information to a sufficiently skilful and ingenious snooper. 4.8.3 Collections Java has an interface called Collection and a whole range of interesting classes derived form it. The general idea is that Collection covers ideas like “set”, “list” and “vector”. In some cases the elements in a collection can be ordered (in which case the objects included must all implement the Comparable interface54), but might not be. Collections may be implemented as linked lists or as vectors, but the library classes arrange that when a vector is used it will be enlarged as necessary so that the user does not have to specify a limit to the size of the collection too early. One sub-case of a Collection is a Map, which provides for general ways to organize various sorts of table or dictionary. I am not providing any sample code using Collections in this little section since I believe that when you browse the documentation you will find them easy to cope with. However it may make sense for me to list more of the names of classes worth looking at: Collection, Collections, Set, HashSet, TreeSet, Vector, LinkedList. The collection classes are keyed to the Java for statement to make it trivial to iterate over the values in a collection: as has been seen in various of the sample programs here. Very typically when you create an instance of a Collection Class you will use the generics capability to indicate the type of the objects you will keep in it, eg Vector<String> v = new Vector<String>(); and if you do so Java will know that the values you extract will be of the type indicated. 4.8.4 Simple use of Threads A thread is a stream of computation that can go on in parallel with others. The term is used when the activities are part of a single program, and where there is no need for security barriers to protect one thread from the next. The more general term used when the extra overhead of protection is needed is process. Java is one of the first languages to make a big point of having threads supported as 54 An especially interesting issue here is the way that Java can compare strings. To support the needs of different nationalities a class Collator is provided, and methods in it can order strings based on proper Locale-specific understandings of where accented or non-English letters should go. Alphabetic ordering with international texts is a more complicated business that almost all of you would have imagined. 4.8. IMPORTANT FEATURES OF THE CLASS LIBRARIES 151 a standard facility. Many systems in the past have had threads, but usually in rather non-portable form. Almost any program that has to implement a complicated windowed user interface or which accesses remote computers55 will need to use threads so that one high priority thread can ensure that the user always gets responses to requests, while several lower priority ones keep on with some bigger calculation. There are very many subtleties in any program that exhibits concurrency. I will not describe these here, and in consequence I expect that people who try to make substantial use of threads based on just these notes will get themselves into deep water. There are two typical bad effects that can arise. In one the system just locks up as a chain of threads each wait for the others to complete some task. In the other two threads both attempt to update some data structure at around the same time and their activities interfere, leaving data in a corrupted state. The Java keyword synchronized is involved in some of the resolutions of these sorts of problem. The example here is supposed to do not much more than to alert you to the possibility of writing multi-threaded Java programs, and to show how easy it is. I will start by defining a class that will encapsulate the behaviour I want in the rather silly thread that I will use here: class Task extends Thread { boolean resultShown; String result; int identification; Task(int i) { identification = i; resultShown = false; } public void run() { try { sleep(20+100*identification % 77); } catch (InterruptedException e) { return; } result = String.valueOf(identification); } } The two critical things are that my class extends Thread and that it implements run. The method run will be to a thread much what main is to a complete program. In this case I make my thread do something rather minimal. It goes to sleep for an amount of time that depends on the argument that was passed to its constructor, and it then sets one of its variables, result, to a string version of that value. When created my task also sets a flag that I will use later on to record whether I have picked up its result. 55 Often a slow business. 152 CHAPTER 4. BASIC USE OF JAVA To demonstrate use of threads I will create half a dozen instances of the above, and then wait around until each has finished its work. As I notice each task completing I will pick up its result and display it. When I have done that I will set the resultShown flag so that I do not display any result twice. I could surely find a cleverer way of achieving that, but the solution I use here is at least quite concise. Once all my threads have finished I will let my main program terminate. I let my top-level class inherit from Thread just because I want to use sleep in it so that while waiting for the sub-tasks to finish I am mostly idle. public class Threads extends Thread { static final int THREADCOUNT = 6; // // // // public static void main(String[] args) { Create and start six threads Task [] spawn = new Task [THREADCOUNT]; for (int i = 0; i<THREADCOUNT; i++) { spawn[i] = new Task(i); spawn[i].start(); } System.out.println("All running now"); int stillGoing = THREADCOUNT; Scan looking for terminated threads while (stillGoing != 0) { for (int i=0; i<THREADCOUNT; i++) { if (!spawn[i].isAlive() && !spawn[i].resultShown) print result the first time I notice a thread dead { System.out.println("Result from " + i + " = " + spawn[i].result); spawn[i].resultShown = true; stillGoing--; } } System.out.println("One scan done"); sleep for 7 milliseconds between scans to avoid waste try { sleep(7); } catch (InterruptedException e) { break; } } System.out.println("All done"); } } Observe that sleep can raise an exception if the sleeping task receives an in- 4.8. IMPORTANT FEATURES OF THE CLASS LIBRARIES 153 terrupt from elsewhere, and I (have to) catch this and quit. The results I obtain follow, and you can see traces that show the main program scanning round looking for threads that have finished and also you can see that the different threads terminate in some curious order. Of course a more worthwhile example would put real computation into each of the threads and their termination would be based on how long that took rather than on the artificial sleeping I have used here! java Threads All running now One scan done One scan done One scan done Result from 0 = Result from 4 = One scan done Result from 1 = One scan done One scan done Result from 5 = One scan done Result from 2 = One scan done One scan done One scan done Result from 3 = One scan done All done 0 4 1 5 2 3 The reason my example is respectably simple and trouble-free is that the threads only communicate by receiving data when first created and by delivering something back when they have finished. Inter-process communication beyond that can be astonishingly hard to get right. 4.8.5 Network access Java really hit the news as a language for animating your own web pages. One part of doing this is the set of graphical operations that it supports. Another less instantly visible but equally important thing is the ability to connect to remote computers and retrieve data from them. The set of rules that make up HTTP56 are what defines the World Wide Web. Standard Java libraries provide various degrees of ability to connect using it. The small program shown here links through to a 56 HyperText Transfer Protocol. 154 CHAPTER 4. BASIC USE OF JAVA fixed location named as its fire command-line argument and displays the data found there. This data comes out as an HTML document with lots of tags that are enclosed in angle brackets. // Read file from a possibly remote web server import java.net.*; import java.io.*; public class Webget { public static void main(String [] args) { URL target; try { target = new URL(args[0]); } catch (MalformedURLException e) { return; } // Badly formed web-page address try { URLConnection link = target.openConnection(); link.connect(); // connect to remote system // Now just for fun I display size and type information // about the document that is being fetched. Note that // documents might be pictures or binary files as well // as just text! System.out.println("size = " + link.getContentLength()); System.out.println("type = " + link.getContentType()); // getInputStream() gives me a handle on the content, and // I rather hope it is text. In that case I can get the // characters that make it up from the InputStream. Reader in = new InputStreamReader( link.getInputStream()); int c; // Crude echo of text from the document to the screen. // It will have lots of HTML encoding in it, I expect. while ((c = in.read()) != -1) System.out.print((char)c); } // A handler is needed in case exceptions arise. catch (IOException e) 4.8. IMPORTANT FEATURES OF THE CLASS LIBRARIES 155 { System.out.println("IO error on link"); } // I am lazy here and do not close anything down. } } // end of Webget.java The data stored on the CL teaching support pages in mid February 1998 started off as follows, apart from the fact that I have split some of the lines to make the text fit neatly on the pages of these notes. It has of course changed by now! I keep this old material in the notes out of nostalgia. <HTML> <HEAD> <TITLE>Comp.Sci. Teaching pages</TITLE> </HEADER> <BODY> <H1> Computer Science teaching material on Thor</H1> <P> <UL> <LI><A HREF="Java/java-index.html"> Some Information about Java </A> (on this server) ... The main message here is that accessing a remote web-site is just about as trivial as reading from a local file. 4.8.6 Menus, scroll bars and dialog boxes Back when Java 1.2 was released Sun finalised a whole set of windows management code that they called Swing. This extended and in places replaced earlier windowing capabilities that were known as AWT. I believe that by now it is proper to use the Swing versions of things even in those cases where older AWT versions remain available. You can tell that you are doing that when you use a lot of class names that start with a capital “J”! The code presented here is called MenuApp and is a pretty minimal demonstration of menus! I will use this example to show how something can be both an application and an applet. The “application” bit of course defined a method called main, and this just sets up a window (frame) that can contain the applet stuff. There is a bit of mumbo jumbo to allow the application to stop properly CHAPTER 4. BASIC USE OF JAVA 156 when the window is closed. As usual I will show the inner bit of the code first – the fragment that actually does the work: public static void main(String[] args) { Menuapp window = new Menuapp(); JFrame f = new JFrame("Menu Demonstration"); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.getContentPane().add(window, BorderLayout.CENTER); f.setSize(600, 400); f.setVisible(true); } I should point out the syntax new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } } } which extends the class WindowAdapter to produce a new (anonymous) class. In this new class it overrided the windowClosing method. It then creates an instance of the new anonymous class. This arrangement is known as an “Inner Class” and can be very handly when you need a small variant on some existing class and will use it just once so that giving it a name would be overclumsy. The constructor for Menuapp will establish a menu bar at the top of itself, then makes menus on that bar, and places menu items on each menu. In much the way that mouse events were dealt with by registering a handler for them it is necessary to implement an Menuapp running 4.8. IMPORTANT FEATURES OF THE CLASS LIBRARIES 157 interface called ActionListener and tell each menu to report via it. The report hands down an ActionEvent from which it is possible to extract the name of the menu item (and if need be which menu it was on) that was activated. I illustrate this by showing how to pop up a dialog box for selecting a file, although once I have the name of the file I just display that in the text area rather than actually opening it. I put a scrollable editable window for the text. The version I use could in fact support multi-colour text in mixed fonts and with icons and other things interleaved with the words: finding out about that is an exercise for those of you who feel keen. You will also find that I have coded this using the “swing” facilities (ie it will not compile on a simple un-extended installation of JDK 1.1.x), and the arrangements for selecting a file and for making the text window scrollable relate to that. The inclusion of javax.swing classes gives access to the relevant bits of the class libraries. Furthermore the code can be run as either an applet or an application. So lots of things are being illustrated at once. You are not expected to be able to follow all of them at first, but maybe the code will be a useful model when sometime later you do need to use some of these facilities in anger. The complete code follows: // Demonstration of Menus and a window created // from an application rather than an applet. // A C Norman 1998-2000 import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.text.*; public class Menuapp extends JApplet implements ActionListener { // This can be run as either an application or an applet! public static void main(String[] args) { Menuapp window = new Menuapp(); JFrame f = new JFrame("Menu Demonstration"); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.getContentPane().add(window, BorderLayout.CENTER); CHAPTER 4. BASIC USE OF JAVA 158 f.setSize(600, 400); f.setVisible(true); } // All real work happens because of this // constructor. I create a JTextPane to hold // input & output and make some menus. JTextPane text; Container cc; public Menuapp() { cc = getContentPane(); text = new JTextPane(); text.setEditable(true); text.setFont( new Font("MonoSpaced", Font.BOLD, 24)); JScrollPane scroll = new JScrollPane(text, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); cc.add(scroll); // Menus hang off a menu bar and contain menu items JMenuBar bar; JMenu mFile, mEdit, mHelp; JMenuItem mNew, mOpen, mSave, mCut, mPaste, mContents; // Create a menu bar first and add it to the Frame bar = new JMenuBar(); setJMenuBar(bar); // Create a menu and add it to the MenuBar mFile = new JMenu("File"); bar.add(mFile); // Create menu items and add to menu mNew = new JMenuItem("New"); mFile.add(mNew); mOpen = new JMenuItem("Open"); mFile.add(mOpen); mSave = new JMenuItem("Save"); mFile.add(mSave); mEdit = new JMenu("Edit"); bar.add(mEdit); mCut = new JMenuItem("Cut"); mEdit.add(mCut); mPaste = new JMenuItem("Paste");mEdit.add(mPaste); mHelp = new JMenu("Help Menu"); 4.8. IMPORTANT FEATURES OF THE CLASS LIBRARIES 159 bar.add(mHelp); mContents = new JMenuItem("Contents"); mHelp.add(mContents); // Each menu has to be activated to be useful. mNew.addActionListener(this); mOpen.addActionListener(this); mSave.addActionListener(this); mCut.addActionListener(this); mPaste.addActionListener(this); mContents.addActionListener(this); } // // // // // When a menu item is selected this gets called, and getActionCommand() retrieves the text from the menuItem. Here I clear the area when New is used, and do something with Open, but otherwise just display a message. public void actionPerformed(ActionEvent e) { String action = e.getActionCommand(); try { if (action.equals("New")) text.setText(""); else if (action.equals("Open")) openFile(); else { StyledDocument s = text.getStyledDocument(); s.insertString(s.getLength(), "Request was " + action + "\n", null); } } catch (BadLocationException e1) {} } void openFile() throws BadLocationException { JFileChooser d = new JFileChooser("Open a file"); if (d.showOpenDialog(cc) == JFileChooser.APPROVE_OPTION) { StyledDocument s = text.getStyledDocument(); s.insertString(s.getLength(), "Load file \"" + d.getSelectedFile().getAbsolutePath() + CHAPTER 4. BASIC USE OF JAVA 160 "\"\n", null); } } } // end of Menuapp.java You should expect that extending the above example or writing your own code that sets up controllable visual effects will cause you to have to do rather a lot of reading of the class library documentation to plan which classes you will derive items from. Also when you have mastered the basics of GUI construction by hand you will very probably want to take advantage of one of the Java development environments that can set up frameworks for user-interfaces for you in really convenient ways. 4.8.7 Exercises Replacement for “ls” On Unix the command ls lists all the files in the current directory. With a command-line flag -R it also lists members of sub-directories. Investigate the Java File class and see how much of the behaviour of ls (or the DOS/Windows dir) you can re-create. RSA The code fragment above suggests how to create a public key and then how to use it to encrypt a message once that message has been reduced to a BigInteger of suitable size. Flesh this out with code that can use the private key to decrypt messages, and with some scheme that can read text from the standard input (or a file, maybe), split it up into chunks and represent each chunk as a BigInteger. You might also want to investigate the Java Cryptographic Architecture and find out what is involved in creating cryptographic-grade random values. You should be made very aware that the ordinary Java random number generator does not pretend that the values it returns to you are suitable for use in security applications. Then do a literature search to discover just what you are permitted to do with an implementation of an idea that has been patented57 and also what the Secu57 The main RSA US patent expired on the 20th September 2000, but that does not necessarily mean that all associated techniques and uses are unprotected. Also note that there are other public key methods for both digital signatures and for encryption where the original patents have 4.8. IMPORTANT FEATURES OF THE CLASS LIBRARIES 161 rity Policies of various countries are about the use, import and export of strong encryption technology. Note again that Java provides the specification of a security library that would do all this for you if it were not for the USA government’s export restrictions, and if these restrictions do not apply to you you could use the Java strong security. There are international re-implementations of this library that can be fetched and used here. See for instance www.openjce.org. But also be aware that exporting code that includes strong encryption may be subject to government restriction. Big Decimals The class BigDecimal builds on BigInteger to provide for long precision decimal fractions. When a BigDecimal is created it will behave as if it has some specified number of digits to the right of its decimal point, but as arithmetic is carried out there can be any number of digits generated before the decimal point. For any number z one can define a sequence of values xi by x0 = 1 and √ xi+1 = (xi + z/xi )/2. This sequence will converge to z, and once it gets even reasonably close it homes in rapidly, roughly doubling the number of correct significant values at each step. For finding square roots of numbers between 0.5 and 2 (say) the starting value x0 = 1 will be quite tolerable. If one wanted the square root of a number larger than 2 or smaller than 0.5 it would make sense to augment the recurrence formula by use of the identity √ √ 4z = 2 z to pre-normalise z. Implement the method and use it to create a table of square roots of the integers from 1 to 10 all to 100 decimal places. If you can perform high-precision arithmetic on floating point numbers you should try the following calculation. I am going to use informal syntax with double constants written where you will need to use BigDecimals, and I will use the ordinary operators to work on values not calls to suitable methods in the BigDecimal class. I am also not going to tell you here either how many cycles of the loop you should expect the code to obey or what the values it prints out will look like! But I suggest that you work to (say) 1000 decimals and see what you get. a = 1.0; b = 1/sqrt(2.0); u = 0.25; x = 1.0; pn = 4.0; do also expired, and so using these can also be of interest without patent worries about deployment. Investigate “El Gamal”. CHAPTER 4. BASIC USE OF JAVA 162 { p = pn; y = a; a = (a+b)/2.0; b = sqrt(y*b); u = u-x*(a-y)*(a-y); x = 2*x; pn = a*a/u; print(pn); } while (pn < p); When you have investigated this for yourself try looking for arithmetic-geometric mean (and the cross-references from there!) in Hakmem. Mandelbrot set (again) Adjust the Mandelbrot set program, as considered in an earlier exercise, so that at the start (maybe in an init method?) it allocates a 400 by 400 array of integers all initially set to 0. Change the paint method so that it just displays colours as recorded in this array, and have a separate thread that (a) computes values to go in the array and (b) uses repaint at the end of each of its resolution cycles to bring make sure that the screen is brought fully up to date then. The objective here is to arrange that each pixel is only calculated once and that thus the paint method is a lot quicker. Further extension of this code would add mouse support so that (as a start) a mouse click would cause the display to re-centre at the selected point and start drawing again using half the previous range. Those of you who get that far are liable to be able to design more flexible and convenient user-driven controls than that, I expect! Tickable exercise 7 Tick 5 was the largest single exercise. Tick 6 was a fairly easy example of getting an Applet working. This final exercise is intended to be a reasonably manageable one to end off the series of Java practicals. The illustration of network code that I gave represents a minimal way of accessing a remote file. 1. Extend it so that it can be used as java Webget URL destination to fetch the contents of an arbitrary web document and store it on the local disc. If an explicit destination file is not specified your program should display the document fetched on the screen; 4.8. IMPORTANT FEATURES OF THE CLASS LIBRARIES 163 2. Investigate document types and encodings and try to arrange that text documents are fetched as streams of characters while binary documents come across the net verbatim; 3. Optional: Some files on the web are of type “zip”, being compressed. Java provides a set of library functions for uncompressing such files. Use it so that when a zipped file is fetched the data that arrives locally is the decompressed form of it. 4. Very optional: A javax.swing.JEditorPane can be set up so that it renders (a subset of) HTML for you. Using this perhaps you could go a significant way towards implementing your own light-weight web browser and you might only need a fairly modest amount of Java! (End of tickable exercise) Yet further network-related code The parts of the Java libraries that can fetch material from remote machines understand quite well that you will only occasionally be fetching raw text, and that fairly often the data to be accessed will be a picture or a sound. Investigate the documentation and sort out how to use these facilities. An image from a local student-run web site is shown here to illustrate the usefulness of these facilities. Further parts of the network classes allow you to detect (to some extent) what sort of data is about to be fetched from a web location so that different sorts of data can each be handled in the most appropriate manner. If you could write code that located words enclosed in angle brackets within text, and lay text out in a Figure 4.8: www.arthurnorman.org! window in such a way that you could tell which word was the subject of a mouse click you might find yourself half-way to writing your very own web browser! CHAPTER 4. BASIC USE OF JAVA 164 A minimal text editor The menu example already allows you to re-position the cursor and type in extra text. I have shown how to identify files to be loaded and saved. The TextArea class provides methods that would implement cut and paste. Put all these together to get a notepad-style editor. More Fonts The Unicode example showed that it is easy to select which font Java uses to paint characters. The class FontMetrics then provides methods that allow you to obtain very detailed measurements of both individual characters and rows of them. Using all this you can get rather fine control over visual appearance. Using however many of few of these facilities you like create a Java applet that displays the word LATEX in something like the curious layout that the designers of that text-formatting program like to see. Two flasks This was a puzzle that I found in one of the huge anthologies of mathematical oddities: An apothecary has two spherical flasks, which between them hold exactly 9 measures of fluid. He explains this to his friend the mathemagician, and adds that the glass-blower who makes his flasks can make them perfectly spherical but will only make flasks whose diameter is an exact rational number. The mathemagician looks unimpressed and says that the two flasks obviously have diameters 1 and 2, so their volume is proportional to 13 + 23 = 9. Hmmm says the apothecary, that would have worked, but I already had a pair like that. This pair of flasks has exactly the same total capacity, still has linear dimensions that are exact fractions but the neither flask has diameter 1. Find a size that the smaller of his two flasks might have had. This is clearly asking for positive solutions to x3 + y3 = 9 with the solution being a neat fraction. In an earlier exercise you were invited to write a class to handle fractions. Before continuing with this one you might like to ensure that you have a version of it that uses BigInteger just to be certain that overflow will not scupper you. Here is an attack you can make on the problem. We are interested in solutions 3 to x + y3 = 9 and know an initial solution x = 1, y = 2. Imagine the cubic equation drawn as a graph (I know it is an implicit equation, so for now I will be happy if 4.8. IMPORTANT FEATURES OF THE CLASS LIBRARIES 165 you imagine that there is a graph and do not worry about actually drawing it!). The point (1,2) lies on the curve. Draw the tangent to the curve at (1,2). This straight line will have an equation of the form lx + my + n = 0 for some l, m and n which you can find after you have done a little differentiation. Now consider where the straight line meets the curve. If one eliminated y between the two equations the result is a cubic in x, but we know that x = 1 is one solution (because the curve and line meet there). In fact because the line is a tangent to the curve that is a double solution, and thus the cubic we had will necessarily have a factor (x − 1)2. If you divide that out you get a linear equation that gives you the x co-ordinate of the other place where the tangent crosses the curve. Solve that and substitute into the line equation to find the corresponding value of y. The pair x, y were found by solving what were in the end only linear equations, and so the numbers must be at worst fractions. This has therefore given another rational point on the cubic curve. What you get there is not a solution to the problem as posed, since one of x and y will end up negative. But maybe if you take a tangent at the new point that will lead you to a third possibility and perhaps following this path you will eventually manage to find a solution that is both rational and where both components are positive. Write code to try this out, and produce the tidiest fractional solution to the original puzzle that you can, ie the one with smallest denominators. 166 CHAPTER 4. BASIC USE OF JAVA Figure 4.9: Odds and Ends follow: watch what the lecturer happens to cover. Chapter 5 Designing and testing programs in Java At this stage in the “Programming in Java” course I will start to concentrate more on the “programming” part than the “Java”. This is on the basis that you have now seen most of the Java syntax and a representative sampling of the libraries: now the biggest challenge is how to use the language with confidence and competence. In parallel with this course you get other ones on Software Engineering. These present several major issues. One is that errors in software can have serious consequences, up to and including loss of life and the collapse of businesses. Another is that the construction of large computer-related products will involve teams of programmers working to build and support software over many years, and this raises problems not apparent to an individual programmer working on a private project. A third is that formal methods can be used when defining an planning a project to build a stable base for it to grow on, and this can be a great help. The emphasis is on programming in the large, which is what the term “Software Engineering” is usually taken to have at its core. Overall the emphasis is on recognition of the full life-time costs associated with software and the management strategies that might keep these costs under control. The remaining section of this Java course complements that perspective and looks at the job of one person or a rather small group, working on what may well be a software component or a medium sized program rather than on a mega-scale product. The intent of this part of the course is to collect together and emphasise some of the issues that lie behind the skill of programming. Good programmers will probably use many of the techniques mentioned here without being especially aware of what they are doing, but for everybody (even the most experienced) it can be very useful to bring these ideas out into the open. It seems clear to me that all computer science professionals should gain a solid grounding carrying out small projects before they actually embark on larger ones, even though a vision of what 167 168 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA will be needed in big projects help shape attitudes and habits. It is at least a myth current in the Computer Laboratory that those who intend to become (mechanical) engineers have as an early part of their training an exercise where they fashion a simple cube of solid metal, and they are judged on the accuracy of their work. Such an activity can be part of bringing them into direct touch with the materials that they will later be working with in more elaborate contexts. It gets them to build experience with tools and techniques (including those of measurement and assessment). Programming in the small can serve similar important purposes for those who will go on to develop large software systems: even when large projects introduce extra levels of complication and risk the design issues discussed here remain vital. One of the major views I would like to bring to the art (or craft, or science, or engineering discipline, depending on how one likes to look at it) of programming is an awareness of the value of an idea described by George Orwell in his book “1984”. This is doublethink, the ability to believe two contradictory ideas without becoming confused. Of course one of the original pillars of doublethink was the useful precept Ignorance is Strength, but collections of views specifically about the process of constructing programs. These notes will not be about the rest of the association of computers with surveillance, NewSpeak or other efficiencyenhancing ideas. The potentially conflicting views about programming that I want to push relate to the prospect of a project succeeding. Try to keep in your minds both the idea Programming is incredibly difficult and this program will never work correctly: I am going to have to spend utterly hopeless ages trying to coax it into passing even the most minimal test cases and its optimistic other face, which claims cheerfully In a couple of days I can crack the core of this problem, and then it will only take me another few to finish off all the details. These days even young children can all write programs. The concise way to express this particular piece of doublethink (and please remember that you really have to believe both part, for without the first you will make a total botch of everything, while without the second you will be too paralysed ever to start actual coding), is Writing programs is easy. A rather closely related bit of doublethink alludes both to the joy of achievement when a program appears to partially work and the simultaneous bitter way in which work with computers persistently fail. Computers show up our imperfections and frailties, which range through unwillingness to read specifications via inability to think straight all the way to incompetence in mere mechanical typing skills. The short-hand for the pleasure that comes from discovering one of your own mistakes, and having spent many frustrating hours tracking down something that is essentially trivial comes out as 169 Writing programs is fun. A further thing that will be curious about my presentation is that it does not present universal and provable absolute truths. It is much more in the style of collected good advice. Some of this is based on direct experience, other parts has emerged as an often-unstated collective view of those who work with computers. There are rather fewer books covering this subject than I might have expected. There is a very long reading list posted regularly on comp.software-eng, but most of it clearly assumes that by the time things get down to actually writing programs the reader will know from experience what to do. Despite the fact that it is more concerned with team-work rather than individual programming I want to direct you towards the Mythical Man Month[7], if only for the cover illustration of the La Brea Tar Pits1 with the vision that programmers can become trapped just as Figure 5.1: The La Brea tar pits. the Ice Age mammoths and so on were. Brooks worked for IBM at a time that they were breaking ground with the ambitious nature of their operating systems. The analogous Microsoft experience is more up to date and can be found in Writing 1 You may not be aware that the tar pits are in the middle of a thoroughly built-up part of Los Angeles, and when visiting them you can try to imagine some of the local school-children venturing too far and getting bogged down, thus luring their families, out for a week-end picnic, to a sticky doom. 170 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA Solid Code[18] which gives lots of advice that is clearly good way outside the context of porting Excel between Windows and the Macintosh. If you read the Solid Code book you will observe that it is concerned almost entirely with C, and its examples of horrors are often conditioned by that. You should therefore let it prompt you into thinking hard about where Java has made life safer than C and where it has introduced new and different potential pitfalls. For looking at programming tasks that are fairly algorithmic in style the book by Dijkstra[10] is both challenging and a landmark. There are places where people have collected together some of the especially neat and clever little programs they have come across, and many of these indeed contain ideas or lessons that may be re-cyclable. Such examples have come to be referred to as “pearls”[4][5]. Once one has worked out what program needs to be written ideas (now so much in the mainstream that this book is perhaps now out of date) can be found in one of the big presentations by some of the early proponents of structured programming[19]. Stepping back and looking at the programming process with a view to estimating programmer productivity and the reliability of the end product, Halstead[14] introduced some interesting sorts of software metrics, which twenty years on are still not completely free of controversy. All these still leave me feeling that there is a gap between books that describe the specific detail of how to use one particular programming language, and those concerned with large scale software engineering and project management. To date this gap has generally been filled by an apprentice system where trainee programmers are invited to work on progressively larger exercises and their output is graded and commented on by their superiors. Much like it is done here! With this course I can at least provide some background thoughts that might help the apprentices start on their progression a little more smoothly. When I started planning a course covering this material it was not quite obvious how much there was going to be for me to say that avoided recitations of the blindingly obvious and that was also reasonably generally applicable. As I started on the notes it became clear that there are actually a lot of points to be covered. To keep within the number of lectures that I have and to restrict these notes to a manageable bulk I am therefore restricting myself (mostly) to listing points for consideration and giving as concrete and explicit recommendations as I can: I am not including worked examples or lots of anecdotes that illustrate how badly things can go wrong when people set about tasks in wrong-minded ways. But perhaps I can suggest that as you read this document you imagine it expanded into a very much longer presentation with all that additional supporting material and with a few exercises at the end of each section. You can also think about all the points that I raise in the context of the various programming exercises that you are set or other practical work that you are involved with. 5.1. DIFFERENT SORTS OF PROGRAMMING TASKS 171 5.1 Different sorts of programming tasks Java experience illustrates very clearly that there are several very different sorts of activity all of which are informally refereed to as “programming”. On style of use of Java — of great commercial importance — involves understanding all of the libraries and stringing together uses of them to implement user interfaces and to move data around. In such cases the focus is entirely on exploiting the libraries, on human factors and on ensuring that the code’s behaviour agrees with the manual or on-line documentation that its users will work from. At another extreme come tasks that involve the design of many new data-structures and algorithmic innovations in their use. Often in this second style of program there will also be significant concern over efficiency. Given that there are different sorts of software it might be reasonable to start with a classification of possible sorts of program. There are three ways in which this may help with development: 1. Different sorts of computer systems are not all equally easy to build. For instance industrial experience has shown repeatedly that the construction of (eg) an operating system is very much harder than building a (again eg) a compiler even when the initial specifications and the amount of code to be written seem very similar. Thinking about the category into which your particular problem falls can help you to plan time-scales and predict possible areas of difficulty; 2. The way you go about a project can depend critically on some very high level aspects of the task. A fuller list of possibilities is given below, but two extreme cases might be (a) a software component for inclusion in a safety-critical part of an aero-space application, where development budget and timescale are subservient to an over-riding requirement for reliability, and (b) a small program being written for fun as a first experiment with a new programming language, where the program will be run just once and nothing of any real important hands on the results. It would be silly to carry forward either of the above two tasks using a mind-set tuned to the other: knowing where one is on the spectrum between can help make the selection of methodology and tools more rational; 3. I will make a point in these notes that program development is not something to be done in an isolated cell. It involves discussing ideas and progress with others and becoming aware of relevant prior art. Thinking about the broad area in which your work lies can help you focus on the resources worth investigating. Often some of these will not be at all specific to the 172 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA immediate description of what you are suppose to achieve but will concerned with very general areas such as rapid prototyping, formal validation, real-time responsiveness, user interfaces or whatever. I will give my list of possible project attributes. These are in general not mutually exclusive, and in all real cases one will find that these are not yes–no choices but more like items to be scored from 1 to 10. I would like to think of them as forming an initial survey that you should conduct before starting any detailed work on your program just to set it in context. When you find one or two of these scoring 9 or 10 out of 10 for relevance you know you have identified something important that ought to influence how you approach the work. If you find a project scores highly on lots of these items then you might consider trying to wriggle out of having to take responsibility for it, since there is a significant chance that it will be a disaster! The list here identifies potential issues, but does not discuss ways of resolving them: in many cases the project features identified here will just tell you which of the later sections in these notes are liable to be the more important ones for your particular piece of work. The wording in each of my descriptions will be intended to give some flavour of how severe instances of the issue being discussed can cause trouble, so keep cheerful because usually you will not be plunging in at the really deep end of the pool. Ill-defined One of the most common and hardest situations to face up to is when a computer project is not clearly specified. I am going to take this case to include ones where there appears to be a detailed and precise specification document but on close inspection the requirements as written down boil down to “I don’t know much about computer systems but I know what I like, so write me a program that I will like, please.” Clearly the first thing to do in such a case is to schedule a sub-project that has the task of obtaining a clear and concise description of what is really required, and sometimes this will of itself be a substantial challenge; Safety-critical It is presumably obvious that safety-critical applications need exceptional thought and effort put into their validation. But this need for reliability is far from an all-or-nothing one, in that the reputation of a software house (or indeed the grades obtained by a student) may depend on ensuring that systems run correctly at least most of the time, and that their failure modes appear to the user to be reasonable and soft. At the other extreme it is worth noting that in cases where robustness of code and reliability of results are not important at all (as can sometimes be the case, despite this seeming unreasonable) that fact can be exploited to give the whole project a much lighter structure and sometimes to make everything very much easier to achieve. A useful question to ask is “Does this program have to work 5.1. DIFFERENT SORTS OF PROGRAMMING TASKS 173 correctly in all circumstances, or does it just need to work in most common cases, or indeed might it be sufficient to make it work in just one carefully chosen case?” Large It is well established that as the size of a programming task increases the amount of work that goes into it grows much more rapidly than the number of lines of code (or whatever other simple measurement you like) does. At various levels of task size it becomes necessary to introduce project teams, extra layers of formal management and in general to move away from any pretence that any single individual will have a full understanding of the whole effort. If your task and the associated time-scales call for a team of 40 programmers and you try it on your own maybe you will have difficulty finishing it off! Estimating the exact size that a program will end up or just how long it will take to write is of course very hard, but identifying whether it can be done by one smart programmer in a month or if it is a big team project for five years is a much less difficult call to make. Shifting sands If either project requirements or resources can change while software development is under way then this fact needs to be allowed for. Probable only a tiny minority of real projects will be immune from this sort of distraction, since even for apparently well-specified tasks it is quite usual that experience with the working version of the program will lead to “wouldn’t it be nice if . . . ” ideas emerging even in the face of carefully discussed and thought out early design decisions that the options now requested would not be supportable. Remember that Shifting Sands easily turn into Tar Pits! Figure 5.2: The museum frieze at La Brea. Urgent When are you expected to get this piece of work done? How firm is the deadline? If time constraints (including the way that this project will compete with other things you are supposed to do) represents a real challenge it 174 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA is best to notice that early on. Note that if, while doing final testing on your code, you find that it has a bug in it there may be no guarantee that you can isolate or fix this to any pre-specified time-scale. This is because (at least for most people!) there is hardly any limit to the subtlety of bugs and the amount of time and re-work needed to remove them. If the delivery date for code is going to be rigidly enforced (as is the case with CST final year projects!) this fact may be important: even if there is plenty of the project as a whole a rigid deadline can make it suddenly become urgent at the last minute; Unrealistic It is quite easy to write a project specification that sounds good, but is not grounded in the real world. A program that modelled the stock markets and thereby allowed you to predict how to manage your portfolio, or one to predict winning numbers in the national lottery, or one to play world-class chess. . . Now of course there are programs that play chess pretty well, and lots of people have made money out of the other two projects (in one case the statistics that one might apply is much easier than the other!), but the desirability of the finished program can astonishingly often blind one to the difficulties that would arise in trying to achieve it. In some cases a project might be achievable in principle but is beyond either today’s technology or this week’s budget, while in other cases the idea being considered might not even realistic given unlimited budget and time-scales. There are of course places where near-unreasonable big ideas can have a very valuable part to play: in a research laboratory a vision of one of these (currently) unrealistic goals can provide direction to the various smaller and better contained projects that each take tiny steps towards the ideal. At present my favourite example of something like this is the idea of nanotechnology with armies of molecular-scale robots working together to build their own heirs and successors. The standard example of a real project that many (most?) realistic observers judged to be utterly infeasible was the “Star Wars” Strategic Defence Initiative, but note that at that sort of level the political impact of even starting a project may be at least as important as delivery of a working product! Multi-platform It is a luxury if a program only has to work on a single fixed computer system. Especially as projects become larger there is substantial extra effort required to keep them able to work smoothly on many different systems. This problem can show up with simple issues such as word-lengths, byte-arrangements in memory and compiler eccentricities, but it gets much worse as one looks at windowed user interfaces, multi-media functions, network drivers and support for special extra plug-in hardware; 5.1. DIFFERENT SORTS OF PROGRAMMING TASKS 175 Long life-time The easiest sort of program gets written one afternoon and is thrown away the next day. It does not carry any serious long-term support concerns with it. However other programs (sometimes still initially written in little more than an afternoon) end up becoming part of your life and get themselves worked and re-worked every few years. In my case the program I have that has the longest history was written in around 1972 in Fortran, based on me having seen one of that year’s Diploma dissertations and having (partly unreasonably) convinced myself I could do better. The code was developed on Titan, the then University main machine. I took it to the USA with me when I spent a year there and tried to remove the last few bugs and make it look nicer. When the University moved up to an IBM mainframe I ran it on that, and at a much later stage I translated it (semi automatically) into BBC basic and ran it (very slowly) on a BBC micro. By last year I had the code in C with significant parts of the middle of it totally re-written, but with still those last few bugs to find ways of working around. If I had been able to predict when I started how long this would be of interest to me for maybe I would have tried harder to get it right first time! Note the radical changes in available hardware and sensible programming language over the lifetime of this program; User interface For programs like modern word processors there is a real chance that almost all of the effort and a very large proportion of the code will go into supporting the fancy user interface, and trying to make it as helpful and intuitive as possible. Actually storing and editing the text could well be fairly straight forward. When the smoothness of a user interface is a serious priority for a project then the challenge of defining exactly what must happen is almost certainly severe too, and in significant projects will involve putting test users in special usability laboratories where their eyemovement can be tracked by cameras and their key-strokes can be timed. The fact that an interface provides lots of windows and pull-down menus does not automatically make it easy to use; Diverse users Many commercial applications need to satisfy multiple users with diverse needs as part of a single coherent system. This can extend to new computer systems that need to interwork seamlessly with multiple existing operational procedures, including existing computer packages. Some users may be nervous of the new technology, while others may find excessive explanation an offensive waste of their time. The larger the number of interfaces needed and the wider the range of expectations the harder it will be to make a complete system deliver total satisfaction; Speed critical Increasingly these days it makes sense to buy a faster computer if 176 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA Figure 5.3: The search for speed can lead to eccentric-looking results. some task seems to take a little longer than is comfortable. However there remain some areas where absolute performance is a serious issue and where getting the very best out of fixed hardware resources can give a competitive edge. The case most in my mind at present is that of (high security) encryption, where the calculations needed are fairly demanding but where there is real interest in keeping some control over the extra hardware costs that user are expected to incur. If speed requirements lead to need for significant assembly code programming (or almost equivalently to the design of task-specific silicon) then the resource requirements of a project can jump 5.1. DIFFERENT SORTS OF PROGRAMMING TASKS 177 dramatically. If in the other hand speed is of no importance at all for some task it may become possible to use a higher level programming system, simpler data structures and algorithms and generally save a huge amount of aggravation; Real time Real-time responsiveness is characteristic of many control applications. It demands that certain external events be given a response within a pre-specified interval. At first this sounds like a variant on tasks that are just speed-critical, but the fine granularity at which performance is specified tends to influence the entire shape of software projects and rule out some otherwise sensible approaches. Some multi-media applications and video games will score highly in this category, as will engine management software for cars and flight control software for airports; Memory critical A programming task can be made much harder if you are tight on memory. The very idea of being memory-limited can feel silly when we all know that it is easy to go out and buy another 64 Mbytes for (currently) of the order of £502 . But the computer in your cell-phone will have an amount of memory selected on the basis of a painful compromise between cost (measured in pennies), the power drain that the circuitry puts on the battery (and hence the expected battery life) and the set of features that can be supported. And the software developers are probably give the memory budget as a fixed quantity and invited to support as long a list of features as is at all possible within it; Add-on A completely fresh piece of software is entitled to define its own file formats and conventions and can generally be designed and build without too much hindrance. But next year the extension to that original package is needed, or the new program is one that has to work gracefully with data from other people’s programs. When building an add-on it is painfully often the case that the existing software base is not very well documented, and that the attempted new use of it reveals previously unknown bugs or limitations in the core system. Thus the effort that will need to be put into the second package may be much greater than would have been predicted based on experience from the first; Embedded If the computer you are going to program is one in an electric eggtimer (or maybe a toy racing car, or an X-ray scanner) then testing may involve be a quite different experience from that you become used to when 2 Last year these notes indicates 16 Mbytes for £50! I may have rounded prices up and down somewhat but still. . . 178 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA debugging ordinary applications that run on standard work-stations. In particular it may become necessary to become something of an expert in the hardware and electronics and also in the application area of the system within which your code will be placed; Tool-weak environment This is a follow-on from the “embedded” heading, in that it is perhaps easiest the envisage an electric pop-up toaster where anything that slowed down or enlarged the code being run would perturb system timing enough to burn the toast, and where the target hardware is not automatically equipped with printers and flashing lights that can be used to help sense what is going on inside its CPU. For some such cases it is possible to buy or build real-time emulators or to wire in extra probes into a debuggable version of the hardware. There are other cases where either technology or budget mean that program development has to be done with a slow turn-around on testing and with only very limited ability to discover what happened when a bug surfaced. It is incredibly easy to simulate such a toolweak environment for yourself by just avoiding the effort associated with becoming familiar with automated testing tools, debuggers and the like; Novel One of the best and safest ways of knowing that a task is feasible is to observe that somebody else did it before, and their version was at least more or less satisfactory. The next best way is to observe that the new task is really rather similar to one that was carried out successfully in the past. This clearly leads to the obvious observation that if something is being attempted and there are no precedents to rely on then it becomes much harder to predict how well things will work out, and the chances of nasty surprises increases substantially. There are two sort of program not listed above which deserve special mention. The first is the implementation of a known algorithm. This will usually end up as a package or a subroutine rather than a complete free-standing program, and there are plenty of algorithms that are complicated enough that programming them is a severe challenge. However the availability of a clear target and well specified direction will often make such programming tasks relatively tractable. It is however important to distinguish between programming up a complete and known algorithm (easyish) from developing and then implementing a new one, and uncomfortably often things that we informally describe as algorithms are in fact just strategies, and lots of difficult and inventive fine detail has to be filled into make them realistic. The second special sort of program is the little throw-away one, and the recognition that such programs can be lashed together really fast and without any fuss 5.2. ANALYSIS AND DESCRIPTION OF THE OBJECTIVE 179 is important, since it can allow one to automate other parts of the program development task through strategic use of such bits of jiffy code. 5.2 Analysis and description of the objective Sometimes a programming task starts with you being presented with a complete, precise and coherent explanation of exactly what has to be achieved. When this is couched in language so precise that there is not possible doubt about what is required you might like to ask why you are being asked to do anything, since almost all you need to do is to transcribe the specification into the particular syntax of the (specified) programming language. Several of the Part IA tickable problems come fairly close to this pattern, and there the reason you are asked to do them is exactly so you get practical experience with the syntax of the given language and the practical details of presenting programs to the computer. But that hardly counts as serious programming! Assuming that we are not in one of these artificial cases, it is necessary to think about what one should expect to find in a specification and what does not belong there. It is useful to discuss the sorts of language used in specifications, and to consider who will end up taking prime responsibility for everything being correct. A place to start is with the observation that a specification should describe what is wanted, rather than how the desired effect is to be achieved. This ideal can be followed up rather rapidly by the observation that it is often amazingly difficult to know what is really wanted, and usually quite a lot of important aspects of the full list of requirements will be left implicit or as items where you have to apply your own judgement. This is where it is useful to think back to the previous section and decide what style of project was liable to be intended and where the main pressure points are liable to be. 5.2.1 Important Questions I have already given a check-list that should help work out what general class of problem you are facing. The next stage is to try to identify and concentrate on areas of uncertainty in your understanding of what has to be done. Furthermore initial effort ought to go into understanding aspects of the problem that are liable to shape the whole project: there is no point in agonising over cosmetic details until the big picture has become clear. Probably the best way of sorting this out is to imagine that some magic wand has been waved and it has conjured up a body of code and documentation that (if the fairy really was a good one!) probably does everything you need. However as a hard-headed and slightly cynical person 180 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA you need to check it first. Deciding what you are going to look for to see if the submitted work actually satisfied the project’s needs can let you make explicit a lot of the previously slightly woolly expectations you might have. This viewpoint moves you from the initial statement “The program must achieve X” a little closer to “I must end up convinced that the program achieves X and here is the basis for how that conviction might be carried”. Other things that might (or indeed might not) reveal themselves at this stage are: 1. Is user documentation needed, and if so how detailed is it expected to be? Is there any guess for how bulky the user manual will be? 2. How formal should documentation of the inner workings of the code be? 3. Was the implementation language to be used pre-specified, and if not what aspects of the problem or environment are relevant to the choice? 4. Is the initial specification a water-tight one or does the implementer have to make detailed design decisions along the way? With regard to choice of programming language note that evidence from studies that have watched the behaviour of real programmers suggests that to a good first approximation it is possible to deliver the same number of lines of working documented code per week almost whatever language it is written in. A very striking consequence of this is that languages that are naturally concise and which provide built-in support for more of the high-level things you want to do can give major boosts to productivity. The object of all this thought is to lead to a proper specification of the task. Depending on circumstances this may take one of a number of possible forms: 5.2.2 Informal specifications Documents written in English, however pedantically phrased and however voluminous, must be viewed as informal specifications. Those who have a lot of spare time might try reading the original description of the language C[16] where Appendix A is called a reference manual and might be expected to form a useful basis for fresh implementations of the language. Superficially it looks pretty good, but it is only when you examine the careful (though still “informal” in the current context) description in the official ANSI standard[22] that it becomes clear just how much is left unsaid in the first document. Note that ANSI C is not the same language as that defined by Kernighan and Ritchie, and so the two documents just mentioned can not be compared quite directly, and also be aware that spotting and making clear places where specifications written in English are not precise 5.2. ANALYSIS AND DESCRIPTION OF THE OBJECTIVE 181 is a great skill, and one that some people enjoy exercising more than others do! The description in section 5.18 is another rather more manageable example of an informal textual specification. When you get to it you might like to check to see what it tells you what to do about tabs and back-spaces, which are clearly characters that have an effect on horizontal layout. What? It fails to mention them? Oh dear! 5.2.3 Formal descriptions One response to the fact that almost all informal specifications are riddled with holes (not all of which will be important: for instance it might be taken as understood by all that messages that are printed so that they look like sentences should be properly spelt and punctuated) has been to look for ways of using formal description languages. The ZED language (developed at Oxford3, and sometimes written as just Z) is one such and has at times been taught in Software Engineering courses here. The group concerned with the development of the language ML were keen to use formal mathematically-styled descriptive methods to define exactly what ML ought to do in all possible circumstances. Later on in the CST there are whole lecture courses on Specification and Verification and so I am not going to give any examples here, but will content myself by observing that a good grounding in discrete mathematics is an absolute pre-requisite for anybody thinking of working this way. 5.2.4 Executable specifications One group of formal specification enthusiasts went off and developed ever more powerful mathematical notations to help them describe tasks. Another group observed that sometimes a careful description of what must be achieved looks a bit like a program in a super-compact super-high-level programming language. It may not look like a realistic program, in that it may omit lots of explanation about how objectives should be achieved and especially how they should be achieved reasonably efficiently. This leads to the idea of an executable specification, through building an implementation of the specification language. This will permitted to run amazingly slowly, and its users will be encouraged to go all out for clarity and correctness. To give a small idea of what this might entail, consider the job of specifying a procedure to sort some data. The initial informal specification might be that the output should be a re-ordering of the input such that the values in the output be in non-descending order. An executable specification might consist of three components. The first would create a list of all the different 3 http://www.comlab.ox.ac.uk/oucl/prg.html 182 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA permutations of the input. The second would be a procedure to inspect a list and check to see if its elements were in non-descending order. The final part would compose these to generate all permutations then scan through them one at a time and return the first non-descending one found. This would not be a good practical sorting algorithm, but could provide a basis for very transparent demonstrations that the process shown did achieve the desired goal! It should be remembered that an executable specification needs to be treated as such, and not as a model for how the eventual implementation will work. A danger with the technique is that it is quite easy for accidental or unimportant consequences of how the specification is written to end up as part of the project requirements. 5.3 Ethical Considerations Quite early on when considering a programming project you need to take explicit stack of any moral or ethical issues that it raises. Earlier in the year you have had more complete coverage of the problems of behaving professionally, so here I will just give a quick check-list of some of the things that might give cause for concern: 1. Infringement of other people’s intellectual property rights, be they patents, copyright or trade secrets. Some companies will at least try to prevent others from creating new programs that look too much like the original. When licensed software is being used the implications of the license agreement may become relevant; 2. Responsibility to your employer or institution. It may be that certain sorts of work are contrary to local policy. For instance a company might not be willing to permit its staff to politically motivated virtual reality simulations using company resources, and this University has views about the commercial use of academic systems; 3. A computing professional has a responsibility to give honest advice to their “customer” when asked about the reasonableness or feasibility of a project, and to avoid taking on work that they know they are not qualified to do; 4. It can be proper to take a considered stance against the development of systems that are liable to have a seriously negative impact on society as a whole. I have known some people who consider this reason to avoid any involvement with military or defence-funded computing, while others will object to technology that seems especially liable to make tracking, surveillance or eavesdropping easier. Those of you with lurid imaginations can no 5.4. HOW MUCH OF THE WORK HAS BEEN DONE ALREADY? 183 doubt envisage plenty more applications of computers that might be seen as so undesirable that one should if necessary quit a job rather than work on them. 5.4 How much of the work has been done already? The points that I have covered so far probably do not feel as if they really help you get started when faced with a hard-looking programming task, although I believe that working hard to make sure you really understand the specification you are faced with is in fact always a very valuable process. From now onwards I move closer to the concrete and visible parts of the programming task. The first question to ask here is “Do I actually have to do this or has it been done before?” There are three notable cases where something has been done before but it is still necessary to do it again. Student exercises are one of these, and undue reliance on the efforts of your predecessors is gently discouraged. Sometimes a problem has been solved before, but a solution needs to be re-created without reference to the original version because the original is encumbered with awkward commercial4 restrictions or is not locally available. The final cause for re-implementation is if the previous version of the program concerned was a failure and so much of a mess that any attempt to rely on it would start the new project off in wrong-minded directions. Apart from these cases the best way to write any program at all is to adopt, adapt and improve as much existing technology as you can! This can range from making the very second program that you ever write a variation on that initial “Hello World” example you were given through to exploiting existing large software libraries. The material that can be reclaimed may be as minor as a bunch of initial comments saying who you (the author) are and including space to describe what the program does. It might just be some stylised “import” statements needed at the head of almost any program you write. If you need a tree structure in today’s program do you have one written last week which gives you the data type definition and some of the basic operations on trees? Have you been provided with a collection of nice neat sample programs (or do you have a book or CD ROM with some) that can help? Many programming languages are packaged with a fairly extensive collection of chunks of sample code. Most programming languages come with standardised libraries that (almost always) mean there is no need for you to write your own sorting procedure or code to convert floating point values into or out of textual form. In many important areas 4 Remember that if the restriction is in the form of a patent then no amount of re-implementation frees you from obligations to the patent-owner, and in other cases you may need to be able to give a very clear demonstration that your new version really has been created completely independently. 184 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA there will be separate libraries that contain much much more extensive collections of facilities. For instance numerical libraries (eg the one from NAG) are where you should look for code to solve sets of simultaneous equations or to maximise a messy function of several variables. When you need to implement a windowed interface with pull-down menus and all that sort of stuff again look to existing library code to cope with much of the low-level detail for you. Similarly for data compression, arbitrary precision integer arithmetic, image manipulation. . . Observing that there is a lot of existing support around does not make the problem of program construction go away: knowing what existing code is available is not always easy, and understanding both how to use it and what constraints must be accepted if it is used can be quite a challenge. For instance with the NAG (numerical) library it may take beginners quite a while before they discover that E04ACF (say, and not one of the other half-dozen closely related routines) is the name of the function they need to call and before they understand exactly what arguments to pass to it. As well as existing pre-written code (either in source or library form) that can help along with a new project there are also packages that write significant bodies of code for you, basing what they do one on either a compact descriptive input file or interaction with the user through some clever interface. The wellestablished examples of this are the tools yacc and lex that provide a convenient and reliable way of creating parsers. Current users of Microsoft’s Visual C++ system will be aware of the so-called “Wizards” that it provides that help create code to implement the user interface you want, and there are other commercial program generators in this and a variety of business application areas. To use one of these you first have to know of its availability, and then learn how to drive it: both of these may involve an investment of time, but with luck that will be re-paid with generous interest even on your first real use. In some cases the correct use of a program generation tool is to accept its output uncritically, while on other occasions the proper view is to collect what it creates, study it and eventually adjust the generated code until you can take direct responsibility for subsequent support. Before deciding which to do you need to come to a judgement about the stability and reliability of the program generator and how often you will need to adjust your code by feeding fresh input in to the very start. Another way in which existing code can be exploited is when new code is written so that it converts whatever input it accepts into the input format for some existing package, one that solves a sufficiently related problem that this makes some sense. For instance it is quite common to make an early implementation of a new programming language work by translating the new language into some existing one and then feeding the translated version into an existing compiler. For early versions of ML the existing language was Lisp, while for Modula 3 some compilers work by converting the Modula 3 source into C. Doing this may result 5.5. WHAT SKILLS AND KNOWLEDGE ARE AVAILABLE? 185 in a complete compiler that is slower and bulkier than might otherwise be the case, but it can greatly reduce the effort in building it. 5.5 What skills and knowledge are available? Figure 5.4: Nancy Silverton’s bakery is in La Brea near the tar pits, and her book (Bread from the La Brea Bakery) is unutterably wonderful. I like her chocolatecherry loaf. This photo is if the racks in her shop. Not much about Java I agree but baking good bread is at least as important to know about as computing. A balance needs to be drawn between working through a programming project using only the techniques and tools that you already know and pushing it forward using valuable but unfamiliar new methods. Doing something new may slow you down substantially, but an unwillingness to accept that toll may lead to a very pedestrian style of code development using only a limited range of idioms. There is a real possibility that short-term expediency can be in conflict with longer term productivity. Examples where this may feel a strain include use of formal methods, new programming languages and program generation tools. The main point to be got across here is that almost everything to do with computers changes every five years or so, and so all in the field need to invest some of their effort in continual personal re-education so that their work does not look too much as if it has been chipped out using stone axes. The good news is that although detailed technology changes the skills associated with working through a significant project should grow with experience, and the amount of existing code that an old hand will have to pillage may be quite large, and so there is a reasonable prospect for a long term future for those with skills in software design and construction. Remember that all the books on Software Engineering tell us that the competence of 186 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA the people working on a project can make more difference to its success than any other single factor. It is useful to have a conscious policy of collecting knowledge about what can be done and where to find the fine grubby details. For example the standard textbook[9] contains detailed recipes for solving all sorts of basic tasks. Only rarely will any one of these be the whole of a program you need to write, but quite often a larger task will be able to exploit one or more of them. These and many of the other topics covered in the CST are there because there is at least a chance that they may occasionally be useful! It is much more important to know what can be done than how to do it, because the how can always be looked up when you need it. 5.6 Design of methods to achieve a goal Perhaps the biggest single decision to be made when starting the detailed design of a program is where to begin. The concrete suggestions that I include here are to some extent caricatures; in reality few real projects will follow any of them totally but all should be recognisable as strategies. The crucial issue is that it will not be possible to design or write the whole of a program at once so it is necessary to split the work into phases or chunks. 5.6.1 Top-Down Design In Top Down Design work on a problem starts by writing a “program” that is just one line long. Its text is: { solveMyProblem(); } where of course the detailed punctuation may be selected to match the programming language being used. At this stage it is quite reasonable to be very informal about syntax. A next step will be to find some way of partitioning the whole task into components. Just how these components will be brought into existence is at present left in the air, however if we split things up in too unreasonable a way we will run into trouble later on. For many simple programs the second stage could look rather like: /* My name, today’s date, purpose of program */ import Standard-libraries; { /* declare variables here */ data = readInData(); results = calculate(data); 5.6. DESIGN OF METHODS TO ACHIEVE A GOAL 187 displayResults(results); } The ideal is that the whole development of the program should take place in baby-sized steps like this. At almost every stage there will be a whole collection of worrying-looking procedures that remain undefined and not yet thought about, such as Calculate above. It is critical not to worry too much about these, because each time a refinement is made although the number of these unresolved problems may multiply the expected difficulty of each will reduce. Well it had better, since all the ones that you introduce should be necessary steps towards the solution of your whole original task, and it makes sense to expect parts to be simpler than the whole. After a rather few steps in the top-down development process one should expect to have a fully syntactically correct main program that will not need any alterations later as the low level details of the procedures that it calls get sorted out. And each of the components that remain to be implemented should have a clearly understood purpose (for choice that should be written down) and each such component should be clearly separated from all the others. That is not to say that the component procedures might not call each other or rely on what they each can do, but the internal details of any one component should not matter to any other. This last point helps focus attention on interfaces. In my tiny example above the serious interfaces are represented by the variables data and results which pass information from one part of the design to the next. Working out exactly what must be captured in these interfaces would be generally need to be done fairly early on. After enough stages of elaboration the bits left over from top-down design are liable to end up small enough that you just code them up without need to worry: anything that is trivial you code up, anything that still looks murky you just apply one more expansion step to. With luck eventually the process ends. There are two significant worries about top-down design. These are “How do I know how to split the main task up?” and “But I can’t test me code until everything is finished!”. Both of these are proper concerns. Splitting a big problem up involves finding a strategy for solving it. Even though this can be quite hard, it is almost always easier to invent a high-level idea for how to solve a problem than it is to work through all the details, and this is what top-down programming is all about. In many cases sketching on a piece of paper what you would do if you had to solve the problem by hand (rather than by computer) can help. Quite often the partition of a problem you make may end up leading your design into some uncomfortable dead end. In that case you need to look back and see which steps in your problem refinement represented places where you had real choice and which ones were pretty much inevitable. It is then necessary to go back to one of the stages where a choice was possible and to rethink things in the light of your new understanding. To make this process sensible 188 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA you should refuse to give up fleshing out one particular version of a top-down design until you are in a position to give a really clear explanation of why the route you have taken represents failure, because without this understanding you will not know how far back you need to go in the re-planning. As an example of what might go wrong, the code I sketched earlier here would end up being wrongly structured if user interaction was needed, and that interaction might be based on evaluation of partial results. To make that sort of interface possible it might be necessary to re-work the design as (say) /* My name, today’s date, purpose of program */ import Standard-libraries; { /* declare variables here */ /* set empty data and results */ while not finished do { extra = readInMoreData(); if (endOfUserInput(extra)) finished = true; else { data = combine(data, extra); results = upDateResults(results, data); displaySomething(results); } } displayFinalResults(results); } which is clearly getting messier! And furthermore my earlier and shorter version looked generally valid for lots of tasks, while this one would need careful extra review depending on the exact for of user interaction required. There is a huge amount to be said in favour of being able to test a program as it is built. Anybody who waits right to the end will have a dreadful mix of errors at all possible levels of abstraction to try to disentagle. At first sight it seems that top-down design precludes any early testing. This pessimism is not well founded. The main way out is to write stubs of code that fill in for all the parts of your program that have not yet been written. A stub is a short and simple piece of code that takes the place of something that will later on be much more messy. It does whatever is necessary to simulate some minimal behaviour that will make it possible to test the code around it. Sometimes a stub will just print a warning message and stop when it gets called! On other occasions one might make a stub print out its parameters and wait for human intervention: it then reads something back in, packages it up a bit and returns it as a result. The human assistant actually 5.6. DESIGN OF METHODS TO ACHIEVE A GOAL 189 did all the clever work. There are two other attitudes to take to top-down design. One of these is to limit it to design rather than implementation. Just use it to define a skeletal shape for your code, and then make the coding and testing a separate activity. Obviously this only makes sense when you have enough confidence that you can be sure that the chunks left to be coded will in fact work out well. The final view is to think of top-down design as an ideal to be retrofitted to any project once it is complete. Even if the real work on a project went in fits and starts with lots of false trails and confusion, there is a very real chance that it can be rationalised afterwards and explained top-down. If that is done then it is almost certain that a clear framework has been built for anybody who needs to make future changes to the program. 5.6.2 Bottom-Up Implementation Perhaps you are uncertain about exactly what your program is going to do or how it will solve its central problems. Perhaps you want to make sure that every line of code you ever write is documented, tested and validated to death before you move on from it and certainly before you start relying on it. Well these concerns lead you towards a bottom-up development strategy. The idea here is to identify a collection of smallish bits of functionality that will (almost) certainly be needed as part of your complete program, and to start by implementing these. This avoids having to thing about the hard stuff for a while. For instance a compiler-writer might start by writing code to read in lines of program and discard comments, or to build up a list of all the variable names seen. Somebody starting to write a word processor might begin with pattern-matching code ready for use in searchand-replace operations. In almost all large projects there are going to be quite a few fundamental units of code that are obviously going to be useful regardless of the high level structure you end up with. The worry with bottom-up construction is that it does not correspond to having any overall vision of the final result. That makes it all to easy to end up with a collection of ill-co-ordinated components that do not quite fit together and that do not really combine to solve the original problem. At the very least I would suggest a serious bout of top-down design effort be done before any bottom-up work to try to put an overall framework into place. There is also a clear prospect that some of the units created during bottom-up work may end up not being necessary after all so the time spend on them was wasted. An alternative way of thinking about bottom-up programming can soften the impact of these worries. It starts by viewing a programming language not just as a collection of fragments of syntax, but as a range of ways of structuring data and of performing operations upon it. The fact that some of these operations happen to be hard-wired into the language (as integer arithmetic usually is) while others exist as 190 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA collections of subroutines (floating point arithmetic on 3000-digit numbers would normally be done that way) is of secondary importance. Considered this way each time you define a new data type or write a fresh procedure you have extended and customised your programming language by giving it support for something new. Bottom-up programming can then be seen as gradually building layer upon layer of extra support into your language until it is rich in the operations most important in your problem area. Eventually one then hopes that the task that at first had seemed daunting becomes just half a dozen lines in the extended language. If some of the procedures built along the way do not happen to be used this time, they may well come in handy the next time you have to write a program in the same application area, so the work they consumed has not really been wasted after all. The language Lisp is notable for having sustained a culture based on this idea of language extension. 5.6.3 Data Centred Programming Both top-down and bottom-up programming tend to focus on what your program looks like and the way in which it is structured into procedures. An alternative is to concentrate not on the actions performed by the code but on the way in which data is represented and the history of transformations that any bit of data will be subject to. These days this idea is often considered almost synonymous with an Object Oriented approach where the overall design of the class structure for a program is the most fundamental feature that it will have. Earlier (and pre-dating the widespread use of Object Oriented languages) convincing arguments for design based on the entities that a program must manipulate or model come from Jackson Structured Programming and Design[8]. More recently SSADM[3] has probably become one of the more widespread design and specification methodologies for commercial projects. 5.6.4 Iterative Refinement My final strategy for organising the design of a complete program does not even expect to complete the job in one session. It starts by asking how the initial problem can be restricted or simplified to make it easier to address. And perhaps it will spot how the most globally critical design decisions for the whole program could me made in two or three different ways, with it hard to tell in advance which would work out best in the end. The idea is then to start with code for a scruffy mock-up of a watered down version of the desired program using just one of these sets of design decisions. The time and effort needed to write a program grows much faster then linearly with the size of the program: the natural (but less obvious) consequence of this is that writing a small program can be much quicker and 5.7. HOW DO WE KNOW IT WILL WORK? 191 easier than completing the full version. It may in some cases make sense even to write several competing first sketches of the code. When the first sketch version is working it is possible to step back and evaluate it, to see if its overall shape is sound. When it has been adjusted until it is structurally correct, effort can go into adding in missing features and generally upgrading it until it eventually gets transformed into the beautiful butterfly that was really wanted. Of all the methods that I have described this is the one that comes closest to allowing for “experimental” programming. The discipline to adhere to is that experiments are worthy of that tag if the results from them can be evaluated and if something can thus be learned from them. 5.6.5 Which of the above is best? The “best” technique for getting a program written will depend on its size as well as its nature. I think that puritanical adherence to any of the above would be unreasonable, and I also believe that inspiration and experience (and good taste) have important roles to play. However if pushed into an opinion I will vote for presenting a design or a program (whether already finished or still under construction) as if it were prepared top-down, with an emphasis on the early design of what information must be represented and where it must pass from one part of the code to another. 5.7 How do we know it will work? Nobody should ever write a program unless they have good reason to believe that it ought to work. It is of course proper to recognise that it will not work, because typographic errors and all sorts of oversights will ensure that. But the code should have been written so that in slightly idealised world where these accidental imperfections do not exist it would work perfectly. Blind and enthusiastic hope is not sufficient to make programs behave well, and so any proper design needs to have lurking behind it the seeds of a correctness proof. In easy-going times this can remain untended as little comments that can just remind you of your thinking. When a program starts to get troublesome it can be worth growing these comments into short essays that explain what identities are being preserved intact across regions of code, why your loops are guaranteed to terminate and what assumptions about data are important, and why. In yet more demanding circumstances it can become necessary to conduct formal validation procedures for code. The easiest advice to give here is that before you write even half a dozen lines of code you should write a short paragraph of comment that explains what the code is intended to achieve and why your method will work. The comment 192 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA should usually not explain how it works (the code itself is all about “how”), but why. To try to show that I (at least sometimes!) follow this advice here is a short extract from one of my own5 programs. . . /* * Here is a short essay on the interaction between flags * and properties. It is written because the issue appears * to be delicate, especially in the face of a scheme that * I use to speed things up. * (a) If you use FLAG, REMFLAG and FLAGP with some indicator then that indicator is known as a flag. * * (b) If you use PUT, REMPROP and GET with an indicator then what you have is a property. * (c) Providing the names of flags and properties are * disjoint no difficulty whatever should arise. * (d) If you use PLIST to gain direct access to property * lists then flags are visible as pairs (tag . t) and * properties as (tag . value). * (e) Using RPLACx operations on the result of PLIST may * cause system damage. It is considered illegal. * Also changes made that way may not be matched in * any accelerating caches that I keep. * * (f) After (FLAG ’(id) ’tag) [when id did not previously have any flags or properties] a call (GET ’id ’tag) * will return t. * (g) After (PUT ’id ’tag ’thing) a call (FLAGP ’id * ’tag) will return t whatever the value of "thing". * A call (GET ’id ’tag) will return the saved value * (which might be nil). Thus FLAGP can be thought of * as a function that tests if a given property is * attached to a symbol. * (h) As a consequence of (g) REMPROP and REMFLAG are * really the same operation. * / * Lisp_Object get(Lisp_Object a, Lisp_Object b) { Lisp_Object pl, prev, w, nil = C_nil; int n; /* 5 This happens to be written in C rather than Java, but since most of it is comment maybe that does not matter too much. 5.7. HOW DO WE KNOW IT WILL WORK? 193 * In CSL mode plists are structured like association * lists, and NOT as lists with alternate tags and values. * There is also a bitmap that can provide a fast test for * the presence of a property... */ if (!symbolp(a)) { #ifdef RECORD_GET record_get(b, NO); errexit(); #endif return onevalue(nil); } ... etc etc The exact details of what I am trying to do are not important here, but the evidence of mind-clearing so that there is a chance to get the code correct first time is. Note how little the comment before the procedure has to say about low-level implementation details, but how much about specifications, assumptions and limitations. I would note here that typing a program in is generally one of the least timeconsuming parts of the whole programming process, and these days disc storage is pretty cheap, and thus various reasons which in earlier days may have discouraged layout and explanation in code no longer apply. Before trying code and as a further check that it ought to work it can be useful to “walk through” the code. In other words to pretend to be a computer executing it and see if you follow the paths and achieve the results that you were supposed to. While doing this it can be valuable to think about which paths through the code are common and which are not, since when you get to testing it may be that the uncommon paths do not get exercised very much unless you take special steps to cause them to be activated. The “correctness” that you will be looking for can be at several different levels. A partially correct program is one that can never give an incorrect answer. This sounds pretty good until you recognise that there is a chance that it may just get stuck in a loop and thereby never give any answer at all! It is amazingly often much easier to justify that a program is partially correct than to go the whole hog and show it is correct, ie that not only is it partially correct but that it will always terminate. Beyond even the requirements of correctness will be performance demands: in some cases a program will need not only to deliver the right answers but to meet some sort of resource budget. Especially if the performance target is specified as being for performance that is good “on the average” it can be dreadfully hard to prove, and usually the only proper way to start is by designing and justifying algorithms way before any mention of actual programming arises. 194 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA A final thing to check for is the possibility that your code can be derailed by unhelpful erroneous input. For instance significant security holes in operating systems have in the past been consequences of trusted modules of code being too trusting of their input, and them getting caught out by (eg) input lines so long that internal buffers overflowed thereby corrupting adjacent data. The proper mind-set to settle in to while designing and starting to implement code is pretty paranoid: you want the code to deliver either a correct result or a comprehensible diagnostic whenever anything imaginable goes wrong in either the data presented to it or its own internal workings. This last statement leads to a concrete suggestion: make sure that the code can test itself for sanity and correctness every so often and insert code that does just that. The assertions that you insert will form part of your argument for why the program is supposed to work, and can help you (later on) debug when it does not. 5.8 While you are writing the program Please remember to get up and walk around, to stretch, drink plenty of water, sit up straight and all the other things mentioned at the Learning Day as relevant occupational health issues. My experience is that it is quite hard to do effective programming in 5 minute snippets, but that after a few hours constant work productivity decreases. A pernicious fact is that you may not notice this decrease at the time, in that the main way in which a programmer can become unproductive is by putting more bugs into a program. It is possible to keep churning out lines of code all through the night, but there is a real chance that the time you will spend afterwards trying to mend the last few of them will mean that the long session did not really justify itself. In contrast to programming where long sessions can do real damage (because of the bugs that can be added by a tired programmer) I have sometimes found that long sessions have been the only way I can isolate bugs. Provided I can discipline myself not to try to correct anything but the very simplest bug while I am tired a long stretch can let me chase bugs in a painstakingly logical way, and this is sometimes necessary when intuitive bug-spotting fails. Thus my general advice about the concrete programming task would be to schedule your time so you can work in bursts of around an hour per session, and that you should plan your work so that as much as possible of everything you do can be tested fairly enthusiastically while it is fresh in your mind. A natural corollary of this advice is that projects should always be started in plenty of time, and pushed forward consistently so that no last-minute panic can arise and force sub-optimal work habits. 5.9. DOCUMENTING A PROGRAM OR PROJECT 195 5.9 Documenting a program or project Student assessed exercises are expected to be handed in complete with a brief report describing what has been done. Larger undergraduate projects culminate in the submission of a dissertation, as do PhD studies. All commercial programming activities are liable to need two distinct layers of documentation: one for the user and one for the people who will support and modify the product in the future. All these facts serve to remind us that documentation is an intrinsic part of any program. Two overall rules can guide the writing of good documentation. The first is to consider the intended audience, and think about what they need to know and how your document can be structured to help them find it. The second is to keep a degree of consistency and order to everything: documents with a coherent overall structure are both easier to update and to browse than sets of idiosyncratic jottings. To help with the first of these, here are some potential styles of write-up that might be needed: 1. Comments within the code to remind yourself or somebody who is already familiar with the program exactly what is going on at each point in it; 2. An overview of the internal structure and organisation of the whole program so that somebody who does not already know it can start to find their way around; 3. Documentation intended to show how reliable a program is, concentrating on discussions of ways in which the code has been built to be resilient in the face of unusual combinations of circumstance; 4. A technical presentation of a program in a form suitable for publication in a journal or at a conference, where the audience will consist of people expert in the general field but not aware of exactly what your contribution is; 5. An introductory user manual, intended to make the program usable even by the very very nervous; 6. A user reference manual, documenting clearly and precisely all of the options and facilities that are available; 7. On-line help for browsing by the user while they are trying to use the program; 8. A description of the program suitable for presentation to the venture capitalists who are considering investing in the next stage of its development. 196 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA It seems inevitable that the above list is not exhaustive, but my guess is that most programs could be presented in any one of the given ways, and the resulting document would be quite different in each case. It is not that one or the other of these styles is inherently better or more important than another, more that if you write the wrong version you will either not serve your reader well or you will find that you have had to put much more effort into the documentation than was really justified. A special problem about documentation is that of when it should be written. For small projects at least it will almost always be produced only after the program has been (at least nearly) finished. This can be rationalised by claiming “how can I possibly document it before it exists?” I will argue here for two ideals. The first is that documentation ought to follow on from design and specification work, but precede detailed programming. The second is that the text of the documentation should live closely linked to the developing source code. The reasoning behind the first of these is that writing the text can really help to ensure that the specification of the code has been fully thought through, and once it is done it provides an invaluable stable reference to keep the detailed programming on track. The second point recognises some sort of realism, and that all sorts of details of just what a program does will not be resolved until quite late in the implementation process. For instance the exact wording of messages that are printed will often not be decided until then, and it will certainly be hard to prepare sample transcripts from the use of the program ahead of its completion6. Thus when the documentation has been written early it will need completing when some of these final details get settled and correcting when the code is corrected or extended. The most plausible way of making it feasible to keep code and description in step is to keep them together. The concept of Literate Programming[17] pursues this goal. A program is represented as a composite file that can be processed in (at least) two different ways. One way “compiles” it to create typeset-quality human readable documentation, while the other leaves just statements in some quite ordinary programming language ready to be fed into a compiler. This goes beyond just having copious comments in the code in two ways. Firstly it expects that the generated documentation should be able to exploit the full range of modern typography and that it can include pictures or diagrams where relevant. It is supposed to end up as cleanly presented and readable as any fully free-standing document could ever be. Secondly Literate Programming recognises that the ordering and layout of the program that has to be compiled may not be the same as that in the ideal manual, and so the disentangling tool needs to be able to rearrange bits of text in a fairly flexible way so that description can simultaneously be thought of as close to the code it relates to and 6 Even though these samples can be planned and sketched early. 5.10. HOW DO WE KNOW IT DOES WORK? 197 to the section in the document where it belongs. This idea was initially developed as part of the project to implement the TEX typesetting program that is being used to prepare these lecture notes. 5.10 How do we know it does work? Figure 5.5: Many people think that their work is over well before it actually is. A conceptual difficulty that many people suffer from is a confusion between whether a program should work and whether it does. A program should work if it has been designed so that there are clear and easily explained reasons why it can achieve what it should. Sometimes the term “easily explained” may conceal the mathematical proof of the correctness of an algorithm, but at least in theory it 198 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA would be possible to talk anybody through the justification. As to programs that actually do work, well the reality seems to be that the only ones of these that you will ever see will be no more than around 100 lines long: empirically any program much longer than that will remain flawed even after extensive checking. Proper Oriental rugs will always have been woven with a deliberate mistake in them, in recognition of the fact that only Allah is perfect. Experience has shown very clearly indeed that in the case of writing programs we all have enough failings that there is no great need to insert extra errors — there will be plenty inserted however hard we try to avoid them. Thus (at least at the present state of the art) there is no such thing as a (non-trivial) program that works. If, however, a program should work (in the above sense) then the residual errors in it will be ones that can be corrected without disturbing the concepts behind it or its overall structure. I would like to think of such problems as “little bugs”. The fact that they are little does not mean that they might not be important, in that missing commas or references to the wrong variable can cause aeroplanes to crash just as convincingly as can errors at a more conceptual level. But the big effort must have been to get to a first testable version of your code with only little bugs left in it. What is then needed is a testing strategy to help locate as many of these as possible. Note of course that testing can only ever generate evidence for the presence of a bug: in general it can not prove absence. But careful and systematic testing is something we still need whenever there has been human involvement in the program construction process7 . The following thoughts may help in planning a test regime: 1. Even obvious errors in output can be hard to notice. Perhaps human society has been built up around a culture of interpreting slightly ambiguous input in the “sensible” way, and certainly we are all very used to seeing what we expect to see even when presented with something rather different. By the time you see this document I will have put some effort into checking its spelling, punctuation, grammar and general coherence, and I hope that you will not notice or be upset by the residual mistakes. But anybody who has tried serious proof-reading will be aware that blatant mistakes can emerge even when a document has been checked carefully several times; 2. If you are checking your own code and especially if you know you can stop work once it is finished then you have a clear incentive not to notice mistakes. Even if a mistake you find is not going to cause you to have to spend time fixing it it does represent you having found yet another instance of your own lack of attention, and so it may not be good for your ego; 7 Some see this observation as a foundation for hope for the future 5.10. HOW DO WE KNOW IT DOES WORK? 199 3. It is very desirable to make a clear distinction between the job of testing a program to identify the presence of bugs and the separate activity of correcting things. It can be useful to take the time to try to spot as many mistakes as you can before changing anything at all; 4. A program can contain many more bugs and oddities than your worst nightmares would lead you to believe! 5. Testing strategies worked out as part of the initial design of a program are liable to be better than ones invented only once code has been completed; 6. It can be useful to organise explicit test cases for extreme conditions that your program may face (eg sorting data where all the numbers to be sorted have the same value), and to collect test cases that cause each path through your code to be exercised. It is easy to have quite a large barrage of test cases but still have some major body of code unvisited. 7. Regressions tests are a good thing. These are test cases that grow up during project development, and at each stage after any change is made all of them are re-run, and the output the produce is checked. When any error is detected a new item in the regression suite is prepared so that there can remain a definite verification that the error does not re-appear at some future stage. Automating the application of regression tests is a very good thing, since otherwise laziness can too easily cause one to skip running them; 8. When you find one bug you may find that its nature gives you ideas for other funny cases to check. You should try to record your thoughts so that you do not forget this insight; 9. Writing extra programs to help you test your main body of code is often a good investment in time. On especially interesting scheme is to generate pseudo-random test cases. I have done that while testing a polynomial factorising program and suffered randomly-generated tests of a C compiler I was involved with, and in each case the relentless random coverage of cases turned out to represent quite severe stress; 10. You do not know how many bugs your code has in it, so do not know when to stop looking. One theoretical way to attack this worry would be to get some fresh known bugs injected into your code before testing, and then see what proportion of the bugs found were the seeded-in ones and which had been original. That may allow you to predict the total bug level remaining. Having detected some bugs there are several possible things to do. One is to sit tight and hope that nobody else notices! Another is to document the deficiencies 200 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA at the end of your manual. The last is to try to correct some of them. The first two of these routes are more reasonable than might at first seem proper given that correcting bugs so very often introduces new ones. In extreme cases it may be that the level of correctness that can be achieved by bug-hunting will be inadequate. Sometimes it may then be possible to attempt a formal proof of the correctness of your code. In all realistic circumstances this will involve using a large and complicated proof assistant program to help with all the very laborious details involved. Current belief is that it will be very unusual for bugs in the implementation of this tool to allow you to end up with a program that purports to be proved but which in fact still contains mistakes! 5.11 Is it efficient? I have made this a separate section from the one on detecting the presence of errors because performance effects are only rarely the result of simple oversights. Let me start by stressing the distinction between a program that is expensive to run (eg the one that computes π to 20,000,000,000 decimal places) and ones that are inefficient (eg one that takes over half a second to compute π correct to four places). The point being made is that unless you have a realistic idea of how long a task ought to take it is hard to know if your program is taking a reasonable amount of time. And similarly for memory requirements, disc I/O or any other important resource. Thus as always we are thrown back to design and specification time predictions as our only guideline, and sometimes even these will be based on little more than crude intuition. If a program runs fast enough for reasonable purposes then there may be no benefit in making it more efficient however much scope for improvement there is. In such cases avoid temptation. It is also almost always by far best to concentrate on getting code correct first and only worry about performance afterwards, taking the view that a wrong result computed faster is still wrong, and correct results may be worth waiting for. When collecting test cases for performance measurements it may be useful to think about whether speed is needed in every single case or just in most cases when the program is run. It can also be helpful to look at how costs are expected to (and do) grow as larger and larger test cases are attempted. For most programming tasks it will be possible to make a trade between the amount of time a program takes to run and the amount of memory it uses. Frequently this shows up in a decision as to whether some value should be stored away in case it is needed later or whether any later user should re-calculate it. Recognising this potential trade-off is part of performance engineering. For probably the majority of expensive tasks there will be one single part of the 5.12. IDENTIFYING ERRORS 201 entire program that is responsible for by far the largest amount of time spent. One would have expected that it would always be easy to predict ahead of time where that would be, but it is not! For instance when an early TITAN Fortran compiler was measured in an attempt to discover how it could be speeded up it was found that over half of its entire time was spent in a very short loop of instructions that were to do with discarding trailing blanks from the end of input lines. Once the programmers knew that it was easy to do something about it, but one suspects they were expecting to find a hot-spot in some more arcane part of the code. It is thus useful to see if the languages and system you use provide instrumentation that makes it easy to collect information to reveal which parts of your code are most critical. If there are no system tools to help you you may be able to add in time-recording statements to your code so it can collect its own break-down to show what is going on. Cunning optimisation of bits of code that hardly ever get used is probably a waste of effort. Usually the best ways to gain speed involve re-thinking data structures to provide cheap and direct support for the most common operations. This can sometimes mean replacing a very simple structure by one that has huge amounts of algorithmic complexity (there are examples of such cases in the Part IB Complexity course and the Part II one on Advanced Algorithms). In almost all circumstances a structural improvement that gives a better big-O growth rate for some critical cost is what you should seek. In a few cases the remaining constant factor improvement in speed may still be vital. In such cases it may be necessary to re-write fragments of your code in less portable ways (including the possibility of use of machine code) or do other things that tend to risk the reliability of your package. The total effort needed to complete a program can increase dramatically as the last few percent in absolute performance gets squeezed out. 5.12 Identifying errors Section 5.7 was concerned with spotting the presence of errors. Here I want to talk about working out which part of your code was responsible for them. The sections are kept separate to help you to recognise this, and hence to allow you to separate noticing incorrect behaviour from spotting mistakes in your code. Of course if, while browsing code, you find a mistake you can work on from it to see if it can ever cause the program to yield wrong results, and this study of code is one valid error-hunting activity. But even in quite proper programs it is possible to have errors that never cause the program to misbehave in any way that can be noticed. For instance the mistake might just have a small effect on the performance of some not too important subroutine, or it may be an illogicality that could only 202 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA be triggered into causing real trouble by cases that some earlier line of code had filtered out. You should also recognise that some visible bugs are not so much due to any single clear-cut error in a program but to an interaction between several parts of your code each of which is individually reasonable but which in combination fail. Most truly serious disasters caused by software failure arise because of complicated interactions between multiple “improbable” circumstances. The first thing to try to locate the cause of an error is to start from the original test case that revealed it and to try to refine that down to give a minimal clear-cut demonstration of the bad behaviour. If this ends up small enough it may then be easy to trace through and work out what happened. Pure thought and contemplation of your source code is then needed. Decide what Sherlock Holmes would have made of it! Run your compilers in whatever mode causes them to give as many warning messages as they are capable of, and see if any of those give valuable clues. Check out the assert facility and place copious assertions in your program that verify that all the high level expectations you have are satisfied. If this fails the next thought is to arrange to get a view on the execution of your code as it makes its mistake. Even when clever language-specific debuggers are available it will often be either necessary or easiest to do this by extra print statements into your code so it can display a trace of its actions. There is a great delicacy here. The trace needs to be detailed enough to allow you to spot the first line in it where trouble has arisen, but concise enough to be manageable. My belief is that one should try to judge things so that the trace output from a failing test run is about two pages long. There are those who believe that programs will end up with the best reliability if they start off written in as fragile way as possible. Code should always make as precise a test as possible, and should frequently include extra cross checks which, if failed, cause it to give up. The argument is that this way a larger number of latent faults will emerge in early testing, and the embedded assertions can point the programmer directly to the place where an expectation failed to be satisfied, which is at least a place to start working backwards from in a hunt for the actual bug. 5.12. IDENTIFYING ERRORS Figure 5.6: Effective debugging calls for great skill. 203 204 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA With many sorts of bugs it can be possible to home in on the difficulty by some sort of bisection search. Each test run should be designed to halve the range of code within which the error has been isolated. Some horrible problems seem to vanish as soon as you enable any debugging features in your code or as soon as you insert extra print statements into it. These can be amazingly frustrating! They may represent your use of an unsafe language and code that writes beyond the limit of an array, or they could involve reliance on the unpredictable value of an un-initialised variable. Sometimes such problems turn out to be bugs in the compiler you are using, not in your own code. I believe that I have encountered trouble of some sort (often fairly minor, but trouble nevertheless) with every C compiler I have ever used, and I have absolute confidence that no other language has attained perfection in this regard. So sometimes trying your code on a different computer or with a different compiler will either give you a new diagnostic that provides the vital clue, or will behave differently thereby giving scope for debugging-by-comparison. Getting into a panic and trying random changes to your code has no proper part to play either in locating or identifying bugs. 5.13 Corrections and other changes With a number of bugs spotted and isolated the time comes to extirpate them. The ideal should be that when a bug is removed it should be removed totally and it should never ever be able to come back. Furthermore its friends and offspring should be given the same treatment at the same time, and of course no new mistakes should be allowed to creep in while the changes are being made. This last is often taken for granted, but when concentrating on one particular bug it is all too easy to lose sight of the overall pattern of code and even introduce more new bugs than were being fixed in the first case. Regression testing is at least one line of defence that one should have against this, but just taking the correction slowly and thinking through all its consequences what is mostly wanted. Small bugs (in the sense discussed earlier) that are purely local in scope give fewest problems. However sometimes testing reveals a chain of difficulties that must eventually be recognised as a sign that the initial broad design of the program had been incorrect, and that the proper correction strategy does not involve fixing the problems one at a time but calls for an almost fresh start on the whole project. I think that would be the proper policy for the program in section 5.18, and that is part of why the exercise there asks you to identify bugs but not to correct them. Upgrading a program to add new features is at least as dangerous as correcting bugs, but in general any program that lasts for more than a year or so will end up with a whole raft of alterations having been made to it. These can very easily 5.14. PORTABILITY OF SOFTWARE 205 damage its structure and overall integrity, and the effect can be thought of as a form of software rot that causes old code to decay. Of course software rot would not arise if a program never needed correcting and never needed upgrading, but in that case the program was almost certainly not being used and was fossilised rather than rotting. Note that for elderly programs the person who makes corrections is never the original program author (even if they have the same name and birthday, the passage of time has rendered them different). This greatly increases the prospect of a would-be correction causing damage. All but the most frivolous code should be kept under the control of some source management tool (perhaps rcs) that can provide an audit trail so that changes can be tracked. In some cases a discussion of a bug that has now been removed might properly remain as a comment in the main source code, but much more often a description of what was found to be wrong and what was changed to mend it belongs in a separate project log. After all if the bug really has been removed who has any interest in being reminded of the mistake that it represented? Whenever a change is made to a program, be it a bug-fix or an upgrade, there is a chance that some re-work will be needed in documentation, help files, sample logs and of course the comments. Once again the idea of literate programming comes to the fore in suggestion that all these can be kept together. 5.14 Portability of software Most high level languages make enthusiastic claims that programs written in them will be portable from one brand of computer to another, just as most make claims that their compilers are “highly optimising”. Java makes especially strong claims on this front, and its owners try rather hard to prevent anybody from diverging from a rigid standard. However even in this case there are differences between Java 1.0 and 1.1 (and no doubt 1.2) that may cause trouble to the unwary. In reality achieving portability for even medium sized programs is not as easy as all that. To give a gross example of a problem not addressed at all by programming language or standard library design, a Macintosh comes as standard with a mouse with a single button, while most Unix X-windows systems have threebutton mice. In one sense the difference is a frivolity, but at another it invites a quite substantial re-think of user interface design. At the user interface level a design that makes good use of a screen with 640 by 480 pixels and 16 or 256 colours (as may be the best available on many slightly elderly computers) may look silly on a system with very much higher resolution and more colours. For most programming languages you will find that implementations provided by different vendors do not quite match. Even with the most standardised languages hardly any compiler supplier will manage to hold back from providing 206 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA some private extra goodies that help distinguish them from their competitors. Such extras will often be things that it is very tempting to make use of. Around 1997-8 a good example of such a feature is “Active-X” which Microsoft is promoting. To use such a feature tends to lock you to one vendor or platform, while to ignore it means that you can not benefit from the advantages that it brings. By now you will know what my suggested response to conflicts like this will be. Yes, it is to make your decisions explicitly and consciously rather than by default, to make them in view of stated ideas about what the users of your code will need, and to include all the arguments you use to support your decision in your design portfolio. There are frequently clever but non-portable tricks that can lead to big performance gains in code but at cost in portability. Sometimes the proper response to these is to have two versions of the program, one slow but very portable and the other that takes full advantage of every trick available on some platform that is especially important to you. 5.15 Team-work Almost all of this course is about programming in the small, with a concentration on the challenges facing a lone programmer. It is still useful to think for a while how to handle the transition from this state into a large-team corporate mentality. One of the big emotional challenges in joining a team relates to the extent to which you end up “owning” the code you work on. It is very easy to get into a state where you believe (perhaps realistically) that you are the only person who can properly do anything to the code you write. It is also easy to become rather defensive about your own work. A useful bit of jargon that refers to breaking out of these thought patterns is ego-free programming. In this ideal you step back and consider the whole project as the thing you are contributing to, not just the part that you are visibly involved in implementing. It may also be useful to recognise that code will end up with higher quality if understanding of it is shared between several people, and that bugs can be viewed as things to be found and overcome and never as personal flaws in the individual who happened to write that fragment of code. When trying to design code or find a difficult bug it can be very valuable to explain your thoughts to somebody else. It may be that they need not say much more than er and um, and maybe they hardly need to listen (but you probably need to believe that they are). By agreeing that you will listen to their problems at a later stage this may be a habit you can start right now with one or a group of your contemporaries. Reading other people’s code (with their permission, of course) and letting them read yours can also help you settle on a style or idiom that works well for 5.16. LESSONS LEARNED 207 you. It can also help get across the merits of code that is well laid out and where the comments are actually helpful to the reader. If you get into a real group programming context, it may make sense to consider partitioning the work in terms of function, for instance system architect, programmer, test case collector, documentation expert,. . . rather than trying to distribute the management effort and split the programming into lots of little modules, but before you do anything too rash read some more books on software engineering so that once again you can make decisions in an informed and considered way. 5.16 Lessons learned One of the oft-repeated observations about the demons of large-scale software construction is that there is no silver bullet. In other words we can not expect to find a single simple method that, as if by magic, washes away all our difficulties. This situation also applies for tasks that are to be carried out by an individual programmer or a very small team. No single method gives a key that makes it possible to sit down and write perfect programs without effort. The closest I can come to an idea for something that is generally valuable is experience – experience on a wide range of programming projects in several different languages and with various different styles of project. This can allow you to spot features of a new task that have some commonalty with one seen before. This is, however, obviously no quick fix. The suggestions I have been putting forward here are to try to make your analysis of what you are trying to achieve as explicit in your mind as possible. The various sections in these notes provide headings that may help you organise your thoughts, and in general I have tried to cover topics in an order that might make sense in real applications. Of course all the details and conclusions will be specific to your problem, and nothing I can possibly say here can show you how to track down your own very particular bug or confusion! I have to fall back on generalities. Keep thinking rather than trying random changes to your code. Try to work one step at a time. Accept that errors are a part of the human condition, and however careful you are your code will end up with them. But always remember the two main slogans: Programming is easy and Programming is fun. 208 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA 5.17 Final Words Do I follow my own advice? Hmmm I might have known you would ask that! Well most of what I have written about here is what I try to do, but I am not especially formal about any of it. I only really go overboard about design and making documentation precede implementation when starting some code that I expect to give me special difficulty. I have never got into the swing of literate programming, and suspect that I like the idea more than the reality. And I sometimes spend many more hours on a stretch at a keyboard than I maybe ought to. If this course and these notes help you think about the process of programming and allow you to make more conscious decisions about the style you will adopt then I guess I should be content. And if there is one very short way I would like to encapsulate the entire course, it would be the recommendation that you make all the decisions and thoughts you have about programming as open and explicit as possible. Good luck! 5.18 Challenging exercises Some of you may already consider yourselves to be seasoned programmers able to cope with even quite large and complicated tasks. In which case I do not you to feel this course is irrelevant, and so I provide here at the end of the notes some programming problems which I believe are hard enough to represent real challenges, even though the code that eventually has to be written will not be especially long. There is absolutely no expectation that anybody will actually complete any of these tasks, or even find good starting points. However these examples may help give you concrete cases to try out the analysis and design ideas I have discussed: identifying the key difficulties and working out how to partition the problems into manageable chunks. In some cases the hardest part of a proper plan would be the design of a good enough testing strategy. The tasks described here are all both reasonably compact and fairly precisely specified. I have fought most of these myself and found that producing solutions that were neat and convincing as well as correct involved thought as well as more coding skill. There are no prizes and no ticks, marks or other bean-counter’s credit associated with attempting these tasks, but I would be jolly interested to see what any of you can come up with, provided it can be kept down to no more than around 4 sides of paper. 5.18. CHALLENGING EXERCISES 209 MULDIV The requirement here is to produce a piece of code that accepts four integers and computes (a ∗ b + c)/d and also the remainder from the division. It should be assumed that the computer on which this code is to be run has 32-bit integers, and that integer arithmetic including shift and bitwise mask operations are available, but the difficulty in this exercise arises because a ∗ b will be up to 64-bits long and so it can not be computed directly. “Solutions” that use (eg) the direct 64-bit integer capabilities of a DEC Alpha workstation are not of interest! It should be fairly simple to implement muldiv if efficiency where not an issue. To be specific this would amount to writing parts of a package that did double-length integer arithmetic. Here the additional expectation is that speed does matter, and so the best solution here will be one that makes the most effective possible use of the 32-bit arithmetic that is available. Note also that code of this sort can unpleasantly easily harbour bugs, for instance due to some integer overflow of an intermediate result, that only show up in very rare circumstances, and that the pressure to achieve the best possible performance pushes towards code that comes very close to the limits of the underlying 32-bit arithmetic. Thought will be needed when some or all of the input values are negative. The desired behaviour is one where the calculated quotient was rounded towards zero, whatever its sign. Overlapping Triangles A point in the X–Y plane can be specified by giving its co-ordinates (x, y). A triangle can then be defined by giving three points. Given two triangles a number of possibilities arise: they may not overlap at all or they may meet in a point or a line segment, or they may overlap so that the area where they overlap forms a triangle, a quadrilateral, a pentagon or a hexagon. Write code that discovers which of these cases arises, returning co-ordinates that describe the overlap (if any). A point to note here is that any naive attempt to calculate the point where two lines intersect can lead to attempts to divide by zero if the lines are parallel. Near-parallel lines can lead to division by very small numbers, possibly leading to subsequent numeric overflow. Such arithmetic oddities must not be allowed to arise in the calculations performed. Matrix transposition One way of representing an m by n matrix in a computer is to have a single vector of length mn and place the array element ai, j at offset mi + j in the vector. Another would be to store the same element at offset i + n j. One of these representation 210 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA means that items in the same row of the matrix live close together, the other that items in the same column are adjacent. In some calculations it can make a significant difference to speed which of these layouts is used. This is especially true for computers with virtual memory. Sometimes one part of a calculation would call for one layout, and a later part would prefer the other. The task here is therefore to take integers m and n and a vector of length mn, and rearrange the values stored in the vector so that if they start off in one of as one representation of a matrix they end up as the other. Because the matrix should be assumed to be quite large you are not allowed to use any significant amount of temporary workspace (you can not just allocate a fresh vector of length mn and copy the data into it in the new order — you may assume you may use extra space of around m + n if that helps, but not dramatically more than that). If the above explanation of the problem8 feels out of touch with today’s computer uses, note how the task relates to taking an m by n matrix representing a picture and shuffling the entries to get the effect of rotating the image by 90 degrees. Just that in the image processing case you may be working with data arranged in sub-word-sized bite-fields, say at 4 bits per pixel. Sprouts The following is a description of a game9 to be played by two players using a piece of paper. The job of the project here is to read in a description of a position in the game and make a list of all the moves available to the next player. This would clearly be needed as part of any program that played the game against human opposition, but the work needed here does not have to consider any issues concerning the evaluation of positions or the identification of good moves. The game starts with some number of marks made on a piece of paper, each mark in the form of a capital ‘Y’. Observe that each junction has exactly three little edges jutting from it. A move is made by a player identifying two free edges and drawing a line between them. The line can snake around things that have been drawn before as much as the player making the move likes, but it must not cross any line that was drawn earlier. The player finishes the move by drawing a dot somewhere along the new line and putting the stub of a new edge jutting out from it in one of the two possible directions. Or put a different but equivalent way, the player draws a new ‘Y’ and joins two of its legs up to existing stubs with lines that do not cross any existing lines. The players make moves alternately and the first player unable to make a further legal move will be the loser. 8 This is an almost standard classical problem and if you dig far enough back in the literature you will find explanations of a solution. If you thought to do that for yourself, well done! 9 Due to John Conway 5.18. CHALLENGING EXERCISES 211 A variation on the game has the initial state of the game just dots (not ‘Y’ shapes) and has each player draw a new dot on each edge they create, but still demands that no more that three edges radiate from each dot. The difference is that in one case a player can decide which side of a new line any future line must emerge from. I would be equally happy whichever version of the game was addressed by a program, provided the accompanying documentation makes it clear which has been implemented! The challenge here clearly largely revolves around finding a way to describe the figures that get drawn. If you want to try sprouts out as a game between people before automating it, I suggest you start with five or six starting points. ML development environment The task here is not to write a program, but just to sketch out the specification of one. Note clearly that an implementation of the task asked about here would be quite a lot of work and I do not want to provide any encouragement to you to attempt all that! In the Michaelmas Term you were introduced to the language ML, and invited to prepare and test various pieces of test code using a version running under Microsoft Windows. You probably used the regular Windows “notepad” as a little editor so you could change your code and then paste back corrected versions of it into the ML window. Recall that once you have defined a function or value in ML that definition remains fixed for ever, and so if it is incorrect you probably need to re-type not only it but everything you entered after it. All in all the ML environment you used was pretty crude (although I am proud of the greek letters in the output it generates), and it would become intolerable for use in medium or large-scale projects. Design a better environment, and append to your description of it a commentary about which aspects of it represent just a generically nice programmer’s work-bench and which are motivated by the special properties of ML. An example from the literature The following specification is given as a paragraph of reasonably readable English text, and there is then an associated program written in Java. This quite small chunk of code can give you experience of bug-hunting: please do not look up the original article in CACM10 until you have spent some while working through the code checking how it works and finding some of the mistakes. In previous years when I have presented this material to our students they did almost as well as the professional programmers used in the original IBM study, but they still found only 10 Communications of the ACM, vol 21, no 9, 1978. 212 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA a pathetically small proportion of the total number of known bugs! I am aware that the Java transcription of this program has changed its behaviour from the original PL/I version and the intermediate C one. I do not believe that the changes are a case of bugs evaporating in the face of Java, but some may be transcription errors I have made. But since the object of this exercise is that you locate bugs, whatever their source, this does not worry me much, and I present the example as it now stands, oddities and all. Figure 5.7: Picture courtesy Shamila Corless. 5.18. CHALLENGING EXERCISES Formatting program for text input. Converted from the original PL/I version which is in a paper by Glen Myers, CACM vol 21 no 9, 1978 (a) This program compiles correctly: it is believed not to contain either syntax errors or abuses of the Java library. (b) A specification is given below. You are to imagine that the code appended was produced by somebody who had been provided with the specification and asked to produce an implementation of the utility as described. But they are not a very good programmer! (c) Your task is one of quality control - it is to check that the code as given is in agreement with the specification. If any bugs or mis-features are discovered they should be documented but it will be up to the original programmer to correct them. If there are bugs it is desirable that they all be found. (d) For the purposes of this study, a bug or a mis-feature is some bad aspect of the code that could be visible to users of the binary version of the code. Ugly or inefficient code is deemed not to matter, but even small deviations from the letter of the specification and the things sensibly implicit in it do need detecting. (e) Let me repeat point (a) again just to stress it the code here has had its syntax carefully checked and uses the Java language and library in a legal straightforward way, so searching for bugs by checking fine details of the Java language specification is not expected to be productive. I have put in comments to gloss use of library functions to help those who do not have them all at their finger-tips. The code may be clumsy in places but I do not mind that! I have tried to keep layout of the code neat and consistent. There are few comments "because the original programmer who wrote the code delivered it in that state". 213 214 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA /********************************************************* * Specification * * ============= * * * Given an input text consisting of words separated by * * * blanks or new-line characters, the program formats it * * into a line-by-line form such that (1) each output * line has a maximum of 30 characters, (2) a word in * * * the input text is placed on a single output line, and * * (3) each output line is filled with as many words as * * possible. * * * The input text is a stream of characters, where the * * * characters are categorised as break or nonbreak * characters. A break character is a blank, a new-line * * * character (&), or an end of text character (/). * * New-line characters have no special significance; * they are treated as blanks by the program. & and / * * * should not appear in the output. * * * A word is defined as a nonempty sequence of non-break * * * characters. A break is a sequence of one or more * break characters. A break in the input is reduced to * * * a single blank or start of new line in the output. * * * The input text is a single line entered from a * * * simple terminal with an fixed 80 character screen * width. When the program is invoked it waits for the * * * user to provide input. The user types the input line, * * followed by a / (end of text) and a carriage return. * * The program then formats the text and displays it on * * the terminal. * * * * If the input text contains a word that is too long to * * fit on a single output line, an error message is * typed and the program terminates. If the end-of-text * * * character is missing, an error message is issued and * * the user is given a chance to type in a corrected * version of the input line. * * * * (end of specification) * * ********************************************************/ 5.18. CHALLENGING EXERCISES 215 import java.io.*; public class Buggy { final static int LINESIZE = 31; public static void main(String [] args) { int k, bufpos, fill, maxpos = LINESIZE; char cw, blank = ’ ’, linefeed = ’$’, eotext = ’/’; boolean moreinput = true; char [] buffer = new char [LINESIZE]; bufpos = 0; fill = 0; while (moreinput) { cw = gchar(); if (cw == blank || cw == linefeed || cw == eotext) { if (cw == eotext) moreinput = false; if ((fill + 1 + bufpos) <= maxpos) { pchar(blank); fill = fill + 1; } else { pchar(linefeed); fill = 0; } for (k = 0; k < bufpos; k++) pchar(buffer[k]); fill = fill + bufpos; bufpos = 0; } else if (bufpos == maxpos) { moreinput = false; System.out.println("Word to long"); } 216 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA else { bufpos = bufpos + 1; buffer[bufpos-1] = cw; } } pchar(linefeed); return; } // I use B as a shorthand for the character ’ ’. final static char B = ’ ’; final static int ILENGTH = 80; // Make suitable array with initial contents Z // then a load of blanks. static char [] buffer = { ’Z’,B,B,B,B,B,B,B,B,B, B,B,B,B,B,B,B,B,B,B, B,B,B,B,B,B,B,B,B,B, B,B,B,B,B,B,B,B,B,B, B,B,B,B,B,B,B,B,B,B, B,B,B,B,B,B,B,B,B,B, B,B,B,B,B,B,B,B,B,B, B,B,B,B,B,B,B,B,B,B}; // bcount is defined here so that it keeps its values // across several calls to gchar(). static int bcount = 1; static char gchar() { char [] inbuf = new char [ILENGTH]; char eotext = ’/’; char c; if (buffer[0] == ’Z’) { getrecord(inbuf); 5.18. CHALLENGING EXERCISES 217 // indexOf returns the index of a position where the given // character is present in a string, or -1 if it is not // found. if (new String(inbuf).indexOf((int) eotext) == -1) { System.out.println("No end of text mark"); buffer[1] = eotext; } else for (int j=0; j<ILENGTH; j++) buffer[j] = inbuf[j]; } c = buffer[bcount-1]; bcount = bcount + 1; return c; } // a static ouput buffer, again blank-filled. static char [] outline = { B,B,B,B,B,B,B,B,B,B,B,B,B,B,B,B,B,B,B,B, B,B,B,B,B,B,B,B,B,B,B }; // i indicates which place in outline pchar should // put the next character at. static int i = 1; static void pchar(char c) { int linefeed = ’$’; if (c == linefeed) { System.out.println(outline); for (int j=0; j<LINESIZE; j++) outline[j] = B; i = 1; } else { outline[i-1] = c; i = i + 1; } } 218 CHAPTER 5. DESIGNING AND TESTING PROGRAMS IN JAVA // Get access to keyboard input. No tricks here! static BufferedReader in = new BufferedReader( new InputStreamReader(System.in), 1); static void getrecord(char [] b) { String s; try { s = in.readLine(); } catch (IOException e) { s = " "; } for (int i = 0; i < ILENGTH; i++) { if (i < s.length()) b[i] = s.charAt(i); else b[i] = ’ ’; } } } /* End of file */ Chapter 6 A representative application The purpose of this chapter is not to form part of the official examinable course, but to provide you with some extended samples of Java so that you can see the various facilities working together and so you can consider how the code ought to have been written to make it as clear and robust as possible. Note that I do not guarantee that my code is a paragon of clarity, and although the example here is much larger than the ones that have gone before it has still been trimmed fairly close to the bone to make it as small and perhaps comprehensible as I could manage. 6.1 A Lisp interpreter This final example for these notes is as large and complicated as any of the others here — but most of the Java features it uses are ones that have been seen before. It is an implementation of a very much cut-down version of the programming language Lisp. In this language, which is one of the oldest programming languages that is still in use today, and a version of which is used to customise the emacs editor, all syntax is indicated with explicit parentheses. The programs that one writes are very much like ML ones, except that Lisp does not have either the syntax and operators of ML nor the type-checking. While I do not want to divert this Java course into one on Lisp I will give a brief example of the sort of program that can be used to test my Minilisp. It defines a function to reverse lists and demonstrates its use. (defun reverse (x) (rev1 x nil)) (defun rev1 (a b) (cond 219 CHAPTER 6. A REPRESENTATIVE APPLICATION 220 ((eq a nil) b) (t (rev1 (cdr a) (cons (car a) b))))) (reverse ’(a b c d)) To find out about Lisp in its modern and very large form you could check out Common Lisp — the Language[21] however the Minilisp here perhaps makes better sense with reference to the Lisp 1.5 manual[13], which is amazingly ancient now but which has the huge advantage of brevity. Of course the Common Lisp manual was written by Guy Steele who participated in writing the Java language reference — so as well as Lisp being a very direct ancestor of ML it can also be seen as having had noticable input into Java1 . Another reason for including an implementation of it here. I have in fact extended the tiny Lisp shown here into a full-scale one that is capable of running programs that are many tens of thousands of lines long. The write-up of that work is in “Further evaluation of Java for Symbolic Calculation”, Proc. ISSAC 00, St Andrews, Scotland, August 2000. 1 The “Flavours” package developed for Lisp at MIT was one of the earlier programming systems that encouraged object oriented design, supported inheritance and worked to make large-scale programming practical. CLOS (Common Lisp Object System) is the major internationally standardised model for dynamic object-oriented programming. 6.1. A LISP INTERPRETER 221 Now for the implementation. Making sense of it is liable to be a substantial struggle for most of you, but I hope that the fact that I can fit an implementation of a programming language into these notes is at least interesting! // // // // // // // // // // // // // Minilisp Basic and utterly tiny Lisp system coded in Java by Arthur Norman, 1998. In spirit much like an older BCPL then C version of the same thing! Supports quote, cond, defun atom, eq, car, cdr, cons has fragments of code waiting to be extended to do rather more. import java.io.*; // // // // Lisp has a single inclusive data-type, which I call LispObject here. It has sub-types that are symbols, numbers, strings and lists. Here I give just two methods (print and eval) that may be used on anything. abstract class LispObject { public abstract void print(); public abstract LispObject eval(Environment env); } // A "cons" is an ordered pair. In ML terms it would be // a bit like (’a * ’b) class Cons extends LispObject { // The left and right parts of a pair are called // CAR and CDR public LispObject car, cdr; Cons(LispObject car, LispObject cdr) { this.car = car; this.cdr = cdr; } 222 CHAPTER 6. A REPRESENTATIVE APPLICATION // Function calls are written as lists (fn a1 a2 ...) public LispObject eval(Environment env) { int n = 0; for (LispObject a=cdr; a instanceof Cons; a = ((Cons)a).cdr) n++; LispObject [] args = new LispObject [n]; n = 0; for (LispObject a=cdr; a instanceof Cons; a = ((Cons)a).cdr) args[n++] = ((Cons)a).car; // Now I have unpicked the actual arguments into a vector if (car instanceof Symbol) { Symbol f = (Symbol)car; // "special" functions are for QUOTE, CONS and DEFUN. They // do not evaluate their arguments if (f.special != null) return f.special.op(args, env); // All other functions have their arguments evaluated. for (int i=0; i<n; i++) args[i] = args[i].eval(env); // Call the function! return f.fn.op(args); } // return NIL if I do not otherwise know what to do else return Minilisp.nil; } // Lists print as (a b c ... ) // and if a list ends in NIL then it is displayed with // just a ")" at the end, otherwise the final atom is // shown after a "." public void print() { LispObject x = this; String delim = "("; while (x instanceof Cons) { System.out.print(delim); delim = " "; ((Cons)x).car.print(); x = ((Cons)x).cdr; } 6.1. A LISP INTERPRETER 223 if (x != Minilisp.nil) { System.out.print(" . "); x.print(); } System.out.print(")"); } } // I do not do a lot with strings here. class LispString extends LispObject { public String string; LispString(String s) { this.string = s; } public LispObject eval(Environment env) { return this; } public void print() { System.out.print("\"" + string + "\""); } } class Symbol extends LispObject { public String pname; // print name public LispObject plist; // property list (unused) Symbol obListNext; // chaining of symbols public LispFunction fn; // function (if any) public SpecialFunction special; // special fn (if any) // intern() looks up a Java String and find the Lisp // symbol with that name. It creates it if needbe. public static Symbol intern(String name, LispFunction fn, SpecialFunction special) { Symbol p; for (p=Minilisp.obList; p!=null; p=p.obListNext) { if (p.pname.equals(name)) return p; } 224 CHAPTER 6. A REPRESENTATIVE APPLICATION // not found on "object-list" (oblist), so create it p = new Symbol(); p.pname = name; p.plist = Minilisp.nil; p.obListNext = Minilisp.obList; Minilisp.obList = p; p.fn = fn != null ? fn : new Undefined(name); p.special = special; return p; } // The symbols NIL and T are special - they evaluate // to themselves. All others get looked up in an // environment that stores current values of local vars. public LispObject eval(Environment env) { if (this == Minilisp.nil || this == Minilisp.lispTrue) return this; return env.eval(this); } public void print() { System.out.print(pname); } } // An environment is a chain of Bindings terminated with // a NullEnvironment. Each binding holds information of // the form // variable = value abstract class Environment { public abstract LispObject eval(Symbol name); } class NullEnvironment extends Environment { public LispObject eval(Symbol name) { System.out.println("Undefined variable: " + name.pname); System.exit(1); return null; } } 6.1. A LISP INTERPRETER 225 class Binding extends Environment { public Symbol name; public LispObject value; public Environment next; Binding(Symbol name, LispObject val, Environment next) { this.name = name; this.value = val; this.next = next; } public LispObject eval(Symbol x) { if (x == name) return value; else return next.eval(x); } } // I do not do a lot with numbers here! class LispNumber extends LispObject { public int value; LispNumber(int value) { this.value = value; } public LispObject eval(Environment env) { return this; } public void print() { System.out.print(value); } } // Each built-in function is created wrapped in a class // that is derived from LispFunction. abstract class LispFunction { public abstract LispObject op(LispObject [] args); } 226 CHAPTER 6. A REPRESENTATIVE APPLICATION class Undefined extends LispFunction { String name; Undefined(String name) { this.name = name; } public LispObject op(LispObject [] args) { System.out.println("Undefined function " + name); System.exit(1); // throw? return null; } } // If a symbol has an interpreted definition its // associated function is this job, which knows how to // extract the saved definition and activate it. class Interpreted extends LispFunction { LispObject a, b; Environment env; Interpreted(LispObject a, // formal args LispObject b, // body Environment env) // environment { this.a = a; this.b = b; this.env = env; } public LispObject op(LispObject [] args) { LispObject a1 = a; int i = 0; Environment e = env; while (a1 instanceof Cons) { e = new Binding( (Symbol)((Cons)a1).car, args[i++], e); a1 = ((Cons)a1).cdr; } return b.eval(e); } } 6.1. A LISP INTERPRETER 227 // Similar stuff, but for "special functions" abstract class SpecialFunction { public abstract LispObject op(LispObject [] args, Environment env); } // (quote xx) evaluates to just xx class QuoteSpecial extends SpecialFunction { public LispObject op(LispObject [] args, Environment env) { return args[0]; } } // (cond (p1 e1) // (p2 e2) // (p3 e3) ) // if p1 then e1 else if p2 then e2 else if p3 then e3 else nil class CondSpecial extends SpecialFunction { public LispObject op(LispObject [] args, Environment env) { for (int i=0; i<args.length; i++) { Cons x = (Cons)args[i]; LispObject predicate = x.car; LispObject consequent = ((Cons)x.cdr).car; if (predicate.eval(env) != Minilisp.nil) return consequent.eval(env); } return Minilisp.nil; } } 228 CHAPTER 6. A REPRESENTATIVE APPLICATION // (defun name (a1 a2 a3) body-of-function) class DefunSpecial extends SpecialFunction { public LispObject op(LispObject [] args, Environment env) { Symbol name = (Symbol)args[0]; LispObject vars = args[1]; LispObject body = args[2]; name.fn = new Interpreted(vars, body, env); return name; } } // like ML "fun car (a :: b) = a;" class CarFn extends LispFunction { public LispObject op(LispObject [] args) { return ((Cons)(args[0])).car; } } // like ML "fun cdr (a :: b) = b;" class CdrFn extends LispFunction { public LispObject op(LispObject [] args) { return ((Cons)(args[0])).cdr; } } // like ML "fun atom (a :: b) = false | atom x = true;" class AtomFn extends LispFunction { public LispObject op(LispObject [] args) { return args[0] instanceof Cons ? Minilisp.nil : Minilisp.lispTrue;; } } 6.1. A LISP INTERPRETER // (eq a b) is true if a and b are the same thing class EqFn extends LispFunction { public LispObject op(LispObject [] args) { return args[0]==args[1] ? Minilisp.lispTrue : Minilisp.nil; } } // like ML "fun cons a b = a :: b;" class ConsFn extends LispFunction { public LispObject op(LispObject [] args) { return new Cons(args[0], args[1]); } } // (stop) exist from this Lisp. class StopFn extends LispFunction { public LispObject op(LispObject [] args) { System.exit(0); return null; } } // The top-level class has a bunch of input // and management code. public class Minilisp { public static Symbol nil, lispTrue, obList, lambda, cond, quote, defun; static StreamTokenizer input; static int inputType; static boolean inputValid; 229 230 CHAPTER 6. A REPRESENTATIVE APPLICATION static void initInput() { input = // Get stream & establish syntax new StreamTokenizer( new BufferedReader( new InputStreamReader(System.in), 1)); input.eolIsSignificant(false); input.ordinaryChar(’/’); input.commentChar(’;’); input.ordinaryChar(’\’’); input.quoteChar(’\"’); input.ordinaryChar(’.’); // disable floating point input.lowerCaseMode(true); inputValid = false; } // read a single parenthesised expression. // Supports ’xx as a short-hand for (quote xx) // which is what most Lisps do. // Formal syntax: // read => SYMBOL | NUMBER | STRING // => ’ read // => ( tail // tail => ) // => . read ) // => read readtail static LispObject read() throws IOException { LispObject r; if (!inputValid) { inputType = input.nextToken(); inputValid = true; } switch (inputType) { case StreamTokenizer.TT_EOF: throw new IOException("End of file"); case StreamTokenizer.TT_WORD: r = Symbol.intern(input.sval, null, null); inputValid = false; return r; 6.1. A LISP INTERPRETER 231 case StreamTokenizer.TT_NUMBER: r = new LispNumber((int)input.nval); inputValid = false; return r; case ’\"’: // String r = new LispString(input.sval); inputValid = false; return r; case ’\’’: inputValid = false; r = read(); return new Cons(quote, new Cons(r, nil)); case ’(’: inputValid = false; return readTail(); case ’)’: case ’.’: inputValid = false; return nil; default: r = Symbol.intern( String.valueOf((char)inputType), null, null); inputValid = false; return r; } } static LispObject readTail() throws IOException { LispObject r; if (!inputValid) { inputType = input.nextToken(); inputValid = true; } switch (inputType) { case ’.’: inputValid = false; r = read(); if (!inputValid) { inputType = input.nextToken(); inputValid = true; } 232 CHAPTER 6. A REPRESENTATIVE APPLICATION if (inputType == ’)’) inputValid = false; return r; case StreamTokenizer.TT_EOF: throw new IOException("End of file"); case ’)’: inputValid = false; return nil; default:r = read(); return new Cons(r, readTail()); } } // set up fixed definitions static void initSymbols() { obList = null; nil = Symbol.intern("nil", null, null); nil.plist = nil; Symbol.intern("car", new CarFn(), null); Symbol.intern("cdr", new CdrFn(), null); Symbol.intern("cons", new ConsFn(), null); Symbol.intern("atom", new AtomFn(), null); Symbol.intern("eq", new EqFn(), null); Symbol.intern("stop", new StopFn(), null); lispTrue = Symbol.intern("t", null, null); // lambda is ready for extension of this code lambda = Symbol.intern("lambda", null, null); cond = Symbol.intern("cond", null, new CondSpecial()); quote = Symbol.intern("quote", null, new QuoteSpecial()); defun = Symbol.intern("defun", null, new DefunSpecial()); } public static void main(String [] args) { initInput(); initSymbols(); System.out.println("Arthur’s Minilisp..."); 6.1. A LISP INTERPRETER 233 try { // this is s READ-EVAL-PRINT loop for (int i=1;;i++) { System.out.print(i + ": "); // Ensure that the prompt gets displayed. System.out.flush(); LispObject r = read(); LispObject v = r.eval(new NullEnvironment()); System.out.print("Value: "); v.print(); System.out.println(""); } } catch (IOException e) { System.out.println("IO exception"); } System.out.println("End of Lisp run. Thank you"); } } // End of Minilisp.java 6.1.1 Exercises ML to Lisp Find a Lisp 1.5 manual and/or study the Minilisp code, and then see how many ML list-processing functions you can convert into Lisp and run on the Java implementation. You have already seen reverse, so the next thing to try is append. It would probably be possible to coax the ML exercise on transitive closures through Minilisp! Add a few more functions Show that you have understood what is going on in the Minilisp code by adding in support for arithmetic, specifically functions to add, subtract, multiply and divide numbers, and to compare them for inequality and “less-than”. 234 CHAPTER 6. A REPRESENTATIVE APPLICATION Emacs Lisp Now you have at least minimal exposure to Lisp, investigate the way it is used in the emacs editor to allow users to create new language-specific editing modes, indentation and colour conventions. WeirdX Visit the web-site http://www.jcraft.com/weirdx/ and fetch yourself a copy of the source of the WeirdX X-windows server. It is around 25000 lines of Java! Do not fetch or examine in any way the related but commercial product called WiredX. Inspect the associated GNU public license carefully: it gives you permission to work on the source but imposes an obligation to make the source version of any adjustments available to the world at no cost. If you are happy with the GNU rules2 investigate the behaviour of WeirdX and look for ways to 1. Identify and remove bugs; 2. Make it implement the latest X-windows specification more fully; 3. Enhance its performance; 4. Put a proper and copious number of comments into the code (!); 5. Ensure that it implements all possible facilities to reduce the security vulnerabilities that X-windows often opens up; 6. Make a careful comparison between the behaviour and capabilities of the improved WeirdX and other commercial and free X servers. Create a nicely structured wish-list for future enhancements. As you make and test changes arrange to make your improvements available to the the world: see http://sourceforge.net/projects/weirdx/. In case you had not spotted, this is not a small exercise for an individual to attack lightly: it is a substantial challenge that calls for a lot of study way beyond core Java and if you decide to try it I suggest you form a small group to work in. Please let me know of any progress. 2 For an extended discussion of the GNU public license and “free” software in general, see “The Cathedral and the Bazaar” by Eric Raymond[20]. Some of the explanation there makes very good sense, some seems to me to be silly! Chapter 7 What you do NOT know yet These days anybody arranging a lecture course is expected to think in terms of aims, objectives and learning outcomes. In particular it is deemed important to consider carefully what will be known by students who have taken the course. I want to take a contrary view: that what is most important to understand is what you will not know just because you have attended this course and done all the exercises. I view a recognition of ones limitations as vital both for any individual’s personal integrity and as something that is essential if they are to be productive in any work environment. So let me start with: After one course you are not a Java expert. . . To a large extent you only become an expert in any sort of programming after you have built up a substantial body of experience working on both individual and group projects. Most people can only gain the paranoia about bugs and documentation that is really needed via personal involvement in projects that fail horribly! Most people only gain a proper paranoid attitude to overall system design via personal involvement in projects that collapse through being inadequately specified, ill-planned or where project management is not strong enough. The Computer Science Tripos provides courses on Software Engineering that document examples of software disasters and explain the state of the art in avoiding trouble. Despite this most people view the horror stories as things that happen to “other people” until it is too late. Java is an object oriented language. Proper use of it involves obtaining full leverage from the package, class and inheritance features it provides. This course has taken the attitude that Object Oriented Design is something that can not be fully appreciated until you are able to write a range of small programs comfortably. So once you have mastered this material it will be proper to re-start Java from the beginning concentrating on starting the design by planning a class hierarchy. This has to include careful thought about private, protected etc class 235 236 CHAPTER 7. WHAT YOU DO NOT KNOW YET members, and it can involve formal schemes to document structures. I have explained what packages are, but not discussed the practicalities of using lots of different packages for your own code. For small programs this is OK, but larger scale work will put more pressure on this side of things. The Java libraries that support windows have been introduced in a very sketchy way here. That is because there is a huge amount to understand, and it would not even start to fit in the time available within the Part IA course. All the issues of getting pages laid out, controlling multiple windows, organising cut-and-paste operations and so on take a lot of learning. The most that can be hoped is that this course has given you a starting point to work from. When a Java applet is run using a web browser it is subject to a variety of security limitations. This is so that when some remote user loads a web page that runs your Java code you can not then steal or destroy information on their computer. There is an elaborate scheme involving signatures and permissions that makes it possible for applets to be granted additional privilege, without giving them total access. Such issues will be important for large-scale Java projects. Network access and concurrency are both areas where a programmer needs to absorb quite a lot of additional understanding before they can use Java (or any other language) in a fully satisfactory manner. It is easy to end up with systems that can either gum up in deadlock or have different threads create inconsistent results, and proper recovery after one thread fails or one network link times out is not at all easy to arrange. The operating systems threads later in the CST have much to say about the issues and pitfalls involved. The way in which Java can interface to databases may appear straightforward, but competent design of the database itself needs specialist understanding. While Java is a pretty respectable general purpose language there are plenty of areas where simple use of pure Java will prove inadequate. Sometimes however it will still make sense to implement either the bulk of a program or perhaps just the user-interface in Java, with some other components in another language. This raises issues of inter-language working, and serious design challenges in the area of the inter-language interface. During this course you have been encouraged to use javac as your Java compiler. For real projects it is almost certain that much more elaborate tools would be used. These would include ones to manage a chain of versions of code, tools to generate stylised code for some bits of a user interface, and a variety of debugging and performance analysis packages. Actually hardly any programmer, however experienced, will be a full expert on all of the above matters. . . . . . but at least you have taken the first step! Chapter 8 Model Examination Questions Some of these are ones I have invented, some have been submitted by members of the class, while yet more are adjustments of previous examination questions modified to fit into a Java context. They may not all be normalised for precision of wording or difficulty, but should give you something to try your skills on. I neither confirm nor deny the possibility that variants on some of these may appear in this or a future year’s real examination paper. . . Note also that for the Computer Science Part IA papers there can be full-sized (ie around 30 mins) questions, half size (around 15 mins) and tiny (about 1 minute) questions, and some of these fit or could readily be modified to fit several of these categories. 8.1 Java vs ML Compare and contrast Java with ML under the following headings 1. Primitive data types; 2. Support for arrays; 3. Support for lists and trees; 4. Functions applicable to several different data-types; 5. Complexity of syntax. [4 marks per section] 237 238 CHAPTER 8. MODEL EXAMINATION QUESTIONS 8.2 Matrix Class Design a Matrix class for doing operations on n × n matrices. The class should include functions for performing matrix multiplication, addition, and multiplication by scalars (and all matrices may be supposed to have elements that are type double). To what extent should other classes have rights to modify individual matrix elements? Justify your answer. 8.3 Hash Tables Java allows you to declare arrays with any sort of content — for instance int, double, String or Object, but the value used to index the array is restricted to being an int. In some applications programmers want structures that behave like arrays whose subscripts are of type String. One way of doing this is to use a structure known as a hash table: Given an index value of type String an integer is computed using a hash function. The Java method hashCode in class String computes a suitable value. The int value computed depends only on the contents of the String, but different strings can be expected to lead to integers that are uniformly distributed across the whole range that integers cover. This hash value is reduced modulo the size of a fixed ordinary array, this (almost) lets the original String act as an index. The problem that arises is that it could be that two different String values hash onto the same location in the array. This concern can be overcome by making the entries in the array linked lists of (key, value) pairs, so that the original index String will match one of the key strings and then the associated value is what is stored with it. The method equals(String s) in class String returns a boolean telling if one string is equal to another. Storing into a hash table will involve adding a new (key, value) pair to one of the lists. Design appropriate data structures and classes for such a table in the case where the values to be stored are themselves of type String. Use the following method signatures in a class called HashTable: void put(String key, String value) throws Duplicate; String get(String key) throws Missing; where you may assume that the exceptions have already been defined elsewhere. Note: The Java class-libraries provide a class called HashMap that does all this for you! The exercise here in coding it for yourself not something the ordinary Java programmer ever needs to do! 8.4. COMPASS ROSE 239 8.4 Compass Rose A Java program to draw compass roses is needed. In the example shown the line pointing North is 6 units long, that pointing South is 5, East and West 4, NE, SE, SW and NW 3 and NNE, ENE and so on 2 and the rest 1 unit. In general if the line pointing North is n units long there will be 2n−1 radial lines in all in the complete rose. You may assume that in your Java applet the method paint will be called when an image is to be displayed, and it gets passed an argument of type Graphics. The class Graphics has a method called drawLine that draws a line from one point to another given four integer arguments x1 , y1 , x2 and y2 . Write two versions of the compass rose applet. The first should use recursion on the length n of a line, while the second should be iterative. In the second case it may be useful to write an auxiliary method that calculates the highest power of two that divides exactly into a given number. The length of the North line should be specified as a final variable in the class. 8.5 Language Words In the context of Java, and given that this whole question is supposed to last 30 minutes, explain the following: • package • class • import • public CHAPTER 8. MODEL EXAMINATION QUESTIONS 240 • protected • interface • static 8.6 Exception abuse Show how Java exceptions can be used in conjunction with a for loop 1. to simulate the effect of a break; statement; 2. to simulate the effect of a continue; statement. 8.7 Queues A priority queue maintains a list of pairs, each consisting of a priority (an integer) and a name (a String), sorted into increasing order of priority. Three operations are required — one that just creates an empty queue, and then one called insert that takes an integer and a string as its arguments and places them in the correct position in the list. Finally a method next takes no arguments. It removes the first pair from the list and returns its text string. It should throw an exception if the queue is empty when it is called. Give the definition of a Java class that implements the queue object. 8.8 Loops Describe the features of Java for controlling the repeated execution of a block of code. Show how general uses of for, while and do could all be emulated using only loops that start of while (true). 8.9 Snap Two identical packs of ordinary playing cards (52 different cards per pack) are shuffled and places face downwards on a table. Two players then play a game of Snap. Each is allocated one pack, and in each turn in the game one card is turned up. The two upturned cards (one in front of each player) are compared. If the cards match a snap-turn is declared. A game ends when all 52 cards have been compared. Being computer scientists rather than five year olds these players 8.10. PARTITIONS 241 just record snap-turns and do not pick up or otherwise disturb the cards when one occurs! Write a Java program which will simulate the game for the purposes of determining the probability of there being at least one snap-turn in a game. You may assume the existence of a random number generator but must state the properties of it that you rely on. 8.10 Partitions Write a program in Java which, given two integer inputs j and k will output the combinations of k things partitions into k groups. For instance if j = 5 and k = 3 the output would be (5, (4, (3, (3, (2, 0, 1, 2, 1, 2, 0) 0) 0) 1) 1) 8.11 Laziness A Java class as follows has been defined abstract class NextFunction { public int next(int n); } class Lazy { int head; Lazy tailOrNull; NextFunction next; public Lazy(int head, NextFunction next) { this.head = head; this.tailOrNull = null; this.next = next; } public int first() { return head; } public Lazy tail() { if (tailOrNull != null) return tailOrNull; tailOrNull = new Lazy(next(head), next); CHAPTER 8. MODEL EXAMINATION QUESTIONS 242 return tailOrNull; } } The idea is to use this to represent lazy lists. In fact the small trick in the tail function that checks if the successor to a node has already been computed makes this a good representation. Derive a sub-class from NextFunction that overrides the next method with one that allows you to create a lazy list of integers (1,2,3,...). Write code that, when given a lazy list, will print the first n integers in it. Adapt the code so as to create a function that can accept a lazy list and generate a new lazy list from it that holds values which are the squares of the ones in the original list. Thus if passed my first lazy list as an argument this one would generate (1,4,9,...). 8.12 Cryptarithmetic Write a Java program which can solve cryptarithmetic puzzles in the format of the sum of two words. For example given the input SEND +MORE ----MONEY the program would output 9567 +1085 ----10652 NB Each letter has to represent a different digit. 8.13 Bandits In Snoresville in small-town America, there are N bandits, and only one sheriff, Sheriff Dozy, who likes to do the minimum amount of work possible. He knows quite a lot about each of them, to the extent that he knows precisely which bandits each bandit knows. Soon, $200,000 worth of gold will be deposited in Snoresville’s main bank; a very tempting target for the local thieves. Dozy knows that the defences and procedures are more than good enough to resist attack by 8.13. BANDITS 243 one bandit, and that they can even resist attack by N/3 bandits. He wonders if some sub-set of the collection of local bandits will manage to gang together to grab the gold. The friendship data for the bandits can be represented in the following way: A ‘1’ indicates a friendship with another bandit. If one bandit is known 1 N = 5 then he also knows of the bandit who knows him, i.e. 01 friendship is mutual. Each line ends with a ‘1’ because 111 each bandit knows himself, obviously. Explain why 0101 10011 the triangular table given is all that’s needed to detail all the friendships, and show how it can be expanded into a square to give the friendship data with a row for each bandit. [2 marks] Given a global data structure defined as an N by N matrix (int [][]gang), filled initially with zeros, and where N is the largest number of bandits to consider, give two Java procedures: 1. generate(): This should input from the keyboard an integer n, and a floating-point p. Check that n is in the range 0 < n ≤ N, and that p is a valid probability between 0 and 1. The procedure should then randomly generate a triangular table as above, such that the probability of any of the points being ‘1’ is p, except for each bandit with himself which should always be ‘1’. 2. square(): This should take the triangular table now in gang from the generate function and transform it into a square table as discussed earlier. It should then display the block in a tabular form. [6 marks] 3. A group of bandits is only possible when each bandit knows every other bandit in the group. In the example given above, what is the group with largest cardinality? [1 mark] 4. Examine the following pieces of code; the procedure compare and a fragment of code to show how it is called. Calling Fragment: for (i=0;i<n;i++) { compare(i,gang[i]); for(j=0;j<n;j++) { gang[i][j]=0; gang[j][i]=0; } CHAPTER 8. MODEL EXAMINATION QUESTIONS 244 } Main example int maxgroup[N]; int max=0; /* * NOTE: current[N] is one array of the array of * arrays gang[N][N] */ void compare(int i, int current[N]) { for (int j=0; j<n; j++) current[j] &= gang[i][j]; for (int k=i+1; k<n; k++) { if (current[k]==1) { compare(k,current); current[k]=0; } } if (count(current)>max) { max=0; for (int k=0; k<n; k++) { if (current[k]==1) maxgroup[max++]=k; } } } 5. Explain in detail what the function compare does, and how it works, paying special attention to the recursive nature of the procedure and the actions of the calling fragment, and giving a function count which returns the number of ‘1’s in the array current. [8 marks] 6. Outline a function main to bring all this together and for each N in the range 2 to 20 calculate an average, over 10 tries, of the cardinality, given p = 0.5, and display the smallest N for which the cardinality is less than N/3. This is the critical number of bandits for the gold safety, and for sheriff Dozy not to lose his job. [3 marks] 8.14 Exception Describe some circumstances where it is useful for functions to return errors as exception, and some where it is not. Give an example of an algorithm which is simplified by the use of exceptions. 8.15. FEATURES 245 8.15 Features Write brief notes on four of the following aspects of Java. In some cases it may be appropriate to compare what Java does in the situation with other programming languages such as ML. 1. Using the same name for several different functions; 2. Data types where a single type has several variants; 3. Programs that live in several source files; 4. Inheritance and abstract classes; 5. The degree to which code will behave the same when run on different computers. 8.16 More features For five of the following Java features write a very short code fragment (it does not have to be complete, and perhaps 2 or 3 lines will suffice in most of the cases) that illustrates the syntax involved. In each case explain briefly what your example achieves. 1. Declaration of constants; 2. Casts between class types; 3. The two styles of comment; 4. Catching an exception; 5. The switch statement, including a default label; 6. Summing all the BigInteger values that are in an array; 7. The class Object. CHAPTER 8. MODEL EXAMINATION QUESTIONS 246 8.17 Debate A grand debate is being planned by a society that has among its members a large number of computer professionals and working programmers. Arthur will propose a motion “That this house considers ML to be a much better programming language than Java”, while Larry will lead the opposition under the banner “Java is the language for any programmer who seeks employment and hence who can truly be referred to as working”. Organise the points in support of the two languages that each proponent will use to justify their position, and identify areas where they are liable to find common ground. You are not expected to reach a definite conclusion about which way the vote will go at the end of the debate: your job is just to collect and organise the arguments so that comparing and contrasting the merits of the two language becomes easy. 8.18 Design You have been invited to start a project to build a program that will check ML programs to see if they have missing punctuation marks or other mistakes, so that this check can be performed before the ML code is forwarded for full execution. So far all you know about what is wanted is the above. Without concerning yourself with fine detail of how the checking will be implemented, identify and discuss: 1. questions you might want to ask the client organisation about its needs before deciding on any more details of your design; 2. choices or options available to you when making a detailed project plan; 3. ways of partitioning the whole job into a handful of separate modules that could be implemented more or less independently; 4. plans for testing the code you write and determining whether, when notionally finished, it has met its objectives. 8.19 Filter (Coffee?) The structure of a binary tree containing integers at just some of its leaves could have been given by the ML type T defined as follows datatype T = X | N of int | D of T*T; 8.20. PARSE TREES 247 Define a Java class or set of classes that can be used to represent trees in the same general form. Then add a method filter which uses an integer k and a tree. Its job is to simplify trees by using the rule that and sub-node (type N in the ML declaration) where the integer stored is k gets turned into an X leaf. Then any node (X,t), or (t,X) [ie nodes of the ML type D where one of the sub-trees is an X] get turned into just t. Thus for instance if k = 0 the initial tree ((0,0),((2,0),3)) would simplify to just (2,3) 8.20 Parse trees What does it mean for a Java class to be abstract? A Java program includes the following class declarations: abstract Class Node { public int eval(); } Class Num extends Node { int value; public Num(int value) { this.value = value; } ... } Class Op extends Node { String sym; Node left; Node right; public Op(String sym, Node left, Node right) { this.sym=sym; this.left=left; this.right=right; } ... } The objective of the programmer who wrote this was to be able to write assignments such as test = new Op("*", new Num(4), new Op("-", new Num(7), new Num(2))); The variable test is of type Node which can cover either of the two concrete cases of Op (representing a dyadic operator together with its two operands) or Num 248 CHAPTER 8. MODEL EXAMINATION QUESTIONS (a number). Thus the above assignment sets up a representation of the expression 4 ∗ (7 − 2). The abstract class Node declares a method eval(). Fill in the dots in the other two classes with code that overrides this so that calling the eval method on a Node returns the value of the arithmetic expression is represents, supposing that the only operators that will be used are plus, minus and times. 8.21 Big Addition Java comes with a class BigInteger that represents potentially huge numbers. Suppose it did not, or for some reason you were prohibited from using it but still needed to work with large positive integers. To fit your needs you will define a new class called Big that stores integers as arrays of byte values, where each byte holds a single decimal digit from the number being used, with the least significant digit held at position 0 in the array. Write a definition of such a class including in it methods to create a big integer from an int (provided that int is positive), to add two Big values together and to convert from a big to a String ready for printing. You need not implement any other methods unless they are needed by the ones mentioned here. 8.22 Lists in Java A list in Java can be represented as a sequence of links. Each link is an object containing one value in the list and a reference to the rest of the list following the link. A null reference indicates the end of the list. Write a Java class that can represent such lists, where the items stored in lists are of type Object. Provide your implementation with two static public methods that append lists. The first of these should be called append and should take two arguments, its result should be the concatenation of the two lists and neither input should be disturbed. The second should be called conc and should have the same interface, but it should work by altering the final reference in the first list to point it towards the second, and it should thus not need to use new at all. 8.23 Pound, Shillings and Ounces The Imperial system for Sterling currency was based on the pound, shilling and penny (plural pence). There were 12 pence in a shilling and 20 shillings in a pound. A Java class that could store amounts in this format might be 8.24. DETAILS 249 class LSD { boolean negative; int pounds; int shillings; int pence; LSD(boolean m, int l, int s, int d) { ... } ... Adjust or finish off the constructor so that it raises an exception of class BadInput (which may be supposed to have been defined already) if the input is invalid, ie unless the number of pence is from 0 to 11 and the number of shillings from 0 to 19, and the specified number of pounds is positive. Now you need to provide a toString method in the class that converts currency into textual form. The following table shows the desired effect when a number of pence is first converted to LSD and then to a string: 0 1 10 60 80 252 479 1201 2400 -252 ⇒ ⇒ ⇒ ⇒ ⇒ ⇒ ⇒ ⇒ ⇒ ⇒ zero 1 penny 10 pence 5 shillings 6 shillings and 8 pence 1 pound and 1 shilling 1 pound, 19 shillings and 11 pence 5 pounds and 1 penny 10 pounds minus 1 pounds and 1 shilling Credit will be given for a clearly explained, concise and tidily presented solution. Minor syntax or punctuation errors in the Java code will not count heavily against you. 8.24 Details Give a brief explanation of each of the following aspects of Java 1. The difference between >> and >>>; 2. The possibility that in some program the test (a == a) might return the value false for some variable a; 3. The keywords final and finally; 250 CHAPTER 8. MODEL EXAMINATION QUESTIONS 4. The expression "three" + 3 and other expressions of a generally similar nature; 5. The meaning of or errors in (whichever case is relevant!) ... int [10] a; for (int i=1; i<=10; ++i) a[i] = 1-a[i]; ... 8.25 Name visibility A complete Java program may use the same name for several different methods or variables. Java has a number of features that allow the user to prevent such re-use of names from causing chaos. Describe there under the headings: 1. Scope rules within individual functions; [6] 2. Visibility of method names within classes, and the effects of inheritance; [8] 3. Avoiding ambiguity when referring to the names of classes. [6] 8.26 Several Small Tasks Write fragments of Java definitions, declarations or code to achieve each of the following effects. You are not expected to show the whole test of a complete program — just the parts directly important for the task described, and you may describe in words rather than Java syntax any supporting definitions or context that you will want to rely on. Clarity of explanation will viewed as at least as important as syntactic accuracy in the marking scheme. It is also understood that names of methods from the standard Java class libraries are things that programmers check in on-line documentation while writing code, so if you need to use any of these you do not need to get their names or exact argument-format correct provided that you (a) describe clearly what you are doing and (b) your use is correct at an overview level: 1. Take a long argument called x and compute the long value obtained by writing the 64 bits of x in the opposite order; [6] 8.27. SOME TINY QUESTIONS 251 2. Define a class that would be capable of representing simple linked lists, where each list-node contains a string. You should show how to traverse such lists, build them and how to reverse a list. In the case of the list reversing code please provide two versions, one of which creates the reversed list by changing pointers in the input list, and another which leaves the original list undamaged and allocates fresh space for the reversed version; [8] 3. Cause a line to appear in a the window of an applet running from the bottom left of the window towards the top right. Your line should remain visible if the user obscures and then re-displays the window, but you can assume that the size of the windows concerned will be fixed at 100 by 100 units. [6] 8.27 Some Tiny Questions 1. List the eight Java primitive data types. 2. What result will be printed if the following fragment of Java code is executed? Why? double d = 6.6; try { d = 1.0 / 0.0; } finally { System.out.println("d = " + d); } 252 CHAPTER 8. MODEL EXAMINATION QUESTIONS Figure 8.1: Remember: programming is fun! Chapter 9 Java 1.5 or 5.0 versus previous versions The Java course from 2005 onwards uses a version of Java (and its libraries) that support a range of things that earlier release did not. The commentary about these features here is not part of the examinable content of the course, but may help those who want their code to be backwards compatible, and may help supervisors understand how features new in 1.5 are relevant in an introductory Java course. 9.1 An enhanced for loop New Java allows direct iteration over either arrays or collection types using syntax along the lines of for (Type s : arrayOrCollection) use(s) With previous versions you would need for (int i=0; i<array.length; i++) use(array[i]) for arrays, or the yet more clumsy for (Iterator i=collection.iterator(); i.hasNext();) { Type s = (Type)i.next(); use(s); } There are special delicacies to watch with the use of nested iterations if you use the old versions, and a strong interaction with the generics feature described next. 253 254 CHAPTER 9. JAVA 1.5 OR 5.0 VERSUS PREVIOUS VERSIONS 9.2 Generics Most of the examples that use collections in these notes decorate the type declarations of the collections to show what they contain. For instance there could be use of HashMap<String,String> for a HashMap that will only be used with strings as both they keys and values it stores. With these decorations code is naturally type-safe. Older Java does not support this. The result is that instead all generic structures use the fall-back Object type. When data is retrieved from then the Java type-checker does not know what sort of Object is being used, and so casts, involving run-time checks, have to be used. 9.3 assert The assert keyword came in with Java 1.4. For use with any earlier version of Java you must remove them all. Please ensure that your code is fully debugged first! 9.4 Static imports If your new code includes a line such as import java.Math.PI and you then use the constant PI in your code you will need to remove the import statement and use the longer name Math.PI everywhere that you reference it. 9.5 Auto-boxing With both collections and the printf facility listed later, you can use the built in types (eg integers and reals, characters and booleans) without having to worry too much about special consequences of them being primitive built-in types rather than part of the Java class system. In older versions of Java this is not the case, and you need to make much more explicit use of the wrapper classes Integer, Double and so on. The effect is much messier code in places! The term “autoboxing” refers to the fact that these wrapper classes are still used, and use of a constructor new Integer(1) is referred to as “boxing” the integer up. 9.6 Enumerations In Java 1.5 you may have used enum to introduce a collection of distinct names, and you may then have used these enumeration values in switch statements. Be- 9.7. PRINTF 255 fore Java 1.5 you could either use an explicit encoding as integers (which does not protect you from type errors) or some more elaborate scheme that uses the class system to protect details of the implementation. In any case the effect is significantly more clumsy than the new scheme. 9.7 printf When you look at many existing Java books and sample codes you will see printing done using the idiom System.out.println("The result is" + i); where these notes have written something more like System.out.printf( "The result is %d (or %<x in hex)%n", i); These notes have preferred the second if only because the format string item %d makes explicit what type of item is being displayed, and so there is an extra check on internal consistency in your code. Format conversions provide amazing (and sometimes complicated) levels of refinement in controlling just how simple information such as numbers are to be laid out. Replicating that control using the facilities from previous Java releases is tedious and leads to fairly bulky and unreadable mess. 9.8 Scanner If you needed to split an input file into words or wanted to read in a simple column of numbers you may have used Scanner. Beware because it is new and you would need to do things by hand to if backwards compatibility was essential. In big programs Scanner may not matter but I find it jolly handy in all sorts of small examples. 9.9 Variable numbers of arguments for methods The smallVarargs facility is something you should probably not often use directly in your own code, but it is exploited by printf and friends. The old way of achieving a similar effect is to make your function accept an array of items, and construct a new array to pass arguments in each call you make to it. 256 CHAPTER 9. JAVA 1.5 OR 5.0 VERSUS PREVIOUS VERSIONS 9.10 Annotations Java 1.5 provides a general framework for adding annotations to your code in a way that is expected to make it easy for program management tools to extract them. This is a relative of the notation /** that inserts “document comments” that javadoc can extract. The scheme is not mentioned in this course beyond the fact that the new-style annotations are introduced by a name preceded by an at-sign (@), and new styles of annotation can be declared using the keyword @interface. Any code that has this syntax will need it removed for use with earlier versions of Java. This will not change the behaviour of the code itself in any way, but would have an effect on how it could interact with annotationprocessing tools (see the Java documentation for the command apt). For those who want to investigate further it might be useful to note that the Java reflection mechanisms can detect when a method defined in a class has been annotated. A lot of the time you will not use annotations, but when you do they may be a huge help and you would hate to go back to a system without them. 9.11 Enhanced concurrency control For example ConcurrentHashMap, EnumSet. Many of the above features interact together so that the savings of using them combined are even greater than using them one at a time. A consequence of that is that giving them up would be even more painful than you might at first expect. I fully expect to see many of them becoming the standard idiom for Java programmers in the very near-term future. Bibliography [1] Abramowitz and Stegun. Handbook of Mathematical Functions (with formulas, graphs and mathematical tables). Dover, 1965. [2] David Barnes and Michael Kölling. Objects First with Java: a Practical Introduction using BlueJ. Prentice Hall/Pearson, 2 edition, 2005. [3] Colin Bentley. Introducing SSADM 4+. NCC Blackwell, and also see http://www.blackwellpublishers.co.uk/ssadmfil.htm, 1996. [4] Jon Bentley. Programming Pearls. Addison-Wesley, 1986. [5] Jon Bentley. MoreProgramming Pearls. Addison-Wesley, 1988. [6] Judy Bishop. Java Gently. Addison Wesley, 3 edition, 2001. [7] Fred Brookes. The Mythical Man Month. Addison Wesley, 2 edition, 1996. [8] J. Cameron. JSP and JSD: The Jackson Approach to Software Development. IEEE Computer Society Press, 1989. [9] Leiserson Cormen and Rivest. An Introduction to Algorithms. MIT and McGraw-Hill, 1990. [10] E. W. Dijkstra. A Discipline of Programming. Prentice-Hall, 1976. [11] Bruce Eckel. Thinking in Java. Prentice Hall, 1998. [12] Christopher Essex, Matt Davison, and Christian Schulsky. Numerical monsters. SIGSAM Journal, 34(4):16–32, December 2000. [13] John McCarthy et al. The Lisp 1.5 Programmer’s manual. MIT Press, 1965. [14] M. H. Halstead. Elements of Software Science. Elsevier North Holland, 1977. [15] Henry S Warren Jr. The Hacker’s Delight. Addison Wesley, 2003. 257 258 BIBLIOGRAPHY [16] Brian W. Kernighan and Dennis M. Richie. The C programming language. Prentice-Hall, 1978. [17] Donald E. Knuth. Literate Programming. CSLI Lecture Notes and CUP, 1992. [18] Steve Maguire. Writing Solid Code. Microsoft Press, 1993. [19] C.A.R. Hoare O.-J. Dahl, E.W. Dijkstra. Structured Programming. Academic Press, 1972. [20] Eric S Raymond. The Cathedral and the Bazaar. O’ Reilly, 1999. [21] Guy Steele. Common Lisp the Language. Digital Press, 1990. [22] X3J11. ANSI X3.159, ISO/IEC 9899:1990. American National Standards Institute, International Standards Organisation, 1990. Index 3n + 1, 90 π, 47 ++ and --, 71 abstract, 123 Annotations, 256 Applet, 37 Applet + application, 157 appletviewer, 42 array.length, 62 Arrays, 61 Assert, 254 assert, 88 Auto-boxing, 254 AWT, 155 BigInteger, 98, 147 Binary files, 145 Binary trees, 115 Book-list, 18 Bottom-up implementation, 189 BufferedImage, 57 BufferedReader, 118 Buggy sample code, 211 byte, short, int and long, 52 Capital letters, 65 catch, 87 char, 56 Character escapes, 56 Checking assertions, 88 close a file, 142 Code layout, 33 Collection classes, 150 Colouring by syntax, 38 Colours for drawing, 45 Command-line arguments, 61 Compiling a Java program, 34 Complex numbers, 108 Constructors, 107 Continued fractions, 139 cos, 47 crypto jurisdiction policy files, 148 Default visibility, 122 Designing and testing, 167 Dip. Comp. Sci., 16 Documentation comments, 33 Documenting, 195 Doubles as bit-patterns, 138 Dutch national flag, 67 Eight queens, 81 Emacs, 29, 34 encryption and checksums, 147 Enhanced for loop, 253 Enumerations, 254 Exam questions, 237 Extended Euclinean algorithm, 84 File access, 140 File object, 144 FileReader, 141 FileWriter, 143 final, 89, 122 finally, 87 float and double, 54 Forests of trees, 79 259 INDEX 260 Game of Life, 80 Generics, 254 getClipBounds, 112 Global Font Lock, 38 goto, 51 Nested scopes, 100 Network programming, 153 new, 63 Newline character, 56 Not a Number, 54 Hacker’s Delight, 76 Hakmem, 76 Hello World, 28, 31 Hexadecimal, 54 Hilbert matrix, 68 HTML, 42 Object, 130 Object as a class, 130 Object oriented, 125 Obtaining Java, 24 Octal, 54 Oz the Werewolf, 12 IEEE floating point standard, 54 import, 102 import static, 47 Infinities, 54 Inheritance, 115 Initialised arrays, 62 inner class, 156 InputStreamReader, 118 Integer overflow, 52 interface, 124 package, 101 Packages and jar files, 110 paint, 46 parseInt, 118 Pentominoes, 134 Pollard rho, 96 Polynomial manipulation, 93 Portability, 205 printf, 255 Printing floating point values, 56 PrintWriter, 143 private, 122 protected, 122 public, 122 public and private, 101 Puzzle with two flasks, 164 JApplet, 37 Java 1.5, 253 Java Bean, 106 JLex, 118 Legendre polynomials, 95 line numbers, 147 Lisp in Java, 219 Long integer constants, 53 Mandelbrot set, 110 Menus etc, 155 Mouse co-ordinates, 39 Mouse events, 39 MULDIV, 209 Mystery big decimal, 161 Nancy Silverton, 185 native, 124 Quicksort, 91 Random numbers, 148 RC4 encryption, 69 read characters, 141 Reflection, 130 Reserved words, 49 Rounding errors, 67 RSA encryption, 160 RSI, 194 Running a simple program, 34 Scanner, 255 INDEX Send more money, 242 set and get methods, 105 Shift operators, 72 Sieve for primes, 77 sin, 47 Sorting, 92 Sprouts, 210 static, 122 static import, 254 StreamTokenizer, 118 String concatenation, 61 Supervisor’s guide, 16 Swing, 155 switch statements, 85 synchronized, 124 tan(x): series, 113 Tar Pits, 169 Ten recommendations, 13 Text editing, 164 this, 108 Threads, 150 throw, 88 Tickable exercise 1, 24 Tickable exercise 2, 43 Tickable exercise 3, 65 Tickable exercise 4, 90 Tickable exercise 5, 93 Tickable exercise 6, 110 Tickable exercise 7, 162 Top-down design, 186 Transpose a matrix, 209 try, 87 Turtle graphics, 46 Two’s complement, 53 Unicode, 57 Varargs, 255 Visibility qualifiers, 120 Walk-through, 193 261 Webget, 162 Weirdx, 234 Wolves and caribou, 109 Writing programs is easy, 168 Writing programs is fun, 168 Y co-ordinate, 47 zip and unzip files, 147