CC 210 Textbook

This is the textbook for CC 210.

Subsections of CC 210 Textbook

Chapter 0

Introduction

Welcome to the Fundamental Computer Programming Concepts course!

Subsections of Introduction

Course Introduction - Spring 2022

YouTube Video

Resources

Video Script

[Slide 1]

Hello and welcome to the Computational Core program!

My name is Emily Alfs-Votipka, and I’ll be one of the instructors for this program. My contact information is shown here, and is also listed on the syllabus.

[Slide 2]

There are many other instructors and TAs for this program that you may interact with or see in the tutorial videos. They all have been instrumental in the development of this program.

[Slide 3]

In this course we will primarily use a K-State email group (cc110-help or cc110-help@ksuemailprod.onmicrosoft.com) to communicate. Email sent to this address is forwarded to all instructors and TAs. Our replies to you will also be shared amongst the instructors and TAs so we all have access to the assistance you have already received. We will respond to you within a business day So a question emailed Friday night may not receive an answer before Monday.

If you wish to pose a discussion topic to you classmates, you should use the discussion feature in Canvas. Please note that asking a question on a discussion forum is not the same as emailing cc110-help; we will certainly monitor the discussion channels, but not with the same speed as the “help line”. Please read and adhere to the guidance on Netiquette in the syllabus for all electronic communications.

[Slide 4]

In addition to email and Canvas, we’ll be using the online learning platform Codio for most of the programming tutorials and projects in this program. We’ll also discuss how to use Codio later in this module.

[Slide 5]

The Computational Core program consists of several courses, and each course contains a number of learning modules. There are about 30 modules in this course. Each module will usually consist of some lecture material and quizzes, and there are a few modules which include a programming component. The modules themselves are gated, which means that you must complete each item in the module in order before continuing. In addition, the modules enforce prerequisite requirements from other modules. For CC 110 you must complete them in order starting with module 0, the enroll module should be completed first, but is not a prerequisite.

You are welcome to work on this course at any time during the week as your schedule allows, provided that you complete each module before the listed due date. There will be roughly two modules due each week. The modules are self-contained, and nearly all of the grading in this course is completed automatically through Canvas and Codio. So, you can complete modules at any time before the due date, and once a module is complete, you may immediately start on the next one.

[Slide 6]

Looking ahead to the rest of this introductory module, you’ll see that there are a few more items to be completed before you can move on. In the next video, we’ll discuss a bit more information about navigating through this course on Canvas and using the Codio learning environment.

[Slide 7]

One thing we highly encourage each of you to do is read the syllabus for this course in its entirety, and let us know if you have any questions. My view is that the syllabus is a contract between me as your teacher and you as a student, defining how each of us should treat each other and what we should expect from each other. We have made a few changes to the standard syllabus template for this program, and those changes are clearly highlighted. Finally, the syllabus itself is subject to change as needed as we adapt this program to meet the needs of its students, and all changes will be clearly communicated to everyone before they take effect.

[Slide 7]

One very important part of the syllabus that every student should read is the late work policy. First off, each module has a due date, and you may work on that module at any time before it is due, provided you have met the prerequisites. As discussed before, you must do all the readings and assignments in a module in listed order before moving on, so you cannot jump ahead. A module is considered completed when all items have been completed.

For the purposes of grading, we will use the date and time that the confirmation quiz was submitted at the end of each module to determine when the module was completed. This is due to the way that Codio handles automated grading, as it may resubmit previously graded assignments if an error in the module is corrected, making a previously completed assignment appear to be submitted late.

If any work is submitted after the due date, a penalty of 10% of the total points possible in that assignment will be deducted for each day it is late, up to a maximum of 3 days. After 3 days beyond the due date, you will receive a 0 on the assignment. Please refer to the full late policy in the syllabus for more information about how late work is handled in this course.

Finally, even if a module is late, it still must be completed before you can move on to a later module. So, it is very important to avoid getting behind in this course, as it can be very difficult to get back on track. If you ever find that you are struggling to keep up, please don’t be afraid to contact either the instructors or GTAs for assistance. We’d be happy to help you get caught back up quickly.

In this program, the standard “90-80-70-60” grading scale will apply, though I reserve the right to curve grades up to a higher grade level at my discretion. Therefore, you will never be required to get higher than 90% for an A, but you may get an A if you score slightly below 90% if I choose to curve the grades.

This is intended to be a completely online, self-paced course. There are no mandatory scheduled course times. All of the content is available online, so you can work whenever and wherever you want. It could be a 3-hour block once a week, or a few minutes here and there between classes. It’s really up to you and your schedule. However, remember that each module may require 4 to 6 or more hours of work to complete, so make sure you have plenty of time available to devote to this course.

Also, a vast majority of the grading in this course will be handled automatically through Canvas and Codio. This means that you’ll be able to receive feedback directly from those systems as soon as you submit your work. You may also contact the instructors and GTAs for additional tips and feedback regarding your work, but depending on the number of students in the program, we may not be able to review every student submission directly.

In addition, due to the flexible online format of this class, there won’t be any long lecture videos to watch. Instead, each module will consist of a guided tutorial and several short videos, each focused on a particular topic or task. Likewise, there won’t be any textbooks required, since all of the information will be presented in the interactive tutorials through Codio. Finally, since we are using Codio as our learning platform, you won’t have to deal with installing and using a clunky integrated development environment, or IDE, just to learn how to program. Codio helps make learning to program quick and painless by moving everything to the web.

For this course, the only supplies you’ll need as a student are access to a modern web browser and a broadband internet connection. No other special hardware or software is necessary!

Finally, as you are aware, this course is always subject to change. This is a relatively new program here at K-State, and we’re always working on new and interesting ideas to integrate into the courses. The best advice I have is to look upon this graphic with the words “Don’t Panic” written in large, friendly letters. If you find yourself falling behind, or not understanding seek our help via cc110-help.

So, to complete this module, there are a few other things that you’ll need to do. The next step is to watch the video on navigating Canvas and Codio, which will give you a good idea of how to most effectively work through the content in this course.

To get to that video, click the “Next” button at the bottom right of this page.

Subsections of Course Introduction - Spring 2022

Navigating Canvas & Codio

YouTube Video

Resources

Video Script

This course makes extensive use of several features of Canvas which you may or may not have worked with before. To give you the best experience in this course, this video will briefly describe those features and the best way to access them.

When you first access the course on Canvas, you will be shown this homepage. It contains quick links to the course syllabus and Piazza discussion boards. This is handy if you just need to jump to a particular area.

Let’s walk through the options in the main menu to the left. The first section is Modules, which is where you’ll primarily interact with the course. You’ll notice that I’ve disabled several of the common menu items in this course, such as Files and Assignments. This is to simplify things for you as students, so you remember that all the course content is available in one place.

When you first arrive at the Modules section, you’ll see all of the content in the course laid out in order. If you like, you can minimize the modules you aren’t working on by clicking the arrow to the left of the module name. I’ll do so, leaving the introductory module open.

As you look at each module, you’ll see that it gives quite a bit of information about the course. At the top of each module is an item telling you what parts of the module you must complete to continue. In this case, it says “Complete All Items.” Likewise, the following modules may list a number of prerequisite modules, which you must complete before you can access it.

Within each module is a set of items, which must be completed in listed order. Under each item you’ll see information about what you must do in order to complete that item. For many of them, it will simply say view, which means you must view the item at least once to continue. Others may say contribute, submit, or give a minimum score required to continue. For assignments, it also helpfully gives the number of points available, and the due date.

Let’s click on the first item, Course Introduction, to get started. You’ve already been to this page by this point. Many course pages will consist of an embedded video, followed by links to any resources used or referenced in the video, including the slides and a downloadable version of the video. Finally, a rough video script will be posted on the page for your quick reference.

While I cannot force you to watch each video in its entirety, I highly recommend doing so. The script on the page may not accurately reflect all of the content in the video, nor can it show how to perform some tasks which are purely visual.

When you are ready to move to the next step in a module, click the Next button at the bottom of the page. Canvas will automatically add Next and Previous buttons to each piece of content which is accessed through the Modules section, which makes it very easy to work through the course content. I’ll click through a couple of items here.

At any point, you may click on the Modules link in the menu to the left to return to the Modules section of the site. You’ll notice that I’ve viewed the first few items in the first module, so I can access more items here. This is handy if you want to go back and review the content you’ve already seen, or if you leave and want to resume where you left off. Canvas will put green checkmarks to the right of items you’ve completed.

Continuing down the menu to the left, you’ll find the usual Canvas links to view your grades in the course, as well as a list of fellow students taking the course.

===

Now, let’s go back to Canvas and load up one of the Codio projects. To load the first Codio projects, click the Next button at the bottom of this page to go to the next part of this module, which is the Codio Introduction tutorial. On that page, there will be a button to click, which opens Codio in a new browser window or tab.

Once Codio loads, it should give you the option to start the Guide for that module. You’ll definitely want to select that option whenever you load a Codio project for the first time.

From there, you can follow the steps in that guide to learn more about the Codio interface. The first page of the guide continues this video. I’ll see you there!

Where to Find Help

YouTube Video

Resources

Video Script

[Slide 1]

As you work on the materials in this course, you may run into questions or problems and need assistance. This video reviews the various types of help available to you in this course.

[Slide 2]

First and foremost, anytime you have a questions or need assistance in the Computational Core program, please email the appropriate help group. It is the best place to go to get help with anything related to this program, from the tutorials and projects to issues with Codio and Canvas. For example, if you are enrolled in CC315 and have questions, from your KSU email, you would type cc315-help and hit tab to auto-complete the email.

[Slide 3]

If you have any issues working with K-State Canvas, K-State IT resources, or any other technology related to the delivery of the course, your first source of help is the K-State IT Helpdesk. They can easily be reached via email at helpdesk@ksu.edu. Beyond them, there are many online resources for using Canvas, all of which are linked in the resources section below the video.

[Slide 4]

If you have any issues using the Codio platform, you are welcome to refer to their online documentation. Their support staff offers a quick and easy chat interface where you can ask questions and get feedback within a few minutes.

[Slide 5]

If you have issues with the technical content of the course, specifically related to completing the tutorials and projects, there are several resources available to you. First and foremost, make sure you consult the vast amount of material available in the course modules, including the links to resources. Usually, most answers you need can be found there.

Of course, as another step you can always exercise your information-gathering skills and use online search tools such as Google to answer your question. While you are not allowed to search online for direct solutions to assignments or projects, you are more than welcome to use Google to access programming resources such as StackOverflow, language documentation, and other tutorials. I can definitely assure you that programmers working in industry are often using Google and other online resources to solve problems, so there is no reason why you shouldn’t start building that skill now.

[Slide 6]

Next, we have grading and administrative issues. This could include problems or mistakes in the grade you received on a project, missing course resources, or any concerns you have regarding the course and the conduct of instructors and your peers. Since this is an online course, you’ll be interacting with us on a variety of online platforms, and sometimes things happen that are inappropriate or offensive. There are lots of resources at K-State to help you with those situations. First and foremost, please email your instructor as soon as possible and let them know about your concern, if it is appropriate for them to be involved. If not, or if you’d rather talk with someone other than your instructor about your issue, I encourage you to contact either your academic advisor, the CS department staff, College of Engineering Student Services, or the K-State Office of Student Life. Finally, if you have any concerns that you feel should be reported to K-State, you can do so at https://www.k-state.edu/report/. That site also has links to a large number of resources at K-State that you can use when you need help.

[Slide 7]

Finally, if you find any errors or omissions in the course content, or have suggestions for additional resources to include in the course, email the instructors. There are some extra credit points available for helping to improve the course, so be on the lookout for anything that you feel could be changed or improved.

[Slide 8]

So, in summary, the content and links in the modules should always be your first stop when you have a question or run into a problem. For issues with Canvas or Codio, you are also welcome to refer directly to the resources for those platforms. For questions specifically related to the projects, use the courses help group. For grading questions and errors in the course content or any other issues, please email the instructors for assistance.

Our goal in this program is to make sure that you have the resources available to you to be successful. Please don’t be afraid to take advantage of them and ask questions whenever you want.

Subsections of Where to Find Help

What You'll Learn

YouTube Video

Resources

Video Script

Finally, before embarking on this program, let’s take a brief minute to review what you’ll learn by the time you complete the program.

Of course, the biggest and most impactful outcome will be learning how to write computer programs. Throughout the Computational Core program, you’ll learn either the Java or Python programming language, and get to a point where you are quite proficient with your language of choice. You’ll be capable of building your own programs from scratch to meet many of the challenges you’ll encounter in your career or elsewhere. This skill alone will set you well above your peers.

There are many additional benefits beyond just learning how to write programs. For starters, programming involves a large amount of problem solving and computational thinking, and these courses will help sharpen you skills in both areas. In addition to programming, you’ll also learn about software engineering methods that will help you build better programs, but also data structures and algorithms that will make your code more efficient and useful as it manipulates and stores data. Of course, you’ll also pick up some new math and logic skills, as both are vitally important to understanding computer code. Lastly, we’ll spend a bit of time discussing how computers actually work, so you can see how your code actually gets a computer to perform the tasks you desire.

Finally, you may be asking yourself why this is important. I could absolutely bring out large numbers of statistics stating how many computer programming jobs are available right now, and how we have a distinct lack of capable graduates to fill these positions. I could also talk about how much more money you could make as a computer programmer than in many other fields. But, instead, I think it is best to just present this quote from Stephen Hawking, one of the most brilliant people to ever live:

Whether you want to uncover the secrets of the universe, or you just want to pursue a career in the 21st century, basic computer programming is an essential skill to learn. - Stephen Hawking

This is just one of the many great quotes encouraging you to learn computer programming from Code.org. I highly recommend checking out their quote archive whenever you need additional inspiration.

That should cover all of the background information you’ll need before you start this program. The rest of this module includes the full course syllabus and a few assignments that you should read through before beginning the course, but you don’t have to do anything else for them right now. Finally, this module wraps up with a quick quiz making sure you are 100% ready to take this course.

Best of luck to you on your adventure through this program!

Subsections of What You'll Learn

How to Learn Programming

YouTube Video

Resources

Video Script

Before we launch into the course itself, I wanted to take a few minutes to share some information with you regarding what we know about how students learn to program. This isn’t just anecdotal evidence from computer science teachers like me, but theories and research from education researchers who study how humans learn new skills and abilities throughout their lives.If I had to summarize all of this information in as few words as possible, I’d simply say “do the work.” Learning to program is difficult, and the only way to really get good at it is through constant practice and learning. However, that greatly oversimplifies the information that I want to share, and I’m hoping that you’ll find some helpful takeaways from this video that you can incorporate into your learning process.

Before I begin, I want go give all the credit to Nathan Bean for developing this information as part of his CIS 400 course. He graciously allowed me to use his hard work here, and I encourage you to check out his original version, which is available at the URL shown on this slide.

The statement “do the work” is a shorter version of a very common quote from educators, which is “the person doing the work is the person doing the learning.” I couldn’t find a solid reference for who said it first, so I’ll just attributed it to various educators throughout time. This really highlights one of the biggest struggles many students run into when learning to program. There are so many guides online, and the answer to many simple problems can be found through a quick Google search. You can just copy and paste the code, and then your program works. However, did you really learn how to write that program and what it does, or just how to find a quick answer? While this may be a useful tactic from time to time, if you rely too much on other people to do your coding, you really won’t learn it yourself. This is just like learning to shoot free throws on a basketball court or beating your best time in a speedrun - you can’t just watch someone do it and expect to do it yourself (believe me, I’ve tried). So, if you aren’t doing the work, you aren’t really learning.

Next, let’s address a major myth in computer science. I’ve heard this many times: “some people are just natural born programmers, and others simply cannot learn to program.” And yes, on the surface, it may appear to be this way. Some students just seem to have a knack for programming, and you may sit and struggle and not really get anywhere. However, there is no innate skill or ability that makes you good at programming.

Instead, let’s reframe what it means to learn programming. At its core, programming is learning to write steps to solve problems in a way that a computer can perform those steps. That’s really what we are doing when we learn programming.

So, we must focus on learning how to write those steps with the proper exactitude and precision so that they make sense, and we must understand how a computer functions to be able to program that computer effectively. So, when you see someone who is good at programming, it’s not because they are good at some esoteric skill that you’ll never have - they just know how to express their steps properly and know enough about how a computer works to make their program do what they want. That’s really it! And, to be honest, after a single semester of learning to program, you’ll have all the skills you need to do both of those things! If you know how to make conditionals, loops, functions, and use simple variables and arrays, that’s really all you need. Everything else that comes after that is just refining those skills to make your programs more powerful and your coding more efficient.

So, how do we learn these skills? Well, there are a couple of important pieces we need to make sure are in the right place first. For starters, we need to have the correct mindset. Many times I’ll see students struggle to learn how to program, and they’ll say things like what you see on this slide. “Its too hard.” “I don’t understand this.” “I give up.” Statements like this are the sign of a “fixed mindset,” and they can be one of the greatest blockers preventing you from really learning to program. Just like learning any other skill, you have to be open to instruction and willing to learn, or else you’ve failed before you even started.

Instead, we want to focus on building a growth mindset. In the TED talk by Carol Dweck that is linked below this video, which I encourage you to watch, she talks about the power of “yet.” We can turn these statements around by simply adding positive power of “yet” - “I don’t understand this yet.” “I love a good challenge.” “I’ll keep trying until I get it.” Going into a programming project with a mindset that is open to growth and change is really an important first steps. When I feel like I’m getting a fixed mindset, I like to think about how difficult it would be to teach a child to tie their shoes if they don’t want to learn. As soon as I realize that, it is pretty easy to recognize that same problem in myself and work to correct it.

So, once we have our growth mindset, how do we actually learn to program? To understand that, let’s dive a bit into the world of educational theory and the work of Jean Piaget. Piaget was a biologist and psychologist who studied how young children acquired new knowledge, and he helped pioneer the concept of Constructivism, one of the most influential philosophies in education. You can read more about Constructivism in the links below this video.

One particular thing that Piaget worked on was a theory of genetic epistemology. Epistemology is the term for the study of human knowledge, so genetic epistemology is the study of the origins, or genesis, of that knowledge. Put more clearly, it’s the study of how humans create new knowledge. This concept was inspired by research done on snails - he was able to prove that two previously distinct species of snails were actually the same by moving snails from one habitat to another and observing how they modified their behaviors and how their shells grew to match the snails in the new habitat. Put clearly, the snails displayed an altered behavior based on their environment. They tried to exist in equilibrium with their environment by adapting their behaviors to fit what they now experienced in the word.

Piaget suspected that something similar happens when humans try to learn something - the brain tries to adapt itself to maintain an equilibrium in its environment, which in this case is the existing knowledge it contains. So, when the brain is exposed to new ideas, it must somehow adjust to account for that new information. Piaget proposed two different mechanisms for how this occurs: assimilation and accommodation. In assimilation, new knowledge can be added to existing structures in the brain. For example, if you are exposed to a new color, such as periwinkle, you can see that it falls somewhere between blue and violet, two colors you already know. So, you can assimilate that new knowledge into the existing knowledge without a major disruption to your mental structure of existing colors. Accommodation, on the other hand, happens when your brain must radically adapt to new information for which no existing structures exist. This can be very difficult, and can lead to a lot of struggle and frustration when trying to get “over the hump” on a new subject. Think about learning algebra or a new language for the first time - you really don’t have anything you can use to help understand this new material, so you just have to keep at it until those new structures are formed in your brain.

Unfortunately, to achieve accommodation, your brain simply has to build brand new structures to store and represent all of this new information, and that process is difficult and takes time. Put another way, it takes significant stimulus, usually in the form of doing homework, struggling with difficult problems and wrestling with the new information to try and understand it all, to create enough disequilibrium in your brain that, coupled with a growth mindset, will allow accommodation to occur. However, when all the pieces are in the right place, and you work hard and have a growth mindset, then…

EUREKA! The structures will form, and you’ll get over that huge hurdle, and things will start falling into place. It may not happen all at once, but it does happen (you’ve probably had it happen to you several times already - think about some eureka moments from your past - were they related to learning a new skill?). Of course, there’s a good chance that your brain might form a few incorrect structures in the process, so you’ll have to overcome those as you continue to learn. I still struggle to spell some words because my brain formed incorrect structures when I was still learning. But, if you continue to work hard and be open to learning, you’ll eventually sort those errors out as well.

Let’s look at one other concept in education, which is called stage theory. Piaget identified four stages that children go through as they learn to reason about the world. Those four stages are shown on this slide. In the sensorimotor stage, the child is just using their senses to interact with the world, without any real understanding of what will happen when they perform an action. This is best represented by babies and toddlers, who touch and taste everything in their surroundings. Next, the preoperational stage is represented in young children as they start to think symbolically about the world, using pictures and words to represent actions and objects. They then progress to the concrete operational stage, where they can begin to think logically and understand how concrete events happen. They can also start to think inductively, building the general principles of the world from their specific experiences. For example, if they observe that cooked spaghetti is better than raw spaghetti, they might reason that other foods like potatoes are better cooked than raw. Finally, the last stage is the formal operational stage. This stage is represented by the ability to work fully with an abstract work, formulating and testing hypotheses to truly understand how the world works and predict how new items will work before experiencing them firsthand.

Many later researchers built upon this model to show that adults learn in much the same way. They also discovered that the stages are not rigid, and you may exhibit behaviors from multiple stages at any given time. This is called the “overlapping waves” model, and is shown here in this diagram. So, as you learn new skills, you may be at the operational stage in some areas, but still at the preoperational stage in other areas. This explains why some concepts may make sense while others don’t for a while - you just have to keep going until it all fits together.

So, how can we apply all of this information to programming? One theory comes from the work of Lister and Teague, who proposed a developmental epistemology of computer programming. Put another way, they applied this theory to computer science education, and gave us a unique way to think about the different stages of learning to program.

At the sensorimotor stage, we’re just getting the basics. So, when given a piece of code and asked to trace what it does, we still make lots of errors and get the answer incorrect. If we want to get a program to work ourselves, it usually involves a lot of trial and error, and many times when it does end up working we don’t even know exactly why it worked that time, but we’re building up a baseline of information that we can use to construct our mental model of how a computer works.

As we progress into the preoperational stage, we become better at tracing code correctly, but we still struggle to understand what the program itself does. We see each line of code as a separate instruction, but not the entire program. A great analogy is reading a recipe that calls for flour, water, salt, and yeast. Will it make bread? Biscuits? Pie crust? We’re not sure yet, but at least we can recognize the ingredients. To solve problems at this stage, we typically will randomly adjust pieces of our code that we don’t quite understand and see what it does, trying to form a better idea of the importance of each line in the code.

Eventually, we’ll get to the concrete operational stage. At this stage, we can construct our own programs, but many times we are simply piecing together parts that we’ve used before and performing some futile patches and bugfixes as we refine the program. We can also work backwards to figure out what a program does from execution results, but we still aren’t very good at deducing the results from the code itself. However, we’re starting to work with abstraction, though we tend to simplify things to a level that we are more comfortable with.

Finally, we’ll reach the formal operational stage. At this stage, we can comfortable read and understand code without executing it, quickly seeing what it does and how it works without fully tracing it ourselves. We can also start to form hypotheses for how to build new programs and code, and reason about whether different approaches would work better or worse than others. This is the goal stage for any programmer! Once you have reached this stage, then you’ll feel totally at home working in code and developing your own programs from scratch.

So, how can we enable ourselves to be the best learners we can be? There is lots of interesting research in that area, best summarized in the book “The New Science of Learning” that is linked below this video. Let’s go through a few of the big concepts.

First, getting ample and regular sleep is important, because it allows your brain to build those knowledge structures we discussed earlier and store the memories from the day in long-term storage. Without enough sleep, your brain is unable to process memories offline and make them ready for retrieval later on, an important step in learning. Also, consuming large amounts of caffeine or alcohol can disrupt your sleep patterns, so keep that in mind before you pour that next cup of coffee or go out partying. You can also take advantage of modern technology to help you track your sleep - most smart watches and smartphones today can help with that!

Likewise, regular exercise is important to both your physical and mental health. When you exercise, especially aerobic exercise that gets your heart rate up, your body releases neurochemicals that help your brain cells communicate. In addition, just getting up and moving around regularly helps keep your body healthy, so take regular breaks, and consider getting a standing desk for some extra benefits.

Research also shows that engaging your senses is an important part in learning. This is why we, as teachers, try to vary our lessons with pictures, videos, activities, and more. It is also the basis of the cognitive apprenticeship style of learning that we use, which you can learn more about in the links below this video. We show you the code we are writing, engaging your sense of vision, while talking about it so you are also listening, and then you are writing your own version, using your sense of touch. You can build upon this by using your senses while you learn by taking notes during a lecture video, building concept maps, and even printing out and writing on your code and these lecture scripts. All of these processes help engage different parts of your brain and make it that much easier to build new knowledge structures.

Looking for patterns is another important way to understand programming. There are many common patterns in computer programs, such as using a for loop to iterate through an array, or an if-else statement to determine if a particular variable is set to a valid value. By recognizing and understanding those patterns, we can more quickly understand new programs that use slightly different versions of the same code. Humans are naturally very good at pattern recognition, and it is one of the reasons why we see the same code structures time and time again - not because they are the only way to accomplish that goal, but because that structure is commonly used across many programs and therefore is easier to understand.

There is quite a bit of research into how memories are formed and how we can adjust our studying habits to take advantage of that. For example, cognitive science shows that the parts of our brain responsible for memory creation are active up to one hour after a learning experience has ended, such as a lecture video or activity. So, instead of jumping to the next task, you may want to take a little while to reflect on what you just did and let it sink in before moving on. Likewise, to build strong memories, it is important to constantly recall the memory or use the skills you’ve learned to strengthen their structures in the brain. This is why teachers like to throw in a few questions from a previous exam or quiz every once in a while - it helps strengthen those structures by forcing you to recall information you’ve learned previously. On the other hand, many students try to “cram” a bunch of information right before an exam, only to forget it soon after because it wasn’t recalled more than once. As you progress further, we’ll continue to come back to concepts you’ve already learned and build upon them, a process called elaboration that helps reinforce what you’ve already learned while building new, related knowledge.

Finally, it is important to remember that we must give our brains the space it needs to focus on the task at hand. Multitasking while learning, such as watching YouTube or Twitch, chatting with friends, or listening to a lecture video while coding can all reduce your brain’s ability to form strong memories and do well. In fact, research shows that individuals who try to multitask tend to make 50% more errors and spend 50% more time on both tasks. So, instead of giving yourself distractions, try to find things that will help you focus better - there are some great playlists online for music without lyrics that can help you focus or code better, and you can easily mute notifications on your phone and on your computer for an hour or so while you work.

So, let’s summarize what we’ve covered here. First, and most importantly, remember that you can learn to program, just like the many students who have done it before you. However, it can be difficult and frustrating at times, and it will take lots of hard work on your part to make it happen. That means that you’ll need to read and write a lot of code before it really starts to make sense. In short, you must do the work to learn to program.

That said, you can help make the process easier by getting good sleep, exercising regularly, and engaging fully with all of the content in the course. That means you’ll need to take your own notes, maybe draw some diagrams, and annotate code you write and code you read to help you understand it. While you are working, try not to multitask so you can focus. If you are given some code to include in your program, don’t copy/paste it - rewrite it, and make sure you completely understand what each line does. Finally, take some time to read code written by others! GitHub is a great place to discover all sorts of code and see how others write code. If you want to write good poetry you have to read lots of good poetry, and the same goes for coding.

With that in mind, I hope you are able to make the best of this course and continue to develop your programming skills. If you are interested in this topic and would like to know more about things you can do to be a better learner, let us know! As you can imagine, teachers like me love to talk about this stuff, so don’t be afraid to ask. Good luck!

Subsections of How to Learn Programming

CC 210 Syllabus - Spring 2023

CC 210 - Fundamental Computer Programming Concepts

Previous Versions

Instructor Contact Information

  • Instructor: Emily Alfs-Votipka (emilyalfs AT ksu DOT edu)
    I use she/her pronouns. Feel free to share your own pronouns with me, and I’ll do my best to use them!
  • Office: DUE 2161

Preferred Methods of Communication:

  • Email: Please use “cc210-help” (cc210-help@ksuemailprod.onmicrosoft.com if not on web-mail) for all communication regarding these courses as it allows instructors and TAs to provide a clear and detailed response, as well as easily store and record communication for reference later. You should receive a response within one business day, and hopefully much sooner. Note emailing the instructor or teaching assistants directly may result in longer wait times for your support.

Teaching Assistants and Office Hours

All TA office hours will be held in DUE 1118A and have the ability to join virtually: https://officehours.cs.ksu.edu/ Email CC210-help for assitance with the queue

Name Office Hours
Sumaira Ghazal Tue/Thur 9:30 - 11:30
Sai Teja Erukude Tue/Thur 11:30-1:30

How to Get Help in this Course

You are encouraged to seek help whenever you feel you are being overwhelmed or don’t understand a topic. You are not alone! The instructors and TAs are always willing to help students with any questions you may have about the class or other issues related to Computing Science. So please, don’t be afraid to ask questions. Get help early and often!

Here are the 4 recommended ways to get help on CC 210:

  • Review the course materials posted on K-State Canvas and the course website
  • Send assignment questions to the CC 210 Help email (cc210-help@ksuemailprod.onmicrosoft.com)
  • Visit your professor’s office hours, or the office hours for your TA if available
  • Schedule a one-on-one meeting with your professor/TA

Prerequisites

  • C or better in CC 110 - Introduction to Computing (Prerequisite or Concurrent Enrollment with instructor permission)

Course Overview

Basic concepts in developing computer programs: program structure and syntax, primitive data types, variables, control flow, iteration, simple algorithms, debugging, and good software development practices. Introduction to object-oriented programming.

Course Description

The course introduces students to computer programming using one of several programming languages. Interactive lessons and engaging projects reinforce new skills and concepts while relating programming fundamentals to the real world. This course covers the basic concepts of programming, from variables and control flow to functions, objects, and simple algorithms.

Learning Objectives

In either Java or Python (J or P), successful students should be able to:

  1. Evaluate data requirements to create variables, use operators and call/create functions for: strings, integers, real numbers and Boolean values.
  2. Understand the creation and use of mono-typed Lists (P) or Arrays (J) and their common built in methods and attributes.
  3. Analyze and adapt string methods to split, join and extract sub-strings to solve problems.
  4. Understand how code written by them may throw exceptions
    1. Understand how to create new exceptions
    2. Understand and adapt exception handling structures
  5. Understand how to create programs that read-from and write-to text files.
  6. Analyze and create conditional statement to control program execution
  7. Analyze and create loops to control program execution
  8. Analyze and adapt methods/function to control program execution
    1. Remember to consider separation of concerns when creating methods
  9. Understand how to create instance-based classes to include
    1. public/private access of components
    2. attributes, properties and methods
    3. inheritance
  10. Understand how to adapt Boolean equations to common natural language problem statements
  11. Understand how to adapt class APIs to incorporate objects in solutions
  12. Analyze medium-to-low-level designs expressed as text-based program requirements to create programs including: UML Class diagrams, flow charts and pseudo code
  13. Create terminal or console based programs

Major Course Topics

  • Programming Basics
  • Primitive Data Types
  • Boolean Logic and Boolean Algebra
  • Conditional Statements
  • Loops
  • Arrays / Lists
  • Strings, String Parsing, and String Formatting
  • Exception Handling and Debugging
  • Console and File I/O
  • Methods, Arguments and Parameters
  • Classes and Objects
  • Object-Oriented Programming
  • Model-View-Controller Architecture
  • Inheritance and Polymorphism
  • Standard Library/Module Collections and generic types

Course Structure

This course is intended to be taught 100% online, each module is self-paced, and each module must be completed to progress to the next one. Students are expected to make good progress; we have found students who fall behind often fail to successfully complete the class. In general, one or more modules are assigned each week. There are 2 weeks where no new module is assigned. This is a strong indication that the previous week’s module takes a lot of time (modules 7, 10 and 12). Modules will contain recorded videos, online tutorials, text and links to online resources. Each module will include a coding project or assignment, many of which will be graded automatically through Codio. You will be asked to pick a language by the end of the first week (Java or Python) at which point you will be invited to a language specific Canvas course. All content is accessed through this second Canvas course.

Grading

Each student starts with 0 points in the gradebook and works upward toward a final point total earned out of the possible number of points. In this course, each assignment constitutes a portion of the final grade, as detailed below: 70% - Codio Programming Projects 30% - Codio Tutorials and Canvas Quizzes 5% - Extra Credit: Bug Bounty

Letter grades will be assigned following the standard scale:

  • 90% - 100% → A
  • 80% - 89.99% → B
  • 70% - 79.99% → C
  • 60% - 69.99% → D
  • 00% - 59.99% → F

Late Work

Warning

Read this late work policy very carefully! If you are unsure how to interpret it, please contact the instructors via the help email. Not understanding the policy does not mean that it won’t apply to you!

Since this course is entirely online, students may work at any time and at their own pace through the modules. However, to keep everyone on track, there will be approximately one module due each week. Each graded item in the module will have a specific due date specified. Any assignment submitted late will have that assignment’s grade reduced by 10% of the total possible points on that project for each day it is late. This penalty will be assessed automatically in the Canvas gradebook.

Even if a module is not submitted on time, it must still be completed before a student is allowed to begin the next module. So, students should take care not to get too far behind, as it may be very difficult to catch up.

Finally, all course work must be submitted on or before the last day of the semester in which the student is enrolled in the course in order for it to be graded on time.

If you have extenuating circumstances, please discuss them with the instructor as soon as they arise so other arrangements can be made. If you find that you are getting behind in the class, you are encouraged to speak to the instructor for options to make up missed work.

Incomplete Policy

Students should strive to complete this course in its entirety before the end of the semester in which they are enrolled. However, since retaking the course would be costly and repetitive for students, we would like to give students a chance to succeed with a little help rather than immediately fail students who are struggling.

If you are unable to complete the course in a timely manner, please contact the instructor to discuss an incomplete grade. Incomplete grades are given solely at the instructor’s discretion. See the official K-State Grading Policy for more information. In general, poor time management alone is not a sufficient reason for an incomplete grade.

Unless otherwise noted in writing on a signed Incomplete Agreement Form, the following stipulations apply to any incomplete grades given in Computational Core courses:

  1. Students may receive at most two incompletes in Computational Core courses throughout their time in the program
  2. Students will be given 6 calendar weeks from the end of the enrolled semester’s finals week to complete the course
  3. Any modules in a future CC course which depend on incomplete work will not be accessible until the previous course is finished
  4. For example, if a student is given an incomplete in CC 210, then all modules in CC 310 will be inaccessible until CC 210 is complete
  5. Students understand that access to instructor and GTA assistance may be limited after the end of an academic semester due to holidays and other obligations
  6. If a student fails to resolve an incomplete grade after 6 weeks, they will be assigned an ‘F’ in the course. In addition, they will be dropped from any other Computational Core courses which require the failed course as a prerequisite or corequisite.

To participate in this course, students must have access to a modern web browser and broadband internet connection. All course materials will be provided via Canvas and Codio. Modules may also contain links to external resources for additional information, such as programming language documentation.

Subject to Change

The details in this syllabus are not set in stone. Due to the flexible nature of this class, adjustments may need to be made as the semester progresses, though they will be kept to a minimum. If any changes occur, the changes will be posted on the Canvas page for this course and emailed to all students.

Academic Honesty

Kansas State University has an Honor and Integrity System based on personal integrity, which is presumed to be sufficient assurance that, in academic matters, one’s work is performed honestly and without unauthorized assistance. Undergraduate and graduate students, by registration, acknowledge the jurisdiction of the Honor and Integrity System. The policies and procedures of the Honor and Integrity System apply to all full and part-time students enrolled in undergraduate and graduate courses on-campus, off-campus, and via distance learning. A component vital to the Honor and Integrity System is the inclusion of the Honor Pledge which applies to all assignments, examinations, or other course work undertaken by students. The Honor Pledge is implied, whether or not it is stated: “On my honor, as a student, I have neither given nor received unauthorized aid on this academic work.” A grade of XF can result from a breach of academic honesty. The F indicates failure in the course; the X indicates the reason is an Honor Pledge violation.

For this course, a violation of the Honor Pledge will result in sanctions such as a 0 on the assignment or an XF in the course, depending on severity. Actively seeking unauthorized aid, such as posting lab assignments on sites such as Chegg or StackOverflow, or asking another person to complete your work, even if unsuccessful, will result in an immediate XF in the course.

Use of AI text and code generators such as ChatGPT and GitHub Copilot in any submission for this course is strictly forbidden unless explicitly allowed by your instructor. Any unauthorized use of these tools is considered plagiarism.

We reserve the right to use various platforms that can perform automatic plagiarism detection by tracking changes made to files and comparing submitted projects against other students’ submissions and known solutions. That information may be used to determine if plagiarism has taken place.

Warning

The copying and pasting of code is not allowed. All coding must be done in the Codio IDE. If you paste any code into Codio (other than code which is explicitly given as starter code in the course) a zero will be given. If the violation occurs a second time, an XF will be given for the course.

Authorized Aid

All graded work is individual effort. You are authorized to use:

  1.  course’s materials,
    
  2.  direct web-links from this course
    
  3.  the appropriate languages documentation (https://docs.python.org/3/  or https://docs.oracle.com/javase/ Links to an external site.)
    
  4.  Email help received through  210 help email, CC - Instructors, GTAs
    
  5.  Zoom/In-person help received from Instructors or GTA
    
  6.  ACM help session (an on campus only resource) Most Tuesdays in EH 1116, 6:30PM. 
    
  7.  Tutors from the Academic Assistance Center or provided by K-State Athletics 
    

Use of on-line solutions whether for reference or code is prohibited. Use of previous semester’s answers, whether your own or another student’s is prohibited. Use of code-completion/suggestion tool’s, other than those we have installed in the Codio editor, is prohibited.

Standard Syllabus Statements

Info

The statements below are standard syllabus statements from K-State and our program. The latest versions are available online here.

Students with Disabilities

At K-State it is important that every student has access to course content and the means to demonstrate course mastery. Students with disabilities may benefit from services including accommodations provided by the Student Access Center. Disabilities can include physical, learning, executive functions, and mental health. You may register at the Student Access Center or to learn more contact:

Students already registered with the Student Access Center please request your Letters of Accommodation early in the semester to provide adequate time to arrange your approved academic accommodations. Once SAC approves your Letter of Accommodation it will be e-mailed to you, and your instructor(s) for this course. Please follow up with your instructor to discuss how best to implement the approved accommodations.

Expectations for Conduct

All student activities in the University, including this course, are governed by the Student Judicial Conduct Code as outlined in the Student Governing Association By Laws, Article V, Section 3, number 2. Students who engage in behavior that disrupts the learning environment may be asked to leave the class.

Mutual Respect and Inclusion in K-State Teaching & Learning Spaces

At K-State, faculty and staff are committed to creating and maintaining an inclusive and supportive learning environment for students from diverse backgrounds and perspectives. K-State courses, labs, and other virtual and physical learning spaces promote equitable opportunity to learn, participate, contribute, and succeed, regardless of age, race, color, ethnicity, nationality, genetic information, ancestry, disability, socioeconomic status, military or veteran status, immigration status, Indigenous identity, gender identity, gender expression, sexuality, religion, culture, as well as other social identities.

Faculty and staff are committed to promoting equity and believe the success of an inclusive learning environment relies on the participation, support, and understanding of all students. Students are encouraged to share their views and lived experiences as they relate to the course or their course experience, while recognizing they are doing so in a learning environment in which all are expected to engage with respect to honor the rights, safety, and dignity of others in keeping with the K-State Principles of Community.

If you feel uncomfortable because of comments or behavior encountered in this class, you may bring it to the attention of your instructor, advisors, and/or mentors. If you have questions about how to proceed with a confidential process to resolve concerns, please contact the Student Ombudsperson Office. Violations of the student code of conduct can be reported using the Code of Conduct Reporting Form. You can also report discrimination, harassment or sexual harassment, if needed.

Netiquette

Info

This is our personal policy and not a required syllabus statement from K-State. It has been adapted from this statement from K-State Global Campus, and theRecurse Center Manual. We have adapted their ideas to fit this course.

Online communication is inherently different than in-person communication. When speaking in person, many times we can take advantage of the context and body language of the person speaking to better understand what the speaker means, not just what is said. This information is not present when communicating online, so we must be much more careful about what we say and how we say it in order to get our meaning across.

Here are a few general rules to help us all communicate online in this course, especially while using tools such as Canvas or Discord:

  • Use a clear and meaningful subject line to announce your topic. Subject lines such as “Question” or “Problem” are not helpful. Subjects such as “Logic Question in Project 5, Part 1 in Java” or “Unexpected Exception when Opening Text File in Python” give plenty of information about your topic.
  • Use only one topic per message. If you have multiple topics, post multiple messages so each one can be discussed independently.
  • Be thorough, concise, and to the point. Ideally, each message should be a page or less.
  • Include exact error messages, code snippets, or screenshots, as well as any previous steps taken to fix the problem. It is much easier to solve a problem when the exact error message or screenshot is provided. If we know what you’ve tried so far, we can get to the root cause of the issue more quickly.
  • Consider carefully what you write before you post it. Once a message is posted, it becomes part of the permanent record of the course and can easily be found by others.
  • If you are lost, don’t know an answer, or don’t understand something, speak up! Email and Canvas both allow you to send a message privately to the instructors, so other students won’t see that you asked a question. Don’t be afraid to ask questions anytime, as you can choose to do so without any fear of being identified by your fellow students.
  • Class discussions are confidential. Do not share information from the course with anyone outside of the course without explicit permission.
  • Do not quote entire message chains; only include the relevant parts. When replying to a previous message, only quote the relevant lines in your response.
  • Do not use all caps. It makes it look like you are shouting. Use appropriate text markup (bold, italics, etc.) to highlight a point if needed.
  • No feigning surprise. If someone asks a question, saying things like “I can’t believe you don’t know that!” are not helpful, and only serve to make that person feel bad.
  • No “well-actually’s.” If someone makes a statement that is not entirely correct, resist the urge to offer a “well, actually…” correction, especially if it is not relevant to the discussion. If you can help solve their problem, feel free to provide correct information, but don’t post a correction just for the sake of being correct.
  • Do not correct someone’s grammar or spelling. Again, it is not helpful, and only serves to make that person feel bad. If there is a genuine mistake that may affect the meaning of the post, please contact the person privately or let the instructors know privately so it can be resolved.
  • Avoid subtle -isms and microaggressions. Avoid comments that could make others feel uncomfortable based on their personal identity. See the syllabus section on Diversity and Inclusion above for more information on this topic. If a comment makes you uncomfortable, please contact the instructor.
  • Avoid sarcasm, flaming, advertisements, lingo, trolling, doxxing, and other bad online habits. They have no place in an academic environment. Tasteful humor is fine, but sarcasm can be misunderstood.

As a participant in course discussions, you should also strive to honor the diversity of your classmates by adhering to the K-State Principles of Community.

Discrimination, Harassment, and Sexual Harassment

Kansas State University is committed to maintaining academic, housing, and work environments that are free of discrimination, harassment, and sexual harassment. Instructors support the University’s commitment by creating a safe learning environment during this course, free of conduct that would interfere with your academic opportunities. Instructors also have a duty to report any behavior they become aware of that potentially violates the University’s policy prohibiting discrimination, harassment, and sexual harassment, as outlined by PPM 3010.

If a student is subjected to discrimination, harassment, or sexual harassment, they are encouraged to make a non-confidential report to the University’s Office for Institutional Equity (OIE) using the online reporting form. Incident disclosure is not required to receive resources at K-State. Reports that include domestic and dating violence, sexual assault, or stalking, should be considered for reporting by the complainant to the Kansas State University Police Department or the Riley County Police Department. Reports made to law enforcement are separate from reports made to OIE. A complainant can choose to report to one or both entities. Confidential support and advocacy can be found with the K-State Center for Advocacy, Response, and Education (CARE). Confidential mental health services can be found with Lafene Counseling and Psychological Services (CAPS). Academic support can be found with the Office of Student Life (OSL). OSL is a non-confidential resource. OIE also provides a comprehensive list of resources on their website. If you have questions about non-confidential and confidential resources, please contact OIE at equity@ksu.edu or (785) 532–6220.

Academic Freedom Statement

Kansas State University is a community of students, faculty, and staff who work together to discover new knowledge, create new ideas, and share the results of their scholarly inquiry with the wider public. Although new ideas or research results may be controversial or challenge established views, the health and growth of any society requires frank intellectual exchange. Academic freedom protects this type of free exchange and is thus essential to any university’s mission.

Moreover, academic freedom supports collaborative work in the pursuit of truth and the dissemination of knowledge in an environment of inquiry, respectful debate, and professionalism. Academic freedom is not limited to the classroom or to scientific and scholarly research, but extends to the life of the university as well as to larger social and political questions. It is the right and responsibility of the university community to engage with such issues.

Campus Safety

Kansas State University is committed to providing a safe teaching and learning environment for student and faculty members. In order to enhance your safety in the unlikely case of a campus emergency make sure that you know where and how to quickly exit your classroom and how to follow any emergency directives. Current Campus Emergency Information is available at the University’s Advisory webpage.

Student Resources

K-State has many resources to help contribute to student success. These resources include accommodations for academics, paying for college, student life, health and safety, and others. Check out the Student Guide to Help and Resources: One Stop Shop for more information.

Student Academic Creations

Student academic creations are subject to Kansas State University and Kansas Board of Regents Intellectual Property Policies. For courses in which students will be creating intellectual property, the K-State policy can be found at University Handbook, Appendix R: Intellectual Property Policy and Institutional Procedures (part I.E.). These policies address ownership and use of student academic creations.

Mental Health

Your mental health and good relationships are vital to your overall well-being. Symptoms of mental health issues may include excessive sadness or worry, thoughts of death or self-harm, inability to concentrate, lack of motivation, or substance abuse. Although problems can occur anytime for anyone, you should pay extra attention to your mental health if you are feeling academic or financial stress, discrimination, or have experienced a traumatic event, such as loss of a friend or family member, sexual assault or other physical or emotional abuse.

If you are struggling with these issues, do not wait to seek assistance.

For Kansas State Salina Campus:

For Global Campus/K-State Online:

  • K-State Online students have free access to mental health counseling with My SSP - 24/7 support via chat and phone.
  • The Office of Student Life can direct you to additional resources.

University Excused Absences

K-State has a University Excused Absence policy (Section F62). Class absence(s) will be handled between the instructor and the student unless there are other university offices involved. For university excused absences, instructors shall provide the student the opportunity to make up missed assignments, activities, and/or attendance specific points that contribute to the course grade, unless they decide to excuse those missed assignments from the student’s course grade. Please see the policy for a complete list of university excused absences and how to obtain one. Students are encouraged to contact their instructor regarding their absences.

© The materials in this online course fall under the protection of all intellectual property, copyright and trademark laws of the U.S. The digital materials included here come with the legal permissions and releases of the copyright holders. These course materials should be used for educational purposes only; the contents should not be distributed electronically or otherwise beyond the confines of this online course. The URLs listed here do not suggest endorsement of either the site owners or the contents found at the sites. Likewise, mentioned brands (products and services) do not suggest endorsement. Students own copyright to what they create.

Subsections of CC 210 Syllabus - Spring 2023

Plagiarism Policy

YouTube Video

Resources

Video Script

“On my honor, as a student, I have neither given nor received unauthorized aid on this academic work.” - K-State Honor Pledge

Plagiarism is a very serious concern in this course, and something that we do not take lightly. Computer programs and code are especially easy targets for plagiarism due to how easy it is to copy and manipulate code in such a way that it is unrecognizable as the original source but still performs correctly.

At its core, plagiarism is taking someone else’s work and passing it off as your own without giving appropriate credit to the original source. As a student at K-State, you are bound by the K-State Honor Code not to accept any unauthorized aid, and this includes plagiarized code.

When it comes to plagiarism in computer code, there is a fine line between using resources appropriately and copying code. In this program, you should strive to avoid plagiarism issues by doing the following:

  1. Do not search for or use any complete solutions to projects in this course found online or from fellow students.
  2. Small portions of code may be used or adapted from an online source with proper citation. To cite a piece of code, include a code comment section above it that contains the original source URL and a description of why it was used.

In general, copying or adapting small pieces of code to perform auxiliary functions in the assignment is permitted. Copying or adapting code that is the general goal of the assignment should be avoided. For example, if the assignment is to create a bubble sort algorithm, you should write the algorithm from scratch yourself since that is the goal of the assignment. If the assignment is to create a program for displaying data that you feel should be sorted, you may choose to adapt an existing sorting algorithm for your needs (or use one from a library).

If you aren’t sure about whether it is OK to use an online resource or piece of code in this course, please contact the instructors using the course discussion forums or help email address. You will not get in trouble for asking, and it will help you determine what the best course of action is. Plagiarism can really only occur when you submit the assignment for grading, so you are welcome to ask for clarification or a judgement on whether a particular usage is acceptable at any time before you submit the assignment.

Codio has features that will compare your submissions against those of your fellow students. Any submissions with a high degree of similarity may be subjected to additional scrutiny by the instructors to determine if plagiarism has occurred.

In this course, any violation of the K-State Honor Code will result in a 0 on that assignment and a report made to the K-State Honor Council. A second violation will result in an XF in this course, as well as any additional sanctions imposed by the K-State Honor Council.

For more information on the K-State Honor & Integrity system, please visit their website, which is linked in the resources section below this video.

Chapter 0

Codio Introduction

A Quick Introduction to Codio

Web Only

This content is presented in the course directly through Codio. Any references to interactive portions are only relevant for that interface. This content is included here as reference only.

Subsections of Codio Introduction

Welcome to Codio

Welcome to Codio! For this class, we’ll be using Codio for most of our work. You will access Codio via the links provided in your class materials.

Each module will contain Codio tutorials, Codio projects and occasionally a quiz or discussion.

Click the Next button below, or the Right Arrow at the top of this page, to continue to the next guide page in this Codio project.

Codio Tutorials

Each module in Canvas will usually contain two Codio tutorial assignments. Some weeks may have more; in any event module content must be accomplished in order.

  • The first tutorial is a programming language agnostic discussion of the concept the module introduces. Often this tutorial introduces pseudo code, has questions and may contain a Parsons Puzzle. Most of the questions only allow one attempt, however Parsons Puzzles will allow multiple tries.
  • The second tutorial will contain your language specific implementation of this concept; the syntax (format) and semantics (behavior/meaning) of the specific keywords. This tutorial may include a programming example as well as a programming exercise.

In these Codio tutorials, there will be several pages of content introducing the material for that module. Some of the pages will look just like this one, with text, images, and maybe even a short video to help you learn the material.

If you’d like to see an outline of the pages available as part of this module, click the “hamburger” menu button at the top-right of the page.

Some of the pages may also include short questions to check for understanding of the material. You’ll need to answer these questions as they appear in order to get points for completing the tutorial module. Remember that the tutorials make up part of your grade in this course, so make sure you answer all of the questions in the tutorial module before submitting it. In some cases, you’ll be able to resubmit your answers until you get a correct answer, but other questions will not allow that.

In fact, below is a quick example of what one of those questions would be like. Take a moment to answer the question correctly, then continue to the next page of this module. For those of you unfamiliar with the work of Douglas Adams, the answer is forty-two.

Web Only

This content is presented in the course directly through Codio. Any references to interactive portions are only relevant for that interface. This content is included here as reference only.

Codio Examples

YouTube Video

On some pages, the Codio guide may also switch to a different view, shown here, allowing you to work directly with code. On the far left is the file tree, which shows all of the files accessible to you for this tutorial. Then, in the middle, you may also see one or more open files as tabs at the top of that panel. Those files are usually the ones that you need to edit to complete the example on this page. You can freely open additional files if needed in that panel, or rearrange the panels as needed. However, whenever you enter this page, it will reset the view back to the default.

In the first programming module of the course, we’ll discuss more information about how to use Codio to run any code that you’ve created. For now, we’ll just use text files to introduce the interface.

Once you’ve completed the example, most pages will include a section at the bottom that allows you to check your work. Just like the other questions, these assessments will count toward your grade on the tutorial project. See if you can complete the exercise and pass the test below. The answer is Picard.

Web Only

This content is presented in the course directly through Codio. Any references to interactive portions are only relevant for that interface. This content is included here as reference only.

Codio Interface

Now that you’ve seen a few pages in Codio, let’s take a minute to discuss some of the features of the Codio user interface. Of course, Codio has some amazing documentation, so feel free to check that out as you work with Codio.

First, let’s look at the menu items at the top of the page. There are several available to you that are worth mentioning. For starters, you can click the Codio Icon at any time to go directly to your Codio dashboard.

Under the Codio menu, you can also find options to manage your preferences. Here you can adjust things such as the editor settings and theme. Feel free to adjust the settings to match your personal preferences.

The Project menu allows you to work with the currently loaded Codio project. Generally you won’t need to access many of these items unless your project stops working. However, they are provided for your use in case you need them.

The File menu contains options for manipulating the file tree, such as creating new files, renaming them, saving them, and even downloading and uploading files. As you work on larger projects, you’ll be using many of these options to manage the files within your project.

Next, the Edit menu gives you access to the Undo and Redo action.

The Find menu contains entries for searching documents and performing a find-and-replace operation. Most of those actions should be pretty self-explanatory.

The View menu allows you to customize your view in Codio. Here we’ll find options for managing panels, open tabs, editor settings, and more. Feel free to make use of these options to arrange your Codio view as you prefer. Also, at the bottom of this menu is a Play Guide option, which is very helpful if you accidentally close the guide and need to reopen it.

Under the Tools menu, you’ll find an option for accessing the Terminal in your project. The Terminal gives you console access to the box that your project is running on.As you work through the content in this program, we’ll slowly introduce the Terminal and some of the tasks it can perform.

The Education menu is very important, though it only has a single entry. The Mark as Completed option allows you to indicate that you have completed this Codio project or tutorial. Once you select that option, your work will automatically be graded and your grade will be sent to Canvas. From there, you can access the next project or module in the course.

||| warning

Don’t Submit Projects Accidentally!

Be very careful when completing a project! Once you’ve marked a project as completed, it will become read-only, and you won’t be able to make any additional changes to the project. So, you’ll need to make sure you’ve finished everything in the project first. If you accidentally mark a project as completed, you may contact the instructors for help. Depending on the situation, they may be able to unlock it for you so you can continue your work. However, unlocking a completed project is entirely at the discretion of the instructor.

|||

Finally, the Help menu gives you access to many of the support features in Codio. If you get stuck, you may want to review some of the help options available here. Of course, you can always post a message in the course help forum or email for assistance! This is for help with Codio, not for help on the lesson content. In general your first request for help should be through the CC210-Help email.

There are also a few other items in the interface you should be aware of. First, in the File Tree, there is a Play icon that can also be used to open the guide for the current project.

In the guide, there are a couple of options available by clicking the gear icon in the upper-right of the page. First, there is an option to Restore Current Files. This option will restore the contents of any currently open files back to the default contents from when you first opened the project. In addition, this menu also contains another way to mark the current project as completed.

That covers most of the major features of the Codio interface that we’ll be using in this course. If you have any questions about how to use Codio, feel free to ask your teahers or email for assistance.

Important Notes

CC 210 Is 4 Credit Hours

The University guidance is you should spend 12 hours per week on a 4 credit hour course. We interpret this to mean twelve 50 minute sessions and assume you spend some time studying and reviewing class materials which is not captured in Codio. Historically, average students come close to this goal, with some weeks going over and the early weeks being low.

Weekly Effort Weekly Effort

Caveats:

  • We assume that when given two weeks for a project, the “effort” is split equally between both weeks.
  • Students learn different concepts at different rates – your effort may vary

Substantially more effort is required to be successful in CC 210 than in CC 110.

Modules 1 - 5 Are Mostly Review

Non Python Students

If you are taking CC 210 in a language other than Python, Modules 1 - 5 will quickly introduce you to the basic control statements and variable types in your selected language. The concepts will be familiar but the application may be a bit more advanced than that which was covered in CC 110.

Python Students

If you are taking CC 210 in Python, Modules 1 - 5 may seem like total review. However, we introduce syntactical and semantic options that, for simplicity’s sake, were omitted from CC 110.

Do not become complacent based on the first 3 weeks of course work. This course will become more difficult quickly.

CC 210 Projects vs CC 110 Homeworks and Labs

CUT and PASTE in PROJECTS is Forbidden

We want you to use the Codio editor for your Projects. It is deliberately feature poor to emphasize the student’s knowledge of the language, not the editor’s fancy assistance. See your syllabus, but in general a project which has materials copied/pasted in may receive a 0.

Note: Although cut and paste are permitted in TUTORIALs, plagiarism is not. DO NOT paste in someone else’s work.

“Check-It” Buttons may not be Comprehensive

You must develop and test your projects from the terminal. In CC 110, the student assessment button ran the same test software that the grader did; thus your score on the “Check-it” button was a true indication of your Codio grade.

Check It Output Check It Output

In CC 210, the “Check-it” button may be only a small subset the tests run for your Codio grade, so just because you pass those tests does not mean that your project is complete. This also does not guarantee you will receive a passing grade from the actual grader run after you submit your project. It is your job to test your code thoroughly in the terminal and develop your own test cases.

All Work is Subject to Manual review

Your submitted project may be reviewed manual for structure, forbidden commands, proper function from the terminal, etc. Please see your syllabus.

As a result your the score you receive from Codio may not reflect your final score. Here is the estimated points workflow:

  1. The Autograder assigns a grade in Codio and LMS software (e.g. Canvas).
  2. Manual review may deduct some points – see syllabus.
  3. Plagiarism detection is run and penalties applied.
  4. Late penalties may be applied.

Completing a Unit

That’s it! We’ve completed this unit in Codio, and we are now on the last page.

There’s just one more thing to do: we need to mark the unit as complete. When we do that, Codio will grade our work and then send the grade to Canvas. Once the grade is recorded in Canvas, we’ll get access to the next item in the module.

Take a Breather

Once you’ve marked a unit complete in Codio, it may take several minutes for the grading process to complete and for your grade to appear in Canvas. So, if you immediately try to access the next item in the module, you may not have access until Codio has submitted your grade to Canvas.

So, once you’ve marked a unit as complete, it’s a great time to take a quick breather, leave your computer behind, refill your beverage of choice, and clear your mind while Codio and Canvas handle the grading. By the time you get back, your grade should appear in Canvas and you’ll be ready to go.

If your grade doesn’t appear in Canvas after several minutes, please send a message to the instructors via your class’s help system.

There are several ways to mark a unit as complete. First and foremost, the last page of the guide in each project should have a “Mark as Completed” button at the bottom of the page, but these textbook tutorials don’t. So, once we see that button, we’ll know we’ve reached the end of a project.

On the tutorials, we can click the gear icon in the upper-right of the page, and select “Mark as Completed” there. It should also be available in the tutorials as well.

Finally, we can find a “Mark as Completed” option on the Education menu at the top of the window. Each of these will perform the same function, so we can use any one of them when we are finished with our work.

The Codio Documentation gives several different ways that it can be done.

Of course, don’t forget the warning on the previous page - we should make sure we are completely done with the unit before marking it as complete.

So, let’s go ahead and mark this unit as complete by clicking the “Mark as Completed” option found by clicking the gear icon above, or the Education menu at the top. Once we do that, we’ll be able to complete the final few things in Canvas for this module, and then we can move on to Module 1 - Hello World!

Chapter 1

Object-Oriented Programming

Representing Real-World Objects in Code

Subsections of Object-Oriented Programming

What is Programming?

As with any learning adventure, we must begin somewhere. When learning how to write computer programs, one of the best questions to tackle first is “what is programming?” As it turns out, the answer to that question is key to understanding exactly what it is we are trying to learn.

A Simple Computer

YouTube Video

Video Materials

At its core, a computer is simply an electronic device that is capable of following instructions to perform calculations. In computer science theory, there is a special kind of theoretical computer called a Turing Machine that represents the simplest version of a modern computer. It might look something like this, as imagined by an artist:

Turing Machine Turing Machine1

A Turing Machine consists of an infinitely long tape that can be used to store data, and a small control box that manipulates the tape. The control box knows how to perform a few simple instructions, such as “Move Left” or “Write 0.” So, to program a Turing Machine, we must simply tell the control box which instructions to follow, and it can do it. For example, if we want the Turing Machine to write “101” on the tape, we could write the following program:

  1. Write 1
  2. Move Right
  3. Write 0
  4. Move Right
  5. Write 1
  6. Stop

Seems simple enough. We won’t go into the details here, but computer scientists have been able to prove that any computer program that can run on a real computer could also be performed on a Turing Machine, as long as the Turing Machine has infinite time and an infinitely long tape.

This video shows an example of what a Turing Machine might look like in real life.

YouTube Video

So, all we really need to learn is how to write programs for a Turing Machine, right?

A Modern Computer

Desktop Computer Desktop Computer2

Well, it’s unfortunately not that simple. There are two major differences between a Turing Machine and a modern computer that we must deal with. First, a modern computer knows many more instructions than a Turing Machine. To learn how to write programs that a modern computer can understand, we’d have to learn an entirely different vocabulary of commands. At the same time, modern computers are very complex systems, so any program we write might not be very efficient at doing what we want.

So, to learn how to write computer programs quickly and easily, we really want to be able to do two things:

  1. Use a vocabulary of commands that are familiar to us
  2. Have those commands turn into programs that run efficiently on a modern computer

Compilers & Interpreters to the Rescue!

Rear Admiral Grace M. Hopper, USN Rear Admiral Grace M. Hopper, USN3

Developing computer programs was very difficult work in the 1950s, and many of those early programmers were looking for a better way to solve that exact problem. One of these was Rear Admiral Grace Hopper, shown above. Her team was one of the first to develop the idea of writing computer programs using English words, and then using a second program, which they called a compiler, to convert those English words into instructions a computer could understand.

Their compiler made developing computer programs much simpler, since programmers didn’t have to learn an entirely new vocabulary to tell the computer what to do. Instead programmers simply had to learn the rules of what a computer could and couldn’t understand, and the syntax, or grammar rules, of how the compiler expected the program to be written. These new programming languages that use English words are referred to as high-level languages.

Programmers would now write the source code for the program in a high-level language, and then use a compiler to generate the machine code that the computer would actually run. In addition, since the compiler was a program itself, it could make sure the machine code it generated was as fast and efficient as possible, eliminating lots of hard work programmers would have to perform to tailor each program to fit the hardware it was going to run on.

Today, programming languages such as C, C++, and Java use compilers to convert source code into machine code.

Steve Russell Steve Russell4

At the same time, other developers such as Steve Russell, shown above, were working on another type of program, called an interpreter, to solve the same problem. An interpreter can read source code and immediately tell the computer what steps to perform, without needing to generate the whole machine code first. This makes it much easier to write and edit programs on the fly, as the interpreter reads the source code directly each time the program runs. Today, programming languages such as PHP, JavaScript, and Python use interpreters to run the source code on a computer.

Programming

So, programming is simply the act of writing computer code in a way that a computer can run it. In most cases today, that means developing the source code for a program in a high-level language, then using either a compiler to generate the machine code for that program, or an interpreter to run the program directly on the computer from the source code. Of course, we can always write machine code by hand, but that is quite a bit more difficult.

In this class, we’ll learn how to write source code in one of two common languages, Java and Python. They both have their own unique features, especially since Java is a compiled language and Python is an interpreted language. However, as we saw above with the Turing Machine example, each language can be used to write any computer program. So, the choice of language is really more about personal preference and the unique features of each language than anything else.

This makes sense, because in general we can use both English and Spanish, as well as most other languages today, to express the same thoughts and ideas, even if we may not always have a word with the same meaning in both languages.


  1. File:Maquina.png. (2014, March 4). Wikimedia Commons, the free media repository. Retrieved 15:31, December 10, 2018 from https://commons.wikimedia.org/w/index.php?title=File:Maquina.png&oldid=118120539 ↩︎

  2. File:Desktop computer clipart - Yellow theme.svg. (2018, July 11). Wikimedia Commons, the free media repository. Retrieved 15:44, December 10, 2018 from https://commons.wikimedia.org/w/index.php?title=File:Desktop_computer_clipart_-_Yellow_theme.svg&oldid=310624404 ↩︎

  3. File:Commodore Grace M. Hopper, USN (covered).jpg. (2018, July 21). Wikimedia Commons, the free media repository. Retrieved 15:51, December 10, 2018 from https://commons.wikimedia.org/w/index.php?title=File:Commodore_Grace_M._Hopper,_USN_(covered).jpg&oldid=311956355 ↩︎

  4. File:Steve Russell.jpg. (2017, December 28). Wikimedia Commons, the free media repository. Retrieved 16:05, December 10, 2018 from https://commons.wikimedia.org/w/index.php?title=File:Steve_Russell.jpg&oldid=274743269 ↩︎

Subsections of What is Programming?

Running a Program

Modern computers share many theoretical similarities with the Turing Machine, but in practice they are much more advanced. In this section, we’ll discuss how a modern computer is able to run programs we’ve written in a high-level programming language like Java or Python.

Programming Languages are Rosetta Stones

A programming language, along with the associated compiler or interpreter, is a complete set of tools to write and translate instructions written in that language into the binary code that a computer’s central processing unit (CPU) can natively execute.

Both Java and Python do this by producing an intermediate representation (byte code) then translating that to machine code. In Java’s case this byte code is saved as a class file, but usually the Python interpreter does not store the byte code.

Simplified View of Computer Execution Simplified View of Computer Execution

Computer central processing units (CPU) exclusively run machine code.

Learning to program requires some basic understanding of how a computer works. In the most basic sense, a computer has a large amount of memory connected to a central processing unit (CPU). The memory consists of many bytes of data, each containing eight binary values (e.g. 01001101).

The memory itself is split into various regions, with different regions storing the machine code instructions that make up a program and the data that the program is operating on.

Simplified View of Computer Architecture Simplified View of Computer Architecture

The CPU contains enough circuitry to interpret the instructions it is given and perform the requested mathematical and logical operations on the data provided.

Consider a single line of code in a program:

z = 5 + 7

In most languages, a line similar to this will instruct the computer add the values of 5 and 7 and save the value into a variable named z, which will be stored in some location in memory. In a machine language, this single line of code may be actually require different instructions to complete. The table below shows one possible interpretation of that line of code into machine language.

Instruction Meaning
LVal eax, 0x01A0 Load the value found at memory loc 0x01A0 into CPU register eax
This is where 5 is stored
LVal ebx, 0x01A8 Load the value found at memory loc 0x01A8 into CPU register ebx
This is where 7 is stored
Add eax, ebx Add the values in CPU registers eax and ebx together and store the result back into eax
Mov eax, 0x01B0 Move the value in CPU register eax to memory location 0x01B0
This is where the variable z is stored

Machine language is pretty ugly, but this is really how a CPU functions. Thankfully, we have many higher-level programming languages available, such as Java and Python, so we usually don’t have to write code directly in machine language.

Estimating Program State

From this, we can come up with a pretty powerful abstraction that represents how a computer works. In programming, an abstraction is a simplified model of a complex system, keeping only the most important details to make using the model easy.

Elements of Program State Elements of Program State

In our abstract model of a computer, we see that a program consists of an ordered list of instructions to be executed, and memory is a set of labelled boxes that can store any data our program requires. We also have a pointer that keeps track of which instruction the computer is currently executing. The list of instructions, the instruction pointer, and the memory combined all make up a program’s state when it is running on a computer. Tools such as Python Tutor or Java Tutor use a model very similar to this to help us understand how our programs actually work.

The Software Development Lifecycle

Another big idea in computer programming is the software development lifecycle. There are many different ways to approach developing a large computer program, but most of them can be divided into a few clear steps, as shown in this diagram.

Five Step Software Development Cycle Five Step Software Development Cycle

Requirements

The first step is the requirements analysis phase. In this phase, a software developer works to clearly describe and understand the problem to solve and the features that the solution should have. Throughout this course, the problem statement or programming project description will form the requirements for the program to be written.

Design

Next comes the design phase. In this phase, we describe the solution in terms of the actual structure of the code. In Object-oriented programming, this generally consist of designing the classes and the interactions between them which meet the requirements.

A class is a collection of data and the methods (procedures) by which data is accessed and manipulated. Classes are generally organized around things (the shopping cart from any online store is a classic example) or functions (communication with your computer’s network card is probably handled by a class). Similar to the requirements, the design of most of the projects in this course will be provided, but it is important to pay attention to those designs since later courses may leave the design up to the developer.

Code

Following the design phase is the development, or coding, phase. In this phase, actual code is written to match the requirements and design from the previous phases.

This course primarily focuses on this phase of the software development lifecycle. In this course, we’ll cover how to write code in classes and methods to perform various operations on data. We’ll also learn how to store and manipulate data in variables and data structures.

Testing During Development

Testing while coding is an important part of the coding process. Good programmers constantly test small parts of their code, both by running the code and by continually thinking about the code, any time they complete a task. When a program fails to run correctly, we say that it contains a “bug” and the process of finding and fixing those bugs is known as “debugging.”

There are many advantages to testing while coding:

  1. This allows the developer to develop their own test cases and have a better understanding of what the code is supposed to do and where any problems may be.
  2. Any bugs introduced or revealed by the most recent addition of code become much easer to find.

We recommend a “code a little, test a little” approach in this class. This helps us narrow down which parts of the code are working properly and which ones contain bugs to be removed. If we write too much code without testing any of it, it can be very difficult to isolate and fix the bugs.

Test

When the development phase is complete, we move on to the formal testing phase of development. In this phase, the software is formally tested to ensure that is matches the specifications and design, usually by specially trained test engineers who can report any bugs that are found back to the developers.

In this class, the autograder that we use serves as a formal test process. It isn’t perfect and won’t catch every bug, but it does a good job of helping you determine if your program meets the specifications for the project.

Deploy

Finally, there is the deployment phase. This is where the code is packaged and released to the users. In this course, this is analogous to turning in our project and marking it complete. At this point, it is very difficult to fix any bugs without making a new release of the software.

OOP Features

In this course, we will be teaching programming from the object-oriented perspective. Both Java and Python support this programming paradigm, and it is commonly used in industry today. However, it is easier to understand some of these concepts (such as classes and objects), after first covering some of the fundamental concepts.

Thus, the first few modules of this course will contains some pieces of starter code that we’ll use without really explaining much about how they work, but rest assured that we’ll cover those concepts at the correct time in the course. Likewise, many of the early programming assignments will include some frozen starter code that provides the structure, and the point of the assignment will be simply to fill in the middle portion of the program that contains the actual code.

Before we begin programming, let’s go over some basic terminology and rules for high-level programming languages.

Syntax

First, all high programming languages have a syntax, which is a pattern of words and symbols that all correct instructions in the language much follow. So, when we receive a syntax error from the compiler or interpreter, we should know that one or more lines of code in our program doesn’t follow the rules of the language. In a very real sense, this is the grammar that all correct programming lines will follow.

Natural languages also have a grammar, but most natural language listeners can understand improper grammar. The question “Which room is the dog in?” which contains the dreaded dangling preposition, will almost certainly be correctly interpreted as “in which room is the dog?” by most listeners.

Programming languages are not like this – the slightest violation of a grammar rule will result in code that most likely will not run. So, as developers we must learn to be very precise and exact in our code. This is a difficult skill to learn, but we’ll develop it over time with practice.

Semantic

Second, all programming instructions have a specific meaning, known as the semantics of the language. When one of our programs compiles and executes but produces an incorrect result, this is due to a semantic error (sometimes known as a logic error) in our code.

Unfortunately, the compiler or interpreter isn’t able to help us find any semantic errors, but we can find them through careful testing of our code.

Initial Vocabulary

Finally, all programming languages have various defined structures, which we can think of as the “parts of speech” in the programming language. This is similar to the concept of nouns, verbs, and adjectives in the English language.

This is a list of some of the most common parts in a programming language:

  • keywords - a word that has a special meaning. These words tell the program exactly what to do, and we cannot use these words as identifiers. For example, in both Java and Python the word class is a keyword. We cannot use a keyword for any purpose other than the one(s) intended by the programming language.
  • literal – text evaluated as itself, for example numbers (such as 6.626) are meant to represent themselves. Similarly, text literals are most often enclosed in double quotation marks (such as "wildcat"). Text literals are most commonly referred to as strings.
  • identifiers - any class, method, or variable name is considered an identifier. Each language will have both rules (syntax) about what can be identifiers and conventions (style guides) about how to construct them.
  • variable – a programmer-defined piece of data. Programmers name their variables with an identifier.
  • method - a piece of code that performs an action in our program. Methods are sometimes referred to as functions or subroutines as well, but we’ll use the term method. Programmers name their methods with an identifier.
  • class – a collection of variables and the methods used to manipulate that data. Classes lay at the heart of object-oriented programming and will be the focus of the last third of this course.
  • symbols - any non-alphanumeric character which has special meaning. Common ones include the math symbols (+ - / * =), brackets of all forms (() [] {}), and then some not so obvious ones ( . , ; " !).
Subroutine, Function, and Method

Properly, a subroutine is a generic term for a named code fragment that does something. Your programming language may have a subroutine named math.sqrt(x) that calculates the square root of x or one called print (something) that prints what ever the value of ‘something’ is to the screen. The print() subroutine may be many lines long, but you gain access to it by calling (using) its name.

The issue is different programming languages use different words to refer to different kinds of subroutines. Functions in C may mean a different thing than functions in Fortran, Java, Python, or mathematics.

We are not going to worry about the details, and will use method or occasionally function to refer to callable subroutines.

Program Structure

When writing code in an object-oriented language, many times we must use some additional syntax and structures in our programs that are required by the language, even though they aren’t strictly required for the program we are trying to develop.

In this section, we’ll briefly discuss some of those limitations. Later on in this course, we’ll come back to these concepts and explain them more fully, but for now we’ll provide some of this structure as starter code for the first few modules.

Everything Is In a Class

Classes are the blueprints of objects. In an object-oriented program, everything is inside a class definition. In this class, every file will contain exactly class definition or class-body.

// Java
public class ClassName {
    class-body
}
# Python
class ClassName:
    class-body

Code Block Delimiters

The class-definition is a “code-block.” Code-blocks signal a boundary to the compiler. This helps the compiler manage identifier names, memory and other things.

Code-blocks are delimited, or set apart, by various symbols. In Java, this is done with brackets { code block }. Spacing is often also used to make the code more readable, so that everything in a block lines up.

In Python, each code block is indented to the right. In this class we will use 4 spaces. When a line ends with a colon, Python expects the next line to be the start of a new code block (and therefore indented).

public class Foo{
    statement one
    statement two
}

public class Bar{
    statement_three
}
class Foo:
    statement_one
    statement_two

class Bar:
    statement_three

In the preceding examples, statement_one and statement_two are the code block for the class named Foo, and statement_three is part of the code block for the class named Bar.

Every Program Starts Somewhere

By tradition (and by rule in many languages), object-oriented programs start in a method called main().

// Java
public class ClassName {
    public static void main(String[] args){
        // method-body
    }
}
# Python
class ClassName:

    @classmethod
    def main(args):
        # method-body

For the first several modules, all of the code we need to write will be in the method body for the main() method. This area will be clearly marked in the starter code using a comment similar to WRITE YOUR CODE HERE.

Hello World

Tradition dictates that the first program in any introduction course is Hello World. Hello World simply prints the text “Hello World” to the screen.

Below is an example of an object-oriented version of this program in both Java and Python:

// Java
public class HelloWorld{
    public static void main(String[] args){
        System.out.println("Hello World");
    }
}
# Python
class HelloWorld:
    @classmethod
    def main():
        print("Hello World")


HelloWorld.main()

In each program, the actual work is done by the line containing the word print. In the rest of this module, we’ll explain the details behind the structure of this program.

Chapter 7.J

Java Hello World

Hello World in Java

Subsections of Java Hello World

History of Java

Java Logo Java Logo1

The Java programming language was originally developed by Sun Microsystems starting in the early 1990s as Oak, a new programming language designed to build upon the ideas of C++. Oak would be object-oriented and include a garbage collector, both things that were seen as weak points of the C++ language at that time. In addition, it would be designed to be portable across many different types of devices.

Eventually, the language was renamed to Java, and was originally used develop applications that could run on a website. These Java applets for the web were very popular in the late 1990s and early 2000s, but most of them have since been replaced by JavaScript code that can run directly in the web browser.

Java vs. JavaScript

While Java and JavaScript may share a common-sounding name, they are in fact completely unrelated languages. JavaScript was originally named Mocha and then LiveScript, but was rebranded to JavaScript in 1995. That move was widely regarded as a marketing ploy to take advantage of the fact that Java was the most popular new language at the time.

In fact, today JavaScript is just one of many implementations of a language standard known as ECMAScript. However, the name confusion still exists.

Since that time, Java has grown into its own fully-fledged programming language, and has indeed met its goal of being highly portable. Java today can run on most major computer operating systems through the use of the Java Runtime Environment (JRE) and Java Virtual Machine (JVM). In addition, the Android mobile operating system uses the Java programming language, and it has been used on many web servers and in consumer electronics.

For software developers, the Java Development Kit (JDK) provides easy access to all of the tools needed to develop programs using the Java language. Once a program is developed, the Java compiler converts the program to Java bytecode, which is similar to machine-code. That Java bytecode can then be run on any compatible platform using the JVM. This allows Java to achieve true portability. The only part of the system that must be specific to the computer’s hardware and operating system is the JVM, while Java bytecode can be used on any system with a compatible JVM installed.

Today, the OpenJDK project handles all development of the Java platform, and the language and all supporting code is free and open source.

If none of these features in the history of Java make any sense at this point, that’s OK! It’s difficult to describe the differences between programming languages without getting technical. However, that is a good thing for us, since nearly every programming language can be used to create the programs we’ll be writing in this course. The differences aren’t really important at this point!

Now that we’ve covered the basics, let’s get right down to it and create our first program in Java!

Resources


  1. File:Java_programming_language_logo.svg. (2018, May 26). Wikipedia. Retrieved 13:46, December 10, 2018 from https://en.wikipedia.org/w/index.php?title=File:Java_programming_language_logo.svg&oldid=872323259↩︎

Hello World

YouTube Video

Video Materials

According to tradition, the first computer program that we cover in any language is a simple program called “Hello World!” The entire goal of the program is to demonstrate what it takes to create our first program from scratch in the language and get to the point where we can print the message of our choice to the screen. While that sounds very simple, it is actually a pretty big first step toward learning how to write our own programs. Let’s get started!

Help!

If this is your first-time programming, it can be quite daunting to know where to get started. This guide will walk you through all the steps to create your first program. However, if you have any questions at all, don’t be afraid to seek help. It’s much easier to answer questions up front when they come up, instead of dealing with them down the road when you are truly lost.

Some Terminology

In any programming language, there is a bit of terminology that we should discuss before diving in. Here are a few terms we’ll want to be familiar with at this point:

  • keyword - in any programming language, a keyword is a word that has a special meaning. These words tell the program exactly what to do, and we cannot use these words as identifiers. Consult the Java Language Keywords list to determine which words are keywords in Java.
  • identifier - any class, method, or variable name is considered an identifier.
  • declaration - in a programming language, we use special lines to declare that something exists. In this example, we’ll see both a class declaration and a method declaration.
  • body - following a declaration, we typically find the body of the declared item. The body is enclosed by braces.
  • braces - in Java, we use braces or curly braces, denoted by { and }, to enclose blocks of code. We’ll use these to enclose both the class body and method body.
Declaration vs. Definition

Formally, a Declaration associates an identifier with a program language element.

public class Pet; 
// a declaration give the name only and
// tells the compiler that there will be a class called Pet
// Java will not support a class declaration this way

A Definition includes the complete information about the program element. So for a class, a definition includes its body.

public class Pet {
     String name = "rover";
     // a definition gives the content of the item; it is
     // like a declaration with all the info about the class
}

Some older languages (notably C) allowed things to be declared and used before they were defined. However, Java does not support this style of coding. As a result, Java documentation and developers tend to use “declaration” for both declaration and definition.

Open a File

To begin, we’ll write our code in a file name HelloWorld.java. If this lesson is being done in Codio or another learning environment, that file may already be open. If not, click on that file in the file tree to the far left to open it. Make sure that file is open for this example, since the file name must match in order for this process to work properly.

Also, we should make sure that the file is completely empty before moving on. If there is any text currently in that file, take the time to delete it now.

We can also do these same steps on a computer with the Java Development Kit installed. Simply create a new, blank text file named HelloWorld.java.

Create a Class

Java is an object-oriented programming language. We’ll discuss this more in detail in a later module, but for now we’ll just need to know that Java requires all of our code to be placed in a class. So, at the very top of our file, we’ll enter the following line:

public class HelloWorld

That line called a class declaration in Java. Let’s break that line down and discuss what each part means:

  1. public - this keyword is used to identify that the item after it should be publicly accessible to all other parts of the program. In a later module, we’ll talk about these keywords, called access modifiers, and the impact they have on our programs. For now, we’ll just use public whenever we need an access modifier, such as in a class declaration.
  2. class - this keyword says that we are declaring a new class.
  3. HelloWorld - this is an identifier that gives us the name of the class we are declaring. Java requires that the class name matches the filename that it is stored in, so we must store the HelloWorld class in a file named HelloWorld.java.

Every Java program must contain at least one class declaration. In fact, each Java file we create will contain at least on class declaration, so we’ll see this structure repeated very often.

Too Much Information?

Unfortunately, when learning to program in Java, there are a few things that we’ll just have to take for granted for now until we learn a bit more about how they actually work. Class declarations are a great example of this. For the next several modules, we’ll just have to include a class declaration at the top of each file without understanding everything about them. As long as we make sure the identifier matches the name of the file, it should work just fine. In fact, in most of the later examples in this book, we’ll have some sample code in each file that includes the class declaration.

We’re covering it in detail here just to make sure it is clear what is going on at first. It’s always better to have too much information than not enough.

Class Body

Once we’ve completed our class declaration, we need to move on to the class body. The class body is where all of the information about the class is stored. In Java, we use braces to enclose a block of code, such as a body. So, let’s modify our file to look like the following example by adding an opening brace { and a closing brace } with a few empty lines in between. Instead of copy-pasting it, try to type it in yourself and see what happens!

public class HelloWorld {
  
  
}

Did you notice that the text editor automatically added a closing brace right after you typed the opening brace? That’s the power of using a text editor that is tailored for programming. It should have also indented all of the lines between the two braces a bit, making it easier to read your code as we continue to fill it in. It may seem a bit jarring a first, but you’ll quickly get used to it. We’ll see it happen again later in this example.

Style Guide

To make your code easier to read, many textbooks and companies use a style guide that defines some of the formatting rules that you should follow in your source code. However, this is a point of contention, and many folks disagree over what is the best format. These formatting rules do not affect the actual code itself, only how easy it is to read.

For this book, most of the examples will be presented in a variant of the K&R Style used by most Java developers, which places the opening brace on the same line as the declaration, but the closing brace is placed on a line by itself and indented at the same level as the declaration. The code inside will be indented by four spaces.

Google provides a comprehensive style guide that is recommended reading if you’d like to learn more about how to format your source code.

Main Method

Inside of our class body, we must create a main method. A method is a piece of code that performs an action in our program. Methods are sometimes referred to as functions or subroutines as well, but we’ll use the term method. Each Java program have one class that contains at least one special method, called the main method, that tells the program where to start. So, let’s modify our file to the left to look like this example:

public class HelloWorld {
  
    public static void main(String[] args){
    
    
    }
  
}

We just added a method declaration and method body to our program! Let’s look at some of the keywords we used here:

  1. public - just like before, we are using the public access modifier to allow any part of our program to access this method.
  2. static - the static keyword is one of the more difficult to understand, and even some experienced programmers struggle with it. In this example, we must use static since this is the main method, which must always be a static method. This is because we aren’t using this method inside of an object, which we’ll cover in a much later module. For now, every method we create will use a static keyword.
  3. void - this describes what kind of data this method should output. Since this is the main method, it cannot output anything to another part of the program, so we use the special void keyword to denote the fact that it doesn’t output anything. (At least, it doesn’t output anything to the rest of the program, but it may display things on the screen!)
  4. main - this is another identifier that gives the name of the method. Since our program needs to have at least one main method, we need to use main as the name of this method.
  5. (String[] args) - following the method name is a section in parentheses that defines the inputs, or parameters, for the method. The main method must take in one parameter, an array of Strings. By convention, we use the identifier args as the name of that parameter. We’ll learn more about parameters, Strings, and arrays, in a later module. For now, we’ll just have to remember that the main method must have this exact set of parameters.

Lastly, we included a second set of braces to enclose the method body. Notice how everything in the class body is indented slightly, making it easy to see the structure of the code.

For now, we’ll just have to memorize the fact that the main method in Java is declared using public static void main(String[] args). As we move through this book, we’ll slowly learn more about each part of that line and what it does, and it will make much more sense.

Saying Hello

Finally, we can write our code. The actual code of our program goes inside the main method’s body, between the two braces. In the classic “Hello World!” example, we simply want to print the words “Hello World!” to the screen. So, let’s modify our program one last time to look like this example:

public class HelloWorld{
  
    public static void main(String[] args){
        System.out.println("Hello World");

    }

}

As you typed in that information, you might have noticed that the editor also added a second set of quotation marks ", just like it did with the braces earlier. This is another example of a programmer-friendly text editor at work!

Let’s review what we just added to our program:

  1. System.out.println - this line tells us that we’d like to use a method called println in the System.out PrintStream object. Again, that means very little to us right now, but for now we’ll need to know to use this method to print a line of text to the screen. Following the name of the method is a set of parentheses that accepts input to the method, which is what we’d like to have printed to the screen.
  2. "Hello World" - by putting this in the parentheses after System.out.println, we are telling the println method in System.out to print Hello World to the screen. We have to enclose it in quotation marks " so that our program will treat it as a line of text and not more Java code. The values, or variables passed to a method are referred to as the method’s parameters.
  3. ; - each line of code in Java must end with a semicolon ;. This helps the compiler determine where one line of code ends and another begins. They really serve the same purpose as the period . in written English. However, periods are already used for other purposes in Java, so the semicolon became the standard symbol for the end of each line of code.

That’s it! That’s all it takes to write our first program in Java. On the next page, we’ll learn how to actually compile and run this program.

Subsections of Hello World

Compile and Run

YouTube Video

Video Materials

Note: the video's "Run" menu reference is obsolete.

Now that we’ve written our first Java program, we must compile and run the program to see the fruits of our labors. There are many different ways to do so. We’ll discuss each of them in detail here.

Codio vs. Other IDE

This textbook was written for the Codio learning environment, so many of the steps below will reference features in Codio. However, most integrated development environments (IDEs) also include features to compile and run your code, and you can always do so manually using commands in the terminal for your operating system. If you aren’t sure how to get it to work, ask for help!

Terminal

Codio includes a built-in Linux terminal, which allows us to perform actions directly on a command-line interface just like we would on an actual computer running Linux. We can access the Terminal in many ways:

  1. Selecting the Tools menu, then choosing the Terminal option
  2. Pressing SHIFT + ALT + T in any Codio window (you can customize this shortcut in your Codio user preferences)
  3. Pressing the Open Terminal icon in the file tree

Additionally, some pages may already open a terminal window for us in the left-hand pane, as this page so helpfully does. As we can see, we’re never very far away from a terminal.

New to Linux?

No worries! We’ll give you everything you need to know to compile and run your Java programs in this course.

If you’d like to learn a bit more about the Linux terminal and some of the basic commands, feel free to check out this great video on YouTube:

YouTube Video

Let’s go to the terminal window and navigate to our program. When we first open the Terminal window, it should show us a prompt that looks somewhat like this one:

Initial Terminal View Initial Terminal View

There is quite a bit of information there, but we’re interested in the last little bit of the last line, where it says ~/workspace. That is the current directory, or folder, our terminal is looking at, also known as our working directory. We can always find the full location of our working directory by typing the pwd command, short for “Print Working Directory,” in the terminal. Let’s try it now!

Enter this command in the terminal:

pwd

and we should see output similar to this:

pwd Command Output pwd Command Output

In that output, we’ll see that the full path to our working directory is /home/codio/workspace. This is the default location for all of our content in Codio, and its where everything shown in the file tree to the far left is stored. When working in Codio, we’ll always want to store our work in this directory.

Next, let’s use the ls command, short for “LiSt,” to see a list of all of the items in that directory:

~/workspace$ ls
java  README.md

We should see a short list of items appear in the terminal.

We can use the cd command, short for “Change Directory,” to change the working directory. To change to the java directory, type cd into the terminal window, followed by the name of that directory:

~/workspace$ cd java
~/workspace/java$

We are now in the java directory, as we can see by observing the ~/workspace/java on the current line in the terminal. Finally, we can do the ls command again to see the files in that directory:

~/workspace/java$ ls
HelloWorld.java

We should see our HelloWorld.java file! If it doesn’t appear, try using this command to get to the correct directory: cd /home/codio/workspace/java.

Once we’re at the point where we can see the HelloWorld.java file, we can move on to actually compiling and running the program.

Compiling in Terminal

To compile a Java program in the terminal, we’ll use the javac command, short for Java Compiler, followed by the name of the Java file we’d like to compile. So, in our case, we’ll do the following:

javac HelloWorld.java

If it works correctly, we shouldn’t get any additional output. The compiler will look through our Java file and create a new file containing the Java bytecode for our program, called HelloWorld.class. We can use the ls command to see it:

ls

javac Command Output javac Command Output

Problems?

If the javac command gives you any output, or doesn’t create a HelloWorld.class file, that most likely means that your code has an error in it. Go back to the previous page and double-check that the contents of HelloWorld.java exactly match what is shown at the bottom of the page. You can also read the error message output by javac to determine what might be going wrong in your file.

We’ll cover information about simple debugging steps on the next page as well. If you get stuck, now is a great time request help via your course’s help system. You aren’t in this alone!

Running in Terminal

Finally, we can now run our program! Once it is compiled, just type the following in the terminal to run it:

java HelloWorld

java Command Output java Command Output

That’s all there is to it! We’ve now successfully compiled and run our first Java program. Of course, we can run the program as many times as we want by repeating the previous java command. If we make changes to the HelloWorld.java file, we’ll need to recompile it using the previous javac command first. Then, if those changes instruct the computer to do something different, we should see those changes when we run the program after compiling it.

Try It!

See if you can change the HelloWorld.java file to print out a different message. Once you’ve changed it, use the javac and java commands to compile and run the updated program. Make sure you see the correct output!

Codio Assessments

Last, but not least, many of the Codio tutorials and projects in this program will include assessments that we must solve by writing code. Codio can then automatically run the program and check for specific things, such as the correct output, in order to give us a grade. For most of these questions, we’ll be able to make changes to our code as many times as we’d like to get the correct answer.

Subsections of Compile and Run

Debugging

Of course, when developing a computer program, there are always times when things don’t quite work the way we’d like them to. Let’s review a few of the common errors and how to solve them.

Compiler Issues

The Java Compiler is usually the source of most of our woes when first learning how to write programs in Java. The compiler expects the source code to be correctly formatted, or else it won’t be able to generate the Java bytecode for our program. Let’s modify our HelloWorld.java file to include some errors, just to see exactly what the error messages from the compiler are like.

Missing Braces

First, let’s replace everything in HelloWorld.java with the following code:

public class HelloWorld{
  
  public static void main(String[] args){
    System.out.println("Hello World");
    
  
}
Spot the Error

Before compiling that program, can you spot the error? Being able to find errors in code without using a compiler will definitely help you develop programs much faster. It is just like being able to spell words correctly without using a spell-checker. It makes everything go just a bit more smoothly.

In each of the examples in this section, take a minute and see if you can spot the error before running it through the compiler. The quiz in this module includes a few questions that require you to spot the error in a piece of code, so this is great practice for later!

Once we’ve updated that file, we can compile it using the using the terminal. When we do, we should see output similar to this:

Java Missing Closing Brace Java Missing Closing Brace

That error message gives us quite a bit of information. The first part, java/HelloWorld.java tells us which file the error is in, which is handy later on when we start working in programs that include multiple source code files. Following that, we see a 7 after the file name, which tells us that the error is on or around line 7. However, that doesn’t always mean that we’ll need to edit something at line 7 to fix the error; it just means that the Java Compiler realized there was an error when it reached line 7. Sometimes, we must make a change elsewhere in the file to resolve the issue.

After that, we’ll see the actual error message, which in this case is error: reached end of file while parsing. That may not seem very helpful at first, but it actually gives us an important clue.

If we look at the code above, we’ll notice that it is missing the second closing brace } at the end of the file. So, the compiler was expecting to see one more closing brace, but didn’t find it. So, to fix that, we’ll just need to add one more closing brace at the end of the file, and it should work just fine.

Missing Semicolon

Here’s another example we can try:

public class HelloWorld{
  
  public static void main(String[] args){
    System.out.println("Hello World")
    
  }
}

When we try to compile this file, we should get output similar to the following:

Java Missing Semicolon Java Missing Semicolon

In this example, the output is really helpful. It clearly shows us exactly where in our code we forgot to include a semicolon. By adding that symbol where indicated, we can solve the problem.

Runtime Issues

Of course, there are some problems that the compiler may not catch. These are known as runtime errors, since they happen at the time we run our programs. They can be especially tricky to deal with, but thankfully they are usually quite rare.

Incorrect Main Method

Let’s look at one more example:

public class HelloWorld{
  
  public static void main(String args){
    System.out.println("Hello World");
    
  }
}

This time, the program actually compiles! However, when we try to actually run the file, we’ll get an error similar to this one:

Java Incorrect Main Method Java Incorrect Main Method

This is because we accidentally forgot the square brackets [] in the main method declaration. It should be public static void main(String[] args), as the error message so helpfully tells us. So, it is important to remember that our programs may still have errors, even if the compiler doesn’t find any.

Break Stuff

Now that you’ve learned a bit about how to debug compiler errors, let’s see if you can figure out how to cause one! Modify HelloWorld.java in a way that causes the compiler to output the following error message:

error: class WrongClass is public, should be declared in a file named WrongClass.java

Subsections of Debugging

Summary

We’ve now written our first program in a real, high-level programming language. While the program may only consist of a few lines of code at most, it is still a very big step toward writing bigger and more advanced programs.

Computer technology has some quite a long way since the 1950s, but the same needs that drove the development of compilers and interpreters continue today to drive the development of more advanced programming languages and related tools. It’s a very exciting field to experience firsthand, and once we understand a bit of code, we’ll be able to see it for ourselves.

In the next chapter, we’ll dive in head first to learn all about how to store and manipulate various types of data in our programs.

Chapter 2

Data Types & Math

Storing and Manipulating Basic Numerical Data

Subsections of Data Types & Math

The World as Data

Binary World Binary World1

Everything in the world can be represented using data. Think about that for a minute. Does it make sense?

Picture a tree. That tree can be described by its width, height, age, number of leaves, and even its color or texture. What about a person? A person has a name, age, height, and many other attributes. Could we simply represent trees, humans, and other entities in our computer programs using their attributes?

That’s the next big step in learning how to write a computer program: dealing with data. Many of the programs we’ll write are simply built to help us manipulate data in some fashion. Think back to the Turing Machine example from the first chapter. All a Turing Machine does is manipulate data on a tape, and it is capable of running any possible computer program.

In this chapter, we’ll learn all about the different ways we can work with data in our programs.

Variables

YouTube Video

Video Materials

First, let’s talk about variables. A variable is a placeholder for another value in a computer program. Most students first learn about variables in a mathematics course, such as Algebra. For example, in math, we might see the equation $ x + 5 = 10 $, where $ x $ is a variable, or placeholder, for a value. Using the algebraic rules, we can solve that equation to find that $ x = 5 $.

Variables in a computer program share some similarities with variables in math, but have some differences as well. First, variables in a computer program are also placeholders that represent a value. However, in programming, instead of representing an unknown value, variables in a computer program represent a stored value, one which we can always access.

Cardboard Box Labelled Height Cardboard Box Labelled Height1

One way to think about variables in a computer program is to imagine them as a cardboard box. When we declare a variable, we are making a new box and giving it a name, which is the identifier of the variable. In the picture above, we’ve declared a variable named height.

Then, we can store a value in that variable, using a statement such as height = 42, which will put the number 42 inside of the height variable. This is an assignment statement, which we’ll cover in detail in this chapter.

Later in our program, we can then access the value stored in that variable and use it for calculations or output. For example, if we have additional variables named width and area, and have already stored the value 10 in width using width = 10, we can calculate the area of rectangular object using a statement such as area = width * height. This would use the current values in the height and width variables, and then store the result in the variable named area.

Behind the scenes, each time we declare a variable, we are telling the computer to set aside a small piece of memory in which to store something. We give the variable a name when we declare it, and then we can use that name to store or retrieve data from the location in memory our computer gave us.

Using variables effectively is a essential to writing good computer programs. This chapter will introduce many of the concepts related to dealing with variables and data in our code.

Subsections of Variables

Basic Data Types

In a computer, all data, even the instructions, is stored in a binary format. Binary data consist entirely of 1s and 0s, or, in the case of a computer, it could be represented as “on” and “off” for current in a circuit, or “positive” and “negative” charges stored on a magnetic storage device. It could even be “open” and “closed” for physical memory gates. There are many different ways to represent it, but it all boils down to just 1s and 0s.

Of course, we need a way to convert between the binary 1s and 0s that a computer understands and the actual data we’d like to represent. On this page, we’ll cover some of the common types of data and how they are handled by most computer programming languages.

Binary

Working with binary data can be a complex topic. In this program, we won’t deal directly with binary data very often. If you’d like to learn more about the binary numerical system and how it works, check out the Wikipedia article on Binary Numbers.

You may also want to review the List of Types of Numbers article as well, since we’ll refer to several of those types.

Binary literals are often prepended with 0b thus 10 represents the decimal number 10, whereas 0b10 represents the binary number 10 and the decimal number 2. Recall that a literal represents the actual typed value.

Common Numerical data

Numbers, and thus numerical literals, are used everywhere in computer programs. This goes beyond obvious arithmetic and accounting applications. Modern graphics, artificial intelligence and simulation programs use numerical representations of data to quickly perform their operations. Thus, it is unsurprising that there are many ways to write and store numeric data.

Integers

The first type of data we’ll deal with in our computer program is whole numbers. These are numbers such as 1, 0, -1, 25, -186, 12852, and more. In both mathematics and programming, we refer to these numbers as integers. An integer is any whole number, or a real number without a fraction or decimal component. We sometimes refer to these numbers as counting numbers.

To store these numbers in our computer program, we typically use a signed integer data type. This data type allows us to store both positive and negative numbers in binary in our computer’s memory. We won’t go too far into the details of how that works in this course, but there is more information on how that works on Wikipedia

Floating Point

Next, we’ll also need to handle numbers that include a fraction or decimal component. These include rational numbers such as 1.5, -2.98, 3 1/3, and even irrational numbers such as $ e $ and $ \pi $.

Our computer uses a special representation known as floating point to store these numbers. Floating point is very similar to scientific notation, where a number such as 1,230,000 is represented as $ 1.23 * 10^{6} $. In this example, 1.23 is the significand and 6 is the exponent of the number. It is known as floating point because the decimal point “floats around” the number based on the exponent. We could represent the same number as $ 12.3 * 10^{5} $ or $ 0.123 * 10^{7} $ just by adjusting the exponent, causing the decimal point to move within the significand.

Modern computers use the IEEE 754 standard for encoding floating-point numbers into binary. Again, we won’t go into the specifics here, but the graphic below gives us a good idea of how a floating point number can be broken up and stored in binary.

Double Precision Floating Point Double Precision Floating Point1

Here we can see that a 64 bit space in memory is divided into three parts, one for the sign (to denote either a positive or negative number), another for the exponent, and a third for the fraction or significand of the number.

Floating Points are Approximations

Just like there are rational numbers (such as 1/3) which cannot be exactly represented in the base 10 decimal number system, there are numbers which cannot be exactly exactly represented in base 2 (the underlying binary system used by a computer). In fact, there are many more rational numbers that cannot be exactly represented by binary numbers using floating-point than there are in a base 10 system.

In addition to this, because floats have a finite (set) number of bits, there is a limit to the number of significant bits we can use (typically 16 for a 64-bit float). This is generally only a concern in scientific computing (where we are dealing with either very big or very small numbers). However, many programs use common graphic processing units, which use only 32-bits (7 significant digits) to speed up calculations. This much lower accuracy can cause problems with statistical simulations.

Boolean

Boolean values, named for George Boole represent true and false in a computer program. While it may be as simple as storing a single bit, with 0 and 1 representing true and false, most programming languages provide a special way to deal with these values as they are very important in our computer programs. We’ll spend most of the next several chapters discussing how to work with Boolean values, but for now they are just another type of data our program could store.

Text as Data

Character

Many high-level programming languages have a character data type. A character represents a single letter in a written language such as English. Most programming languages use a special code called ASCII, or the American Standard Code for Information Interchange. It defines a numerical value for each character in the English language, as well as several special characters such as the newline or \n character we’ve already seen. Below is a table showing the entire ASCII code.

ASCII Table ASCII Table2

So, to store the character c, our computer would store the number 99 in binary. We should also notice that the capital and lowercase letters are separate, so C is 67.

Modern computer programming languages also support the use of Unicode characters. We won’t cover that in this course, but it is important to remember later on when working with languages other than English.

String

Sometimes we want to store entire sentences in our computer programs. A sentence is just a String of characters, object-oriented languages use a string class for this purpose and we’ll cover everything we need to know about strings in a later chapter.


  1. File:IEEE 754 Double Floating Point Format.svg. (2015, January 21). Wikimedia Commons, the free media repository. Retrieved 20:50, December 18, 2018 from https://commons.wikimedia.org/w/index.php?title=File:IEEE_754_Double_Floating_Point_Format.svg&oldid=147276375↩︎

  2. File:ASCII-Table-wide.svg. (2018, March 6). Wikimedia Commons, the free media repository. Retrieved 21:51, December 18, 2018 from https://commons.wikimedia.org/w/index.php?title=File:ASCII-Table-wide.svg&oldid=291044172↩︎

Data Types in Detail

A variable in a high level language is just a memory address. It tells the compiler/interpreter where to start looking for the binary string of data.

Decoding the binary

All high-level programming languages use a type system to tell the system how to decode the binary data it finds there. All types have a size and a semantic which describes how many bits are in the data and how to interpret each bit. Consider the 2-byte (16-bit) binary sequence 1100 0110 1110 0100, it can be interpreted as many values based on its type.

TYPE value
16-bit floating point -6.891
16-bit unsigned integer 50916
16-bit 2’s complement integer -14620
8-bit ASCII Æ ä

Performing Operations

Operations and methods are only defined for specific types. For example, we can divide two integers, but not two characters. Likewise, integer division works differently than floating point division, which we’ll cover later in this chapter. It is up to the programmer to select the appropriate type for a variable and keep track of it.

Implications for Object-Oriented Programming (OOP)

All objects are created from a class definition. For example, an object created from a class named Cat is itself a new data type. A key part of object-oriented programming is that objects modify their own data. So, understanding the type of each variable is vital to understanding program state, which allows us to verify correct behavior and troubleshoot buggy code.

Typing Systems

Static (i.e. Java)

One typing system, often used by compiled languages, is the static type system. In this scheme the programmer explicitly tells the compiler the type of each variable when the is declared or defined. The statement int x = 5; tells the compiler that:

  1. it should reserve a memory address for an integer variable
  2. that the programmer is going to use the variable name x for that memory address
  3. it should always interpret the bits stored in x as an integer
  4. it should put the binary string which, when evaluated as an int is equal to the decimal number 5 into the reserved memory address

Dynamic (i.e. Python)

Another typing system, often used by interpreted languages, is the dynamic type system. In this system, the interpreter infers (guesses) the type of the variable based on the literal or variable in the definition. The statement x = 5 tells the interpreter:

  1. to infer a type based on the literal 5
  2. to reserve a memory address for the inferred type
  3. that the programmer is going to use the variable name x for that memory address
  4. to put the binary string which, when evaluated as the inferred type is equal to the decimal number 5 into the reserved memory address

Converting Between Types

High-level languages typically have operations or methods to convert from one data type to another.

For example, in Java, to print a line to the terminal, we use the System.out.println() method. As input, this method expects to receive a value that is a String data type. So, many languages include methods such as the toString() method built into all Java classes, as well as the str() method provided by Python, to convert most data types into a string. When a programmer explicitly tells the computer to convert one variable Type to another, the process is called casting.

Thankfully, many languages also allow some data conversions to happen automatically through a process known as coercion. In most modern high-level languages, when a method is supplied the wrong type of parameter, the language checks to see if there is an automatic conversion from the supplied type to the required type. If so, the language first converts the incorrect parameter to the correct type, then calls the method. The same procedure is used for operators. Depending on coercion for simple types is a common practice. Depending on it for more complex types or programmer developed classes can be more difficult and result in errors.

In practice, we generally should prefer explicitly casting variables to convert their types instead of relying on coercion. Casting ensures the program does exactly what we expect it to do.

Arithmetic

Once we have data stored in our programs, there are a number of ways we can manipulate that data. Let’s look at a few of them here.

Basic Operations

At its core, a computer is capable of performing all of the basic arithmetic operations, so we can write programs that can add, subtract, multiply and divide numbers. However, there is one big caveat that must be dealt with.

As discussed on the previous page, computer programs can store numbers as both integers and floating point numbers. What happens when we perform operations on two different types of numbers? For example, if an integer is added to a floating point number, would the resulting number be an integer or a floating point number, as in $ 1 + 1.5 $?

As our intuition suggests, performing the operation gives us $ 1 + 1.5 = 2.5 $, which is a floating point number. In general, when performing an operation on either two integers or two floating point numbers, the result will match the type of the two operands. However, when performing an operation on both an integer and a floating point number, the result is a floating point number.

There are two notable exceptions to this rule:

  1. In a strongly-typed programming language, such as Java, the result may be converted to match the type of the variable it is being stored in, if allowed by the language.
  2. When dividing two integers, the result may be either a floating point number or an integer, depending on the language.

We’ll discuss both of these exceptions later in this chapter when we dig into the details for each programming language.

New Operations

YouTube Video

Video Materials

In addition to the basic operations listed above that we are all familiar with from our mathematics class, there are a few new operations we should be aware of as well.

Modulo

The first new operation, the modulo operation, finds the remainder after dividing two numbers. It is typically written as $ 9 \bmod 5 $ when printed, but most programming languages use the percent sign %, as in 9 % 5 to refer to this operation.

To calculate the result of the modulo operation, we must look back to long division. In the example above, we can calculate $ 9 \bmod 5 $ by first calculating $ 9 / 5 $, which is $ 1 $ with a remainder of $ 4 $. So, the result of $ 9 \bmod 5 $ is $ 4 $.

Another way to think about the modulo operation is to simply find the largest multiple of the second operand that is smaller than the first, and then subtract the two, just like you do when performing long division. So, we can find $ 42 \bmod 13 $ by first calculating the largest multiple of $ 13 $ that is smaller than $ 42 $, which is $ 39 $, or $ 13 * 3 $. Then, we can subtract $ 42 - 39 = 3 $, so $ 42 \bmod 13 = 3 $.

The modulo operation is very important in many areas of programming. In fact, it is one of the core operations for most modern forms of encryption!

Modulo & Negative Numbers

The modulo operation is not consistently defined when applied to negative numbers. Therefore, each programming language may return a slightly different result when performing this operation on a negative number. Make sure you carefully consider how this operation is used, and consult the official documentation for your programming language or test the operation if you aren’t sure how it will work.

Truncated & Floor Division

As discussed a bit earlier, one of the stranger things in programming is dealing with division. When dividing two integers, it is possible to end up with a result that is a floating point number, as in $ 9 / 8 = 1.125 $. So, how should we handle this?

It turns out that each programming language may handle it a bit differently. For example, Java would truncate the result by removing everything after the decimal point to make the result an integer. So, in Java, the statements 99 / 100 and -99 / 100 would both usually evaluate to 0. However, we can force the result to be a floating point number by making sure that at least one of the operands is a floating point number, as well as the variable we are storing the result in.

Python, on the other hand, would store the result as a floating point number by default if needed. However, Python also includes a special operator, known as floor division or //, that would round the result down. For positive numbers, this means that the result would be rounded toward 0, while negative numbers would be rounded away from 0. So, in Python, the statement 99 // 100 would evaluate to 0 since it is rounded toward 0, while -99 // 100 would evaluate to -1 since it is rounded away from 0.

In short, we just need to pay special attention when we use the division operation to make sure we get the result we expect.

Assignment & Equality

Lastly, we should briefly discuss the assignment operator. We’ve already come across it earlier in this chapter. In mathematics, we usually use the equals sign = to denote equality, as in $ x + 5 = 10 $.

In programming, we use the single equals sign = to perform assignment. This allows us to store a value into a variable, as in x = 5. This would store the value $ 5 $ into the variable x. We could also say that we assign the value $ 5 $ to x.

However, it is very important to note that the variable we are assigning a value to goes first, on the left side of the equals sign. So, we cannot say 5 = x in most programming languages.

Equality

Most programming languages use a double equals sign, or == to denote equality. We’ll learn more about equality and other comparison operators in a later chapter.

Subsections of Arithmetic

More on Operations

Operation

Computer Science, and therefore programming, has its roots in mathematics and uses a lot technical terms and jargon from math. Unfortunately this means that most programmers need to be familiar with some of this jargon.

An operation is a fancy math term for “do something”, but carries with it its own set of vocabulary. An operation is composed of:

  1. an operator (one or more symbols)
  2. operands (one or more pieces of data)
  3. a semantic meaning (i.e. what the operator does)

Binary Operations

Binary operations operate on two inputs. This class of operations should be familiar from arithmetic. Its format is

    operand1 operator operand2
        2     +        4
        x     +        y

where the operator is typically some type of symbol.

Binary Operations

Note that in this case the term binary refers to the fact that the operation is performed on exactly 2 operands, and not that the data is stored in a base-2 (binary) data format.

This can lead to some confusion among programmers when referring to bitwise operators, which are operators that perform actions on individual bits in a piece of data. Many programmers mistakenly call these “binary” operators since they operate on data at the binary level, but the proper term is “bitwise” operators.

Operator symbols are typically overloaded in programming languages, which means that they can do different things based on the types of the operands. For example, a typical language might do any of the following operations when the plus sign + is used as the operator, depending on the types of the individual operands:

Type Operand 1 Type Operand 2 Result Type
int
2
int
4
int
6
int
2
float
4.0
float
6.0
float
1.1
float
3.7
float
4.8
String
“2”
String
“4”
String
“24”
String
“2”
int
4
Error operation not defined for an operand of type String and int

We will have to refer to our language’s documentation for how each operator functions. Operator behavior, particularly in the presence of mixed-type operands varies from language to language.

Infix vs. Prefix

The form operand1 operator operand2 is called infix notation. However, there is another construct called prefix, that looks like operator operand1 operand2 or + 2 4. For technical reasons it is easier to write programming languages that use prefix notation. In OOP, we may see prefix notations used for methods and classes, which were adapted from a functional programming language where prefix notation is more common.

Unary Operations

There are some operations that have just one operand, these are called unary operations. The typical form is operator operand or operand operator. A fairly common unary operator is ! for the logical operation “not”. !True is the same as False. We’ll introduce more of these operators in a later chapter.

User Input

Most programs process some type of user input, either text input by the user, mouse clicks on the screen, or many other methods. In this course, we’ll typically deal with three different types of user inputs:

  1. Command-line arguments
  2. Reading from a file stream
  3. Reading from the keyboard stream

For the first few chapters, we’ll go ahead and provide the code needed to handle and process user inputs. As we learn more, we’ll go deeper into detail about each method and discuss the nuances involved with reading and processing user input.

Hard Coding

Consider the code

 int x = 3
 int y = 4
 int z = x + y
 print (z)

Here the programmer has explicitly coded the values of x, y, and z. Because the user has no way to directly assign values to any of these variables, they are referred to as “hard coded.” However, programs that contain only hard-coded values are not very useful since they don’t respond to any input from the user. So, in most cases, our programs will include some sort of user input instead of hard-coded values.

Command Line Arguments

Command-line arguments are pieces of data that are provided to the program when executed as part of the initial command. For example, we learned that we can run a basic Hello World program using a command similar to these:

# Java
java HelloWorld
# Python
python3 HelloWorld

We can provide a command-line argument to these programs by including them after the command. For example, we could re-write our program to make user of the user’s name when provided as the first command-line argument. In that case, we can use the following command to run our program:

# Java
java HelloWorld Willie
# Python
python3 HelloWorld Willie

In this example, Willie is the input provided as the first command-line argument. We can change that to be any value we want.

Common Properties of Command Line Arguments

There are some common properties of command line arguments we should be aware of

  1. Arguments are passed in as strings. Programmers must convert them to the appropriate data types.
  2. Arguments are passed in order from left to right. If a problem specification asks for a positive number then a negative number, we must provide them in that order.
  3. Arguments are separated by one or more spaces.
  4. There are ways for a program to tell how many arguments there are.

That’s pretty much it. User input can be provided on the command line in the form of string arguments (values). When the command is run, these arguments are packed up by the operating system and given to the program. When command line arguments are required (or optional), the problem statement will tell us their order and purpose. The decision on whether or not to use command line arguments is typically a design consideration.

Streams

Both file and keyboard input are handled as streams. Streams are a computer science abstraction for data that is organized in a queue. Data (usually bytes) are ordered and read from front to back. We can see how this works for the keyboard. When we type “dog” the keyboard stream (called stdin for “standard input”) receives the bytes [100, 111, 103]. Files from a disk drive are similarly read first byte to last.

Streams as Objects

Object oriented high level programming like Java and Python have built in classes that read values from a stream. Thus, with some minor set up differences, the commands to read from a file are typically exactly same as the commands to read from the keyboard. This is the power of abstraction – the details of how to read from these different streams are hidden from the programmer who only wants read them.

Streams are literally bytes of data. We will use methods that interpret those byes as text, and then convert the text to the type of variable we need.

We’ll learn more about reading data from the keyboard and from a file later in this course.

Chapter 8.J

Java Data Types

Data Types in Java

Subsections of Java Data Types

Types

The Java programming language is a statically typed language. This means that each and every variable we use in our programs must be declared with a name and a type before we can use it. The Java compiler can then perform a step known as type checking, which verifies that we are using the proper data types in our program.

The major advantage of this approach is that many errors in our computer programs can be discovered by the compiler, well before we ever try to run the program directly. It can also help us determine what the cause of the error is by stating that it is a type error, giving us a clue toward how it could be solved.

One downside to this approach is that it makes our programs a bit more complex, as we must think ahead about what types of data we’ll be storing in each variable, and we’ll need to write our programs carefully to avoid type errors. However, it may also make our programs easier to manage later on, as we’ll know exactly what type of data is stored in each variable at any given point in the program’s execution.

To make dealing with types a bit easier, Java will automatically coerce data from a “narrower” data type to a “wider” data type without any additional code. For example, an integer in Java can be stored in a floating point variable.

However, the opposite case is not true. In Java, we cannot store a floating point number in an integer without explicitly converting the data type. We’ll see how to do that later in this chapter.

Numbers

Most of the computer programs we’ll write must deal with numbers in some way. So, it makes perfect sense to start working with the numerical data types, since we’ll use them very often. Let’s dive in and see how we can use these numerical data types in Java!

Java has built in primitive types for various numeric, text and logic values. A variable in Java can refer to either a primitive type or a full fledged object.

Integers

The first data types we’ll learn about in Java are the ones for storing whole numbers. There are actually 4 different types that can perform this task, each with different characteristics.

Name Type Size Minimum Maximum
Byte byte 8 bits $ -128 $ $ 127 $
Short short 16 bits $ -32\,768 $ $ 32\,767 $
Integer int 32 bits $ -2\,147\,483\,648 $ $ 2\,147\,483\,647 $
Long long 64 bits $ -2^{63} $ $ 2^{63} - 1 $

As we can see, each data type in this list has a different size, and can store numbers within a different range. So, if we know the minimum and maximum values that could possibly be stored in a particular variable, we can use the smallest corresponding data type that can store that value. This would allow us to conserve the amount of memory used in our programs.

However, in practice, most modern computers have more than enough memory available to handle our programs, so this is typically not a concern for most developers. Instead, it is best to use the largest possible data type, to avoid errors in the future as the program is updated and data values may become larger than initially anticipated.

In this program, and most of the code in this book, we’ll typically use the integer, or int data type for all whole numbers. Even though it isn’t the largest data type for storing whole numbers, it is generally large enough. In addition, the int data type is supported universally across many different programming languages, so learning how to use it will make it easier to switch between languages later on.

Floating Points

The next data types we’ll learn about in Java are the ones for storing rational and irrational numbers. There are actually 2 different types that can perform this task, each with different characteristics.

Name Type Size Range
Float float 32 bits $ \pm 10^{\pm 38} $
Double double 64 bits $ \pm 10^{\pm 308} $

Unlike the data types for whole numbers, it is more difficult to discuss the minimum and maximum values for these data types, as it requires a thorough understanding of how they are stored in binary and interpreted by the processor in a computer. In general, each one can handle large numbers as well as small numbers extremely well.

However, just like scientific notation, the numbers it stores at best can only be as accurate as the number of digits it holds. So, when storing an extremely large number, there will be some rounding error.

In this program, and most of the code in this book, we’ll typically use the Double, or double data type for all decimal numbers.

Using Numbers in Code

Now that we’ve discussed the various data types available in Java, let’s look at how we can actually create variables that can store data in our programs.

Getting Started

Before working with the code examples in the rest of this chapter, we’ll need to add a class declaration and a main method declaration to a file named Types.java. In Codio, you can open it by clicking on the file in the tree to the left. If you don’t recall how to do that, now is a great time to review the material in Chapter 1 before continuing.

Setting up a new file each time is great programming practice!

Declaring

In Java, we can declare a variable using this syntax:

<type> <identifier>;

So, to declare a variable of type int named x, we would write:

int x;

We can also do the same for each of the types listed above:

byte b;
short s;
int i;
long l;

float f;
double d;

As with any other part of our program, we must first declare it before we can use it.

Identifier Rules & Conventions

Java has rules about the allowable names with can be used as identifiers, you can find them in the Java Documentation.

By convention, variable names should be descriptive and use camelCase to aid in reading.

Assigning

Once a variable has been declared, we can give it a value using an assignment statement. Assignment uses this syntax:

<destination> = <expression>

In that example above, <destination> is the identifier of the variable we’d like to store data in, and <expression> is any valid Java expression that produces a value. It could be a number, another variable, a mathematical operation, or even a method call, which we’ll learn about in a later chapter. The variable we are storing the value in must always be on the left side of the equals = sign.

For example, if we want to store the value $ 5 $ in an int variable named x, we could write the following:

int x;
x = 5;

We can even combine the declaration and assignment statements into a single statement, like this:

int x = 5;

The same syntax applies to all of these types in Java:

byte b = 1;
short s = 2;
int i = 3;
long l = 4;

float f = 1.2f;
double d = 3.4;

When assigning values from one variable to another using primitive data types, the value is copied in memory. So, changing the value of the first variable would not affect the others, as in this example:

int x = 5;
int y = x;
x = 6;

At the end of that code, the value of x is $ 6 $, but y still contains $ 5 $. This is important to remember.

Try It!

See if you can create a variable using each of the six data types listed above in Types.java. What happens when you assign a value at that is too big or too small for the variable’s data type? Can you assign the value from an int variable into a byte variable?

Doubles are Default

Notice in the code that the float variable f is assigned using the value 1.2f instead of just 1.2. This is because decimal values in Java are interpreted as double values by default, so when assigning a double to a float there is a possible loss of precision that the Java compiler will complain about. To avoid that, we can explicitly state that the value is a float by appending the letter f to it. We won’t see this very often outside of this lesson.

Printing

We can also use the System.out.println and System.out.print methods we learned earlier to output the current value of a variable to the screen. Here’s an example:

int x = 5;
System.out.println(x);

Notice that the variable x is not in quotation marks. This way, we’ll be able to print the value stored in the variable x to the screen. If we place it in quotation marks, we’ll just print the letter x instead.

Later, in the project for this chapter, we’ll learn how to combine multiple variables into a single output.

Casting

When writing our programs, sometimes we need to change the data type that a particular value is stored as, especially when we want to store it in a new variable. Ideally, we would construct our programs to avoid this issue, but in the real world we aren’t always so lucky.

To change the data type of a value, we can cast that value to a different data type. To cast a value to a different data type, we put the data type we’d like it to be in parentheses in front of the value.

Let’s look at the example below:

int x = 120;
byte y = x;
System.out.println(y);

In this example, we’ve created an int variable x, and stored $ 120 $ in that variable. We then create a byte variable y, and try to store the value from x into y.

Follow Along

Try to run these examples by placing each one in Types.java and seeing what happens. Does it work? Try it before reading the answers below.

When we try to compile that example, we should get the following compiler error:

Incompatible Types Error Incompatible Types Error

Since the int data type is larger than the byte data type, the compiler will give us an error stating that we might lose data when we perform that conversion. Of course, if we reverse the int and byte data types, and try to assign a byte to and int, it will work just fine.

In general, we should try to avoid this problem by redesigning our program to eliminate the need to store a variable in a smaller type, but sometimes it is necessary. To do this, we’ll need to cast the value to the correct data type. Let’s update the example above:

int x = 120;
byte y = (byte) x;
System.out.println(y);

In this example, we have added a (byte) in front of the variable x when we are assigning it to y. This tells the compiler that we would like to convert the data type of x to byte before storing it in y. Now, when we try to compile and run this program, it will act as we expect.

Successful Type Cast Successful Type Cast

However, let’s look at one final example to see why the compiler would warn us about converting to a smaller data type:

int x = 128;
byte y = (byte) x;
System.out.println(y);
Cast vs. Convert

When we say “cast” we really mean convert. Sometimes this results in a change in the the binary representation. Conversion preserves the semantics (meaning) but over writes the binary. In addition to the “casting” syntax (desired type) value ... (int)2.0, you will be exposed to many ConvertTo() methods later in this course.

Casting nearly always preserves the original binary but may result in gibberish; you lose the meaning.

int x = (int)'2';
// results in x equal to 50 not 2
// '2' binary 00110010
// int value of 00110010 is 50

However, “casting” is nearly always used to mean “convert”. It comes from the origins of programming, where languages supported fewer types and the binary had the same semantic meaning across multiple data types.

typebytesbinary for the value of 2
byte100000010
short20000000000000010
int400000000000000000000000000000010

From the above table you can see how casting might work for various sized integer values. Leading “bits” were ignored when casting to a byte-wise smaller type. Leading 0s were added when casting to larger type.

We will use cast and convert interchangeably in this course to mean convert to the desired data type. In Java casting between primitive numeric-types often preserves the semantics (value) of the cast. (int) 2.5 is the integer 2, and (double)-4 is the double -4.0.

In this example, instead of storing $ 120 $ in x, we have instead stored $ 128 $. When we compile and run this program, we get this unexpected output:

Overflow Error Overflow Error

We expect it to output $ 128 $, but instead it outputs $ -128 $. That’s strange, isn’t it?

What’s happening is an error known as integer overflow. Since $ 128 $ is too large to fit in a byte variable, the computer will truncate, or remove, the bits that are at the front of the number that won’t fit. This could cause data to be lost or misinterpreted, which is what happens here.

So, we must always be careful and not try to cast a variable to a smaller data type if it is too large to fit in that data type. This is why the compiler will always warn us when we try to do so, unless we add an explicit cast to our code.

Simplify our Code

To make things simpler, we typically just use a single data type for whole numbers, and a single data type for decimal numbers. By using the same type consistently throughout our programs, we can avoid many issues related to data types and casting.

As stated above, most of the examples in this book will use the int data type for whole numbers, and the double data type for decimal numbers. These choices are consistent with the majority of official Java code.

Other Types

Beyond numbers, there are a few other primitive data types in Java. Let’s take a quick look at them and see how they can be used in our programs.

Boolean

Java supports a primitive data type named boolean that can only store two values: true and false. As we might expect, we can use these boolean variables to store answers to questions in our program, such as “Is x greater than y?” At this point, we won’t use boolean variables for much in our programs, but in a later module we’ll cover the basics of boolean logic and learn how to use these variables to control how our program runs.

Character

Java also has a char data type, which can be used to store a single character of text. It uses the 16-bit Unicode format to store the data, which is the same as ASCII for simple characters and commands.

Declaring & Assigning

To declare a variable using these data types, the format is the same as the numerical data types:

boolean t;
char c;

We can also assign values to each variable. For example, to assign a value to a boolean variable, we could do this:

boolean t;
t = true;
boolean f = false;

Notice that both true and false are keywords in Java, which means that we won’t have to put them in quotation marks to use them. These values can be used to directly represent the boolean values True and False, respectively. Some programming languages, most notably C, do not support boolean values in this way, but Java includes them as keywords.

For characters, the process is similar:

char c;
c = 'a';
char d = 'B';

Notice that we use a single quotation mark ' around character values in our code. This is very important to remember! In our Hello World example, we used double quotation marks " around strings, or sentences of text. For a single character, however, we only use a single quotation mark.

Characters & Strings

Thankfully, the Java compiler will catch this problem pretty quickly. For example, take a look at this code, and see if you can spot the errors:

char c = "a";
System.out.println("c");

When we first try to compile the program, we should get the following error:

Compilation Error Compilation Error

As we can see, the compiler will not allow us to assign the string "a" to the variable c, which is of type char. So, to fix this error, we’ll need to modify our code to be the following:

char c = 'a';
System.out.println("c");

However, there might be another error in this code. Can you spot it? What will be printed to the screen when we run this program?

Let’s find out:

Program Output Program Output

You might have expected the program to output a, but instead it output c. This is because we have "c" inside of our System.out.println method. To print the value stored inside a variable, we shouldn’t put it in quotation marks. Otherwise, it will interpret that as a string, and print the name of the variable (or whatever is in quotation marks) instead.

Operators

Now that we’ve learned how to create our own variables, we can use them to perform a wide variety of mathematical operations. For most of these, we’ll use the same operator symbols that we’ve used before in math, but a few of them are unique to programming languages like Java.

For each of the examples below, we’ll assume that we have already created two int variables, x and y, as follows:

int x = 5;
int y = 8;

Addition +

Using the plus + symbol, we can add two numbers together and find their sum:

int z = x + y;
System.out.println(z); // 13

Since $ 5 + 8 = 13 $, this program would output 13.

When writing our code, it is always good practice to put spaces between operators and operands unless otherwise noted, just to make it easier to read our code.

The plus symbol can also be use to concatenate or link multiple strings together. We’ll learn how to do that in the project for this chapter.

Code Comments

We can add comments to our code to help us describe it to others or simply understand it better ourselves. These comments are simply text that is added to the source code, but they are marked in a way that tells the compiler to ignore it when compiling the program.

In Java, we can use two forward slashes // to tell the compiler that everything on that line following those symbols should be treated as a comment, as we can see in the examples on this page.

We can also use a forward slash followed by an asterisk /* to start a multiline comment in Java. The compiler will ignore everything after that symbol until a closing asterisk followed by a forward slash */ is found.

Finally, Java also uses a forward slash followed by two asterisk /** to denote special comments that can be used to automatically generate documentation for the code. Again, this comment section is closed by an asterisk followed by a forward slash */.

Traditionally, each additional line in these multiline comments will be prefixed by an asterisk to make them easier to read. In fact, the Codio editor will do that for us!

Here are some examples in code showing how those comments can be used.

/**
 * This is a comment at the beginning of a class that gives
 * information about the class contained in this file
 * 
 * @author Username
 * @version 1.0
 */

public class Example{
  
  /**
   * This is a comment at the beginning of a method. 
   * It can give information about the inputs and outputs 
   * of that method
   * 
   * @param args - the command-line arguments
   */
  public static void main(String[] args){
    /*
     * This is a simple multi-line comment
     * It can have multiple lines
     * and ends with the slash below
     */
    
    int x = 0;
    
    //This is a single line comment
    int y = 0; //Comment after code on the same line
  }
}

Feel free to use comments anywhere in your source code to help you understand what the code does! It is always better to include too many comments than too few, especially in larger programs.

Subtraction -

Likewise, we can use the minus - symbol to subtract two values and find their difference:

int z = x - y;
System.out.println(z); // -3

This program would output -3, since $ 5 - 8 = -3 $. It is important to remember that these operations can result in negative numbers, so the order of operands we use matters when subtracting.

We can also use the minus symbol to find the additive inverse of a number, or change the sign of a variable from positive to negative and vice-versa:

int z = -x - y;
System.out.println(-z); // 13

In this example, we use -x to denote the inverse of x. In this case, we don’t include a space between the minus symbol and the variable it is attached to. So, this expression will calculate $ -5 - 8 = -13 $ and store that value in z. Then, when we print that variable, we invert it again, so it will actually print 13 to the screen instead of the value $ -13 $ stored in z.

Multiplication *

We can also use the asterisk * or star symbol for multiplication to find the product of two numbers:

int z = x * y;
System.out.println(z); // 40

We know that $ 5 * 8 = 40 $, so this program would output 40 as expected.

Division /

Division is a bit different. Most programming languages use the forward slash / symbol for division, making the statement look like a fraction. That is probably the easiest way to think about division in a programming language - it makes a fraction of the dividend and the divisor.

Earlier in this chapter we discussed that strange things may happen when we try to divide two whole numbers. Let’s look at a few examples:

int z = x / y;
System.out.println(z); // 0

This first example will calculate $ 5 / 8 $ but store it in an int variable. We expect the answer to be $ 0.625 $, but since an int variable can only store a whole number, the result is truncated by removing the decimal portion. So, our program just outputs 0.

Here’s another example:

int z = -x / y;
System.out.println(z); // 0

We know that $ -5 / 8 = -0.625 $, but once again the result is truncated and the decimal part is removed. So, this program will also output 0.

Let’s look at another division example:

double z = x / y;
System.out.println(z); // 0.0

Here we are storing the result in a double variable. We’d expect the output to be 0.625, but we actually just get 0.0 when we run this program. That’s strange, isn’t it?

Earlier we discussed some of the rules for how a programming language calculates numbers. One of those rules states that an operation performed on two whole numbers will result in a whole number, which is exactly what is happening here. The processor calculates $ 5 / 8 = 0 $ as a whole number using truncated division, then converts the value $ 0 $ to a floating point value before storing it in z. So, even though it appears we should be storing $ 0.625 $, we aren’t able to due to the data types of our two operands. This is a very common mistake made by programmers of all skill levels, so it is very important to understand what is going on.

So, how can we solve this issue? The answer lies in the use of a cast operation to convert the data type of one of our operators to a floating point value. Here’s an example showing how to accomplish that:

double z = (double) x / y;
System.out.println(z); // 0.625

In this example, we are casting the value stored in x to be of type double. Then, the division operation is performed, this time between a floating point number and a whole number, which will result in a floating point number. Finally, that result will be stored in z, and the program will output 0.625, which is the correct answer.

In short, we need to make sure we pay special attention when dividing numbers in our programs, just to make sure we are getting the result we expect.

Modulo %

The modulo operator uses the percent sign % to calculate the remainder of a division operation. Let’s look at an example:

int z = x % y;
System.out.println(z); // 5

Here, we are calculating the remainder of the division $ 5 / 8 $. Since $ 8 $ does not go into $ 5 $ at all, we are left with $ 5 $ as the remainder of that division.

If we reverse the operands, we can get a different result:

int z = y % x;
System.out.println(z); // 3

Here, we are calculating the remainder of $ 8 / 5 $. Since $ 5 $ will go into $ 8 $ once, we can perform $ 8 - 5 = 3 $ to find the remainder of that division.

Modulo & Negative Numbers

In Java, the modulo operation is treated like a true remainder operation. So, when applied to negative numbers, it will perform the expected division and return the remainder, which may be a negative number, depending on the signs of the operands. It follows the same sign rule as multiplication and division.

Here are some examples:

System.out.println(-8 % 5);  // -3
System.out.println(-8 % -5); // -3
System.out.println(8 % -5);  // 3

We already know that $ 8 % 5 = 3 $. In this case, the negative sign is applied if just one operand is negative. Otherwise, the result is positive if both operands are negative.

Increment ++ & Decrement --

Increment ++ and decrement -- are two special operators in Java. They are used to add or subtract $ 1 $ from an existing variable. Let’s look at a couple of examples:

int z = x++;
System.out.println(x); // 6
System.out.println(z); // 5

Here, the increment operator will increase the value stored in x by $ 1 $. In this case, we don’t include a space between the operator and the variable it is attached to. However, since the operator is after the variable, it will happen at the end of the operation. So, the value $ 5 $ will be stored in z first, then x will be incremented by $ 1 $ afterwards. So, the program will output 6 as the value of x, and 5 as the value of z.

By placing the increment operator in front of the variable, we can see a different outcome:

int z = ++x;
System.out.println(x); // 6
System.out.println(z); // 6

As we’d expect, here the increment operation will be performed first, and then the new value stored in x will also be stored in z, so both variables will contain the value $ 6 $ in this program.

The decrement operator works similarly, subtracting $ 1 $ instead of adding $ 1 $.

In practice, these operators are only used in a few scenarios, most notably within loops to increment or decrement a counter variable. In general, they can make our code more complex and difficult to read due to the variety of different ways these operators can be used.

Assignment Shortcuts

Finally, many operators in Java also have combined operation and assignment operators. These will perform the operation specified and store the value in the first variable, all in a single statement.

Operator Example Equivalent
+= x += y; x = x + y;
-= x -= y; x = x - y;
*= x *= y; x = x * y;
/= x /= y; x = x / y;
%= x %= y; x = x % y;

As with the increment and decrement operators, some of these shortcuts are rarely used in practice since they can make our code more difficult to read and understand. However, in some scenarios they can be quite helpful.

Order of Operations

Video Error

There is a typo in the video below. The order of operations given on the slide is incorrect. The correct order is shown below the video.

YouTube Video

Video Materials

We are already familiar with the order of operations in mathematics, using mnemonics such as “PEMDAS” to refer to the following order:

  1. Parentheses
  2. Exponents
  3. Multiplication & Division
  4. Addition & Subtraction

Programing languages work very similarly, but there are many more operators to deal with. Here’s a quick list of the order of operations, or operator precedence of the operators we’ve covered so far:

  1. Parentheses
  2. Postfix: Increment ++ & Decrement -- after variable*
  3. Prefix: Inverse -, Increment ++ & Decrement -- before variable*
  4. Multiplicative: Multiplication *, Division /, and Modulo %
  5. Additive: Addition +, Subtraction -
  6. Assignment: =, +=, -=, *=, /=, %=

* The increment and decrement operations will be calculated either before or after the rest of the operation is performed, depending on where they are placed in relation to the associated variable. This lists what order they will be analyzed by the compiler to determine which variable they are attached to.

Here’s a quick example to show how this works:

int x = 1;
int y = ++x + x - x / x * x % x--;
System.out.println(x); 
System.out.println(y); 

Can we determine what the output should be? Let’s try to break it down!

Based on the operator precedence table above, we know that the x-- will be evaluated first. However, since it is after the variable, it won’t be calculated until after the rest of the operation is complete. So, we can just remember that step for now, and we’ll actually perform that calculation last.

Next, we know that ++x will be evaluated. This step must be performed before any other operation since it is before the variable, so we must immediately increment the value of x by $ 1 $. So, x is now $ 2 $.

Now we can perform all of the multiplicative operations, going from left to right. So, by adding the appropriate parentheses, we’d calculate the following:

$$ x + x - x / x * x \% x $$ $$ 2 + 2 - 2 / 2 * 2 \% 2 $$ $$ 2 + 2 - (2 / 2) * 2 \% 2 $$ $$ 2 + 2 - ((2 / 2 * 2) \% 2 $$ $$ 2 + 2 - (((2 / 2) * 2) \% 2) $$ $$ 2 + 2 - ((1 * 2) \% 2) $$ $$ 2 + 2 - (2 \% 2) $$ $$ 2 + 2 - 0 $$

Following that, we can perform any additive operations. Once again, we can add parentheses as appropriate to find the following:

$$ 2 + 2 - 0 $$ $$ (2 + 2) - 0 $$ $$ ((2 + 2) - 0) $$ $$ (4 - 0) $$ $$ 4 $$

Once we’ve calculated all of the operations on the right-hand side of the assignment operator, we can then perform the assignment operation to store the final value $ 4 $ in y.

Finally, we must remember to go back and decrement the value in x by $ 1 $, so x once again is just $ 1 $.

So, the final output from this program would be:

Operators Example Operators Example

However, as any good math teacher will tell us, it is always better to use additional parentheses to make sure our operations are performed in the correct order instead of relying on the order of operations. So, we should definitely avoid writing code as ambiguous as the example given above.

Subsections of Operators

Command-Line Arguments

The Linux command line consists of the command and the arguments.

linux command line for java linux command line for java

In this example, Linux will see the first part of the command, which is java, and that will tell it what program to execute. Everything else is packaged up as string-values and sent to the Java Virtual Machine. The JVM takes the name of the program, SomeProgram, from the command, and then starts the program with that file name, passing the remaining items (arguments) to the program.

Accessing Command-line Arguments in a Object Oriented Program

By convention, the command line arguments are sent to the program’s main method. Java handles this automatically.

linux command line for java linux command line for java

Inside Java, the command line arguments are mapped (assigned) to the parameter variable in the main method called args. In main args is an array of Strings.

Java map of cmd line arguments Java map of cmd line arguments

Accessing arguments

Arrays are ordered collections of data - we might think of it as a numbered list. We access elements of the array using the indexing operator [], the indexes start at 01. The syntax is variableName[indexNumber].

The parameter args is always an array of strings, in the order they appeared on the command line.

Converting arguments

Since args is always an array of Strings, programmers must convert those arguments (values in the args array) to the appropriate type. For numbers, this is typically done using various parse methods that are provided as part of the Java language.

Syntax Semantics
int x = Integer.parseInt(args[0]) Take the string in args at index 0
Convert it to an int
Assign it to x
double x =Double.parseDouble(args[0]) Take the string in args at index 0
Convert it to a double
Assign it to x

If we try and convert something that does not make sense, Double.parseDouble("act") or Integer.parseInt("3.1415"), we’ll get an error when we run our program. Later in this class we’ll learn about how to detect and handle those errors, but for now they will simply crash our program.

Example

Consider the code below, which can be stored in a file named SomeProgram.java if we want to execute it.

public class SomeProgram{
    public static void main(String[] args){ 
        System.out.println("args[0] is a String"); 
        System.out.println("args[0]'s value is \""+ args[0] + "\"");
        int x = Integer.parseInt(args[0]);
        System.out.println("x is a int");
        System.out.println("x's value is " + x);
        int y = Integer.parseInt(args[1]);
        int z = x + y;
        System.out.println(x + " + " + y + " = " + z);
    }
}

A proper command line for running SomeProgram.java would be java SomeProgram <int> <int>, since it requires to command-line arguments that will be converted to integers. An example would be java SomeProgram 2 3 which will store "2" as args[0] and "3" as args[1]. At this point, let’s trace the execution SomeProgram.java if we execute it using the command java SomeProgram 2 3.

  1. When we run this program we start on line 2, the beginning of the main method.
  2. The array {"2", "3"} is assigned to args
  3. This line prints that args[0] is a String. We know this because args is declared to be an array of Strings (String[] args, line 2)
  4. This line prints the value of the string at index 0 of the array args
  5. This will convert the string args[0] to an int, assign that integer value to x
  6. This will print that x is an int. We know this because x is declared to be an int (int x, line 5)
  7. Next we’ll print the value of x
  8. Now we convert the string args[1] to an int, assign that integer value to y
  9. Add x and y, assign that sum to the int z
  10. Print the result of the computation.

Feel free to experiment with SomeProgram.java to explore more ways to use command-line arguments in code. What happens if we give too few arguments? Or too many? What about the wrong types?


  1. Staring at 0 saves memory and simplifies memory access. Nearly all languages start at 0. ↩︎

Subgoals - Evaluating Expressions

One of the unique parts of this course will be the inclusion of subgoals to help us better understand what is going on in our code. Subgoals are designed to help us structure our thinking process in a way that matches what an experienced developer might have.

A great example is learning how to read. Fluent readers can read whole words at a time, while processing and retaining information from several sentences at once. Novice readers have to sound out words one letter at a time, and can store and retain a few words at a time in memory before being overwhelmed by information. Learning a new language can quickly make this difference very apparent, even for readers who are fluent in many languages.

Writing computer code is very similar. An experienced developer can fluently read and write code, seeing and understanding entire code structures at a glance. Novice developers often have to look at a single line at a time, and break it down into individual parts before it is apparent what the code says. That’s just part of the learning process!

To make this process easier, this course will include many pages that describe subgoals we can use to help analyze and write code. In essence, we can treat these subgoals as a set of steps to follow mentally when performing these tasks. Over time, we’ll become more comfortable with each task, and the subgoals will quickly become ingrained knowledge. Just like we can easily read both “cat” and “catastrophe” at a glance, with practice we’ll be able to read code just as quickly!

More Information

For more information on Subgoals, see the work of Dr. Briana Morrison published through the ACM and elsewhere. Link

Analyzing Expressions

To analyze an expression involving variables and mathematical operators in code, here are some mental subgoals we can use:

1. Check Types

First, we must determine whether the data type of the expression is compatible with the data type of the variable we want to store it in. In Java, we must be very careful to make sure we are only storing whole numbers in the integer data type, and floating point values in the double data type.

2. Perform Prefix Operations

Next, we should look at the expression to determine if there are any prefixed operations that must occur first. In Java, for example, we could find a prefixed increment operator like ++x, so we’ll need to update the value of x before moving to the next step.

3. Solve Arithmetic Equation

At this point, we can solve the arithmetic equation using the order of operations for our language. This simply involves the process of substituting values for variables and performing the requested operations. However, once again we must be careful to make sure that the operands provided to each operator are valid and produce the correct data type. The example we saw earlier for handling a large equation showed us a great way to work through this process.

4. Confirm Data Type of Result & Store It

Once we’ve solved the arithmetic equation, we should be left with a variable on the left side of the equals sign and a single value on the right side. So, once again, we should confirm that the value on the right can be stored in the data type of the variable on the left.

5. Perform Postfix Operations

Finally, if the original expression included any postfix operations, such as a postfixed decrement like x-- in Java, we’ll need to update the value of x before moving on to the next line.

Examples

Let’s walk through an example showing how to use these subgoals to evaluate a few expression statements.

int x = 3 + 5;

We’ll break this down by subgoals:

  1. We can easily confirm that the data type of the variable is int, and that the numbers on the right side of the equation are all integers, so we shouldn’t have any issues with data types initially.
  2. This reminds us to look for prefix operations. Since there aren’t any, we can move on.
  3. We can now solve the arithmetic equation on the right side of the expression. So, we can easily find that 3 + 5 can be replaced with 8.
  4. This tells us to once again confirm that the result is a compatible value for the data type of the variable on the left. Since 8 can be stored in an int variable, we are just fine. So, we can update the value of x to 8.
  5. Finally, we must look for any postfix operations in the original equation. Since there aren’t any, we can skip this step.

That’s all there is to it! While this may seem like quite a bit of information for handling a simple statement, it becomes much more useful as the statements get more complex.

Here’s a second example:

int x = 5;
double y = 3.5;
double z = ++x + -y--;

Once again, let’s break the last line in this code example down by subgoals to evaluate the expression:

  1. This is a bit tricky, since the statement includes several variables. However, we can look at the earlier code to see that x is an integer type, and y is a double type. Since this expression is adding them together, the result should be a floating point number, which can be stored back in the variable z with type double. So, we should be fine here.
  2. Next, we’ll see that there is a prefix expression ++x, so we’ll increment the value of x by $ 1 $ to $ 6 $. We also see a prefix operator -y, so we’ll need to remember that we are dealing with the inverse of the value stored in y.
  3. Now we can solve the equations. To do this, we’ll replace each variable with its value, then calculate the result. So, x + -y becomes 6 + -3.5 which results in 2.5.
  4. Once we have the result, we must confirm that it can be stored in the variable to the left. Since 2.5 is a floating point number, it can be stored in the double data type. So, we’ll update the value of z to $ 2.5 $.
  5. Finally, we’ll need to look for any postfix operators in the original equation. We’ll see y--, so we must decrement the value of y by $ 1 $ to $ 2.5 $

So, at the end of this short code example, we’ll see that x is $ 6 $, y is $ 2.5 $, and z is also $ 2.5 $.

Finally, let’s look at one more example:

int x = 5;
double y = 8.5;
int z = x + y;

Here’s how we can break down the process of evaluating the last line in this example:

  1. First, just like the example above, we see that x is an integer and y is a double, so the sum of those values should also be a double. However, we’ll see that z is an integer. So, what will happen here? As it turns out, the Java compiler will give us an error when we try to compile this code, because we are assigning a double value to an integer which could result in the loss of data.

As we can see, by carefully following these subgoals, we can even find errors in our code, just by evaluating it step by step.

Subgoals - Writing Expressions

We can also use subgoals to help us write new expressions in our code. These subgoals help us understand each part of the process of building a new expression, and they also help us avoid many common errors.

Writing Expressions

Here are the subgoals for writing a new expression:

1. Find Variable in Problem Statement

The first step is to determine which part of the problem statement will be represented by a variable. Sometimes this is obvious, and other times it is not. This may be a new variable that we are creating, or it could be an update to an existing variable.

2. Determine Name and Data Type of Variable

Once we’ve found a variable to work with, we must also determine the variable’s name and data type. Once again, this may be obviously found in the problem statement, but other times we must think a bit more about what type of data will be stored in the variable. If we are working with an existing variable, we’ll need to make sure that the data type of that variable is compatible with the type of data we’d like to store in it. Of course, we should also make sure the variable has a descriptive and memorable name if we are creating a new one.

3. Build Arithmetic Equation with Operators

Now that we’ve isolated our variable, we must build an arithmetic equation and operators required to produce the desired value. This may involve using additional variables in our equations as well.

4. Build Expression

Once we have our arithmetic equation, we can then build the overall expression. This usually consists of three parts: the variable on the left, an assignment operator in the middle, and the arithmetic equation on the right.

5. Make sure Operators and Operands are Compatible

Finally, once we’ve constructed the overall expression, we should check and make sure that all operators and operands are compatible. This means making sure that we aren’t trying to assign a floating point value to an integer, or dividing two integers and expecting a floating point number as a result.

Example

Here’s a quick example to show how this process works. Consider the following problem statement:

Given two whole numbers, find the remainder of dividing the first number by the second number.

Let’s use the subgoals listed above to build an expression for this problem statement

  1. First, we must identify our variables. Looking at this problem statement, we see that it expects us to be given two whole numbers. So, we’ll probably need two integer variables to represent those two numbers. Then, we’ll need a third variable to store the remainder. In total, we can easily identify three variables that we’ll need to create for this program.
  2. Next, we must determine the name and type of each of these variables. To make it very clear, I’m going to label the two input variables inputA and inputB, and the remainder variable remainder. We can also determine that each of them should be the int type, since we are only dealing with whole numbers. So, our code might begin with these three variable declarations:
int inputA;
int inputB;
int remainder;
  1. Now we can assemble our arithmetic equation. We need to calculate the remainder of dividing inputA by inputB, so we can use the modulo operation % to find this value. So, our arithmetic equation could be inputA % inputB.
  2. Then, we can build the overall expression itself. Since we want to store the result of the arithmetic equation in remainder, we’ll build the line remainder = inputA % inputB as our entire expression. So, we can add that to our code:
int inputA;
int inputB;
int remainder;
remainder = inputA % inputB;
  1. Finally, we must make sure our operators and operands are all compatible. Since we know that both inputA and inputB are integers, the result of the modulo operation would also be an integer. So, this line is valid.

Of course, the part we are missing is the values for inputA and inputB. At this point, we haven’t covered how to accept user input yet, so we can assume that those values are hard-coded into the program. So, our final program might look something like this:

int inputA;
int inputB;
int remainder;

inputA = 5;
inputB = 8;
remainder = inputA % inputB;

Using these subgoals is a very great way to learn how to build programs one step at a time. As we learn more about programming and get more experience, many of these steps will become automatic. This is very similar to how we write sentences by hand. We typically don’t think about each letter as we write it once we are fluent with the language, but instead we think of entire words and our brain is able to send the correct commands to produce the desired output. With time and practice, writing code will be very similar.

Printing Text & Numbers

Printing Text with Variables

Now that we’ve learned how to run our program and provide it some input, we must also learn how to provide output that includes our variables as well as text all on the same line. There are a few ways to accomplish this.

First, we can use System.out.print() to print text without a newline character at the end, so that the next output will begin on the same line. So, we could use that to print text and variables on the same line. Here’s an example:

int x = 1;
int y = 2;
System.out.print("Variable x: ");
System.out.print(x);
System.out.print(", Variable y: ");
System.out.println(y);

will produce this output:

Variable x: 1, Variable y: 2

In Java, we can also concatenate multiple outputs together using the + symbol. So, we could produce similar output using this example:

int x = 1;
int y = 2;
System.out.println("Variable x: " + x + " , Variable y: " + y);

which should output:

Variable x: 1 , Variable y: 2

Notice that we needed to include an extra space after 1 in the output. This is because Java does not add a space between strings when concatenating them, so we must be careful to add the spaces ourselves where needed.

There are many ways that we can use Java to output text and variables as desired. We’ll use some of these methods to complete this project.

Summary

We’ve now learned a lot about variables and data types in our programming language. We can use this information to build programs that store and manipulate large amounts of information.

We still have quite a bit to learn before we have the basics of programming covered. As we continue, we’ll see the usefulness of variables and the various data types we’ve learned.

Chapter 3

Boolean Logic

Computing True and False or Not

Subsections of Boolean Logic

The Laws of Thought

YouTube Video

Video Materials

In this chapter, we’re going to step away from computer programming for a bit to discuss a more foundational topic: logic. Logic and reasoning are core concepts at the heart of any level of education, and they are one of the major ways we are able to understand the world around us. Without logic and reasoning, we cannot build upon information we learn to create deeper meaning. Instead, all we are left with are facts, without any knowledge or wisdom gained from them.

Aristotelian Logic

Aristotle Aristotle1

The earliest approach to formal logic comes from the work of Aristotle, seen above. His work established rules by which further information could be inferred, or deduced, from a set of truths about the world known as premises.

For example, in Aristotelian logic, we might find the following premises to be true:

Socrates is a man All men are mortal

Using Aristotle’s rules for logic, we can infer from those premises the following conclusion:

Socrates is mortal

This becomes a powerful system for inferring additional information based on facts in the world. By supporting each conclusion with premises and rules, it is possible to build a great wealth of knowledge.

Boolean Logic

George Boole George Boole2

In the 1800s, there was a great interest in using the rules of mathematics to understand the world as well. Mathematics contains a well understood set of rules and operations, and many thinkers hoped to find a way to apply those rules in the world of logic as well. By doing so, they could move away from using an imprecise spoken language to reason about the world, and instead use the very precise language of mathematics.

In 1854, George Boole, seen above, published An Investigation of the Laws of Thought on Which are Founded the Mathematical Theories of Logic and Probabilities, a book which described exactly how to combine mathematics and logic into a single system. Using the system he proposed, it became very easy to use mathematical expressions and rules to perform the same logical inferences that Aristotle first used nearly 2000 years prior.

In Boolean logic, we may understand the following premises to be true:

$$ A \land B $$ $$ B \land C $$

In this instance, the $ \land $ symbol is used to denote the word “and”. We’ll learn more about these symbols later in this chapter. Using a rule from Boolean logic, we can reach the following conclusion:

$$ A \land C $$

If we compare the example from Aristotelian logic above to this example from Boolean logic, we can quickly see that they are very similar in structure. The first premise establishes a relationship between two items. The second premise establishes another relationship between the second item in the first premise, and a third item. The conclusion states that there must be a relationship between the first and third items. This is very similar to the transitive property of some mathematical operators.3

In programming, we use Boolean logic to control many aspects of how our computer programs function. In this chapter, we’ll learn all about how Boolean logic works so we can apply it correctly in our programs.


  1. File:Aristotle Altemps Inv8575.jpg. (2018, November 23). Wikimedia Commons, the free media repository. Retrieved 20:49, January 9, 2019 from https://commons.wikimedia.org/w/index.php?title=File:Aristotle_Altemps_Inv8575.jpg&oldid=328967130↩︎

  2. File:George Boole color.jpg. (2018, November 19). Wikimedia Commons, the free media repository. Retrieved 21:02, January 9, 2019 from https://commons.wikimedia.org/w/index.php?title=File:George_Boole_color.jpg&oldid=328260467↩︎

  3. This is not a perfect translation from Aristotelian logic to Boolean logic, but it fits well within the scope of this textbook. A more appropriate translation would use implication ( $ \implies $), but we won’t be using that concept, so it has been substituted with “and” instead. ↩︎

Subsections of The Laws of Thought

Logic Operators

YouTube Video

Video Materials

Boolean logic contains four operators that perform various actions in a Boolean logic statement. Before we learn about them, let’s take a minute to discuss variables in Boolean logic.

As we covered in a previous chapter, most programming languages support a special data type for storing Boolean values. That data type can only contain one of two values, True or False. So, in a Boolean logic statement, any variable can only be either True or False

For each of the operators below, we’ll see both a truth table and Venn diagram representation of the operation. They both present the same information, but in a slightly different way. In each operation, we’ll see all possible values of the variables present in the statement, and the resulting value of the operation on those variables. Since each variable can only have two values, the possibilities are limited enough that we can show all of them.

In the Venn diagrams below, each circle represents a variable.1 The variables are labeled in the circles on each diagram.

Not $ \neg $

The first, and simplest, Boolean logic operator is the not, or negation, operator. This operator simply negates the given Boolean value, turning True into False and False into True. When written, we use the $ \neg $ symbol, but most programming languages use an exclamation point ! instead.

Below is a truth table giving the value of $ \neg A $ and $ \neg B $, for all possible values of $ A $ and $ B $.

$ A $ $ B $ $ \neg A $ $ \neg B $
F F T T
F T T F
T F F T
T T F F

We can also see the operation visually using a Venn diagram. In these drawings, assume each individual variable is True, and the shaded portion shows which parts of the diagram are True for the entire statement.

$ \neg A $

Not A Venn Diagram Not A Venn Diagram

As we can see, if $ A $ is true, then $ \neg A $ represents everything in the diagram outside of the circle labeled $ A $. In fact, it includes the area outside of any circle, which represents the whole universe of items that are neither inside of $ A $ nor $ B $. So, the value of $ \neg A $ is not affected by the value of $ B $ in this instance.

We can also extend this to three variables, as shown in the truth table below. It gives the values of $ \neg A $, $ \neg B $, and $ \neg C $ for all possible values of $ A $, $ B $, and $ C $.

$ A $ $ B $ $ C $ $ \neg A $ $ \neg B $ $ \neg C $
F F F T T T
F F T T T F
F T F T F T
F T T T F F
T F F F T T
T F T F T F
T T F F F T
T T T F F F

The Venn diagram for this instance is very similar:

$ \neg C $

Not C Venn Diagram Not C Venn Diagram

As we can see, the not operation is very useful when dealing with Boolean values.

And $ \land $

The next Boolean logic operator we’ll cover is the and, or conjunction, operator. This operator works similar to the way we use “and” in our spoken language. If $ A $ is True, while $ B $ is also True, then we can say that both $ A $ and $ B $ are True. This would be written as $ A \land B $. Most programming languages use two ampersands && to represent the Boolean and operator, but some languages also include and as a keyword to denote this operator.

Here is a truth table giving the value of $ A \land B $ for all possible values of $ A $ and $ B $.

$ A $ $ B $ $ A \land B $
F F F
F T F
T F F
T T T

As we can see, the only time that $ A \land B $ is True is when both $ A $ and $ B $ are True.

Next, we can look at a Venn Diagram that represents this operation

$ A \land B $

A And B Venn Diagram A And B Venn Diagram

As we can see, only the center of the Venn diagram is shaded, representing the parts of the diagram that are both in $ A $ and $ B $ at the same time.

This operation also easily extends to three variables. This truth table gives the value of $ A \land B \land C $ for all possible values of $ A $, $ B $, and $ C $.

$ A $ $ B $ $ C $ $ A \land B \land C $
F F F F
F F T F
F T F F
F T T F
T F F F
T F T F
T T F F
T T T T

The Venn diagram for this instance is very similar to the one above:

$ A \land B \land C $

A And B and C Venn Diagram A And B and C Venn Diagram

Once again, we see that only the very center of the diagram is shaded, as expected.

Or $ \lor $

The third Boolean operator is the or, or disjunction, operator. It is somewhat similar to how we use the word “or” in our spoken language, with a major difference. This operator would be written as $ A \lor B $. Most programming languages use two vertical pipes || to represent the Boolean or operator, but some languages also include or as a keyword to denote this operator.

Consider ordering food at a restaurant as an example. On the menu, you might see the statement “Soup or Salad” as one of the options for your meal. This means that you must choose either “soup” or “salad” to go with your meal, but usually you aren’t allowed to have both (at least, without paying more).

Now, consider a similar statement, $ A \lor B $. If either $ A $ or $ B $ are True, we would say that $ A \lor B $ is also True. However, if both $ A $ and $ B $ are True, we would also say that $ A \lor B $ is True. So, the Boolean or operator will return True when both inputs are True, which differs from how we use it in spoken language.

Here is a truth table giving the value of $ A \lor B $ for all possible values of $ A $ and $ B $.

$ A $ $ B $ $ A \lor B $
F F F
F T T
T F T
T T T

As we can see, the only time that $ A \lor B $ is False is when both $ A $ and $ B $ are False. As long as at least one of the values is True, the whole statement is True.

Next, we can look at a Venn Diagram that represents this operation

$ A \lor B $

A Or B Venn Diagram A Or B Venn Diagram

As we can see, both circles are completely shaded, showing that $ A \lor B $ contains all items in either $ A $ or $ B $, or both $ A $ and $ B $ at the same time.

This operation also easily extends to three variables. This truth table gives the value of $ A \lor B \lor C $ for all possible values of $ A $, $ B $, and $ C $.

$ A $ $ B $ $ C $ $ A \lor B \lor C $
F F F F
F F T T
F T F T
F T T T
T F F T
T F T T
T T F T
T T T T

The Venn diagram for this instance is very similar to the one above:

$ A \lor B \lor C $

A Or B Or C Venn Diagram A Or B Or C Venn Diagram

Once again, we see that all parts of the Venn diagram are shaded, except for the part outside of all the circles.

Exclusive Or $ \oplus $

The last Boolean operator we’ll cover is exclusive or, or exclusive disjunction. We’ll sometimes see this referred to as xor as well. When written, we use the $ \oplus $ symbol. However, not all programming languages have implemented this operator, so there is no consistently used symbol in programming.

This operator works in the same way that “or” works in our spoken language. Recall the “Soup or Salad” example from earlier. The exclusive or operation is True if only one of the inputs is True. If both inputs are True, the output is False.

Here is a truth table giving the value of $ A \oplus B $ for all possible values of $ A $ and $ B $.

$ A $ $ B $ $ A \oplus B $
F F F
F T T
T F T
T T F

As we can see, the result is True when either $ A $ or $ B $ are True, but it is False if both of them are True, or if both of them are False.

As a Venn diagram, it would look like this:

$ A \oplus B $

A Xor B Venn Diagram A Xor B Venn Diagram

As we can see, the parts of the diagram in $ A $ and $ B $ are shaded, but the center part, which is in both $ A $ and $ B $, is not shaded.

This operation is most interesting when extended to three variables. This truth table gives the value of $ A \oplus B \oplus C $ for all possible values of $ A $, $ B $, and $ C $.

$ A $ $ B $ $ C $ $ A \oplus B \oplus C $
F F F F
F F T T
F T F T
F T T F
T F F T
T F T F
T T F F
T T T T

The Venn diagram for this instance is as follows:

$ A \oplus B \oplus C $

A Xor B Xor C Venn Diagram A Xor B Xor C Venn Diagram

In this instance, we see the parts of the diagram representing items that are just in $ A $, $ B $, and $ C $ alone are shaded, as we’d expect. However, we also see the center of the diagram, representing items in $ A \land B \land C $ is also shaded. Interesting, isn’t it? Let’s see if we can break it down.

Just like we hopefully remember from mathematics, when we have multiple operations chained together, we must respect the order of operations. So, we can insert parentheses into the original statement to show how it would actually be calculated:

$ ((A \oplus B) \oplus C) $

Now, let’s assume that all three of our variables are True, represented by $ T $. So, we can reduce the first part as follows:

$$((A \oplus B) \oplus C) $$ $$((T \oplus T) \oplus T) $$ $$(F \oplus T) $$

As we can see, since $ A $ and $ B $ are both True, we find that $ A \oplus B $ is False, as expected. Now, we can continue to reduce the expression:

$$ (F \oplus T) $$ $$ T $$

In this case, since only one of the two operands is True, the result of exclusive or would also be True. Therefore, $ A \oplus B \oplus C $ is True when all three variables are True. In short, a string of exclusive ors is True if an odd number of the variables are True, and it is False if an even number of variables are True.

Not all languages support an logical exclusive or. Alternate forms are $ ( A \lor B) \land \lnot (A \land B) $ or $ (\lnot A \land B) \lor (A \land \lnot B) $.

Soup, Salad, or Breadsticks?

At some restaurants, you are given a choice of not just two, but three items to go with your meal. Of course, the intent is for you to choose just one of the three items listed.

However, now that you understand how exclusive or works, you might see how you could get all three items instead of just one! Logic is pretty powerful, isn’t it?

Comparators

Finally, it is worth discussing other operators that can result in a Boolean value. The most important of these operators are the comparators, which compare two values and return a result that is either True or False. We’ve definitely seen these operators before in mathematics, but may not have actually realized that they result in a Boolean value.

Below is a table giving the common comparators used in programming, along with an example of how they might be used:

Name Operator Symbol True Example False Example
Equal $ = $ == 1 == 1 1 == 2
Not Equal $ \neq $ != 1 != 2 1 != 1
Greater Than $ > $ > 2 > 1 1 > 2
Greater Than or Equal To $ \geq $ >= 1 >= 1 1 >= 2
Less Than $ < $ < 1 < 2 2 < 1
Less Than or Equal To $ \leq $ <= 1 <= 1 2 <= 1

That covers all of the basic operators and symbols in Boolean logic. On the next page, we’ll learn how to combine them together and create logical statements using the rules of Boolean algebra.


  1. The Venn diagrams are adapted from ones found on Wikimedia Commons ↩︎

Subsections of Logic Operators

Boolean Algebra

Of course, one of the major goals of George Boole’s work was not only to create a system of logic that looked like math, but also to be able to apply some of the same techniques from math within that system. Boolean Algebra is a form of algebra that can be done on boolean expressions, and it contains many of the same laws and operations that we’ve already learned from algebra.

Boolean Expressions

A Boolean expression is any expression that results in a Boolean value. They consist of the values True and False, sometimes denoted as $ T $ and $ F $, literal values, and variables which are usually lower-case letters, combined using the operations discussed on the previous page. Here are a few examples:

$$ T \land x $$ $$ (\neg x \land y) \oplus z $$ $$ (5 < x) \land (y > 8) $$

Laws of Boolean Algebra

Boolean algebra contains many of the same laws found in algebra. Here is a table listing some of these laws with an example for each. If the law does not list a specific operation, then in general it will work for all operations.

Name Example
Associative $ x \land (y \land z) \iff (x \land y) \land z $
Commutative $ x \land y \iff y \land x $
Idempotence $ x \land x \iff x $
Distributive $ \land $ over $ \lor $ $ x \land (y \lor z) \iff (x \land y) \lor (x \land z) $
Distributive $ \lor $ over $ \land $ $ x \lor (y \land z) \iff (x \lor y) \land (x \lor z) $
Identity $ \land $ $ x \land T \iff x $
Identity $ \lor $ $ x \lor F \iff x $
Eliminate $ \land $ $ x \land F \iff F $
Eliminate $ \lor $ $ x \lor T \iff T $
Complement $ \land $ $ x \land \neg x \iff F $
Complement $ \lor $ $ x \lor \neg x \iff T $
Double Negative $ \neg(\neg x) \iff x $
Or Absorption $ x \lor (x \land y) \iff x $
And Absorption $ x \land (x \lor y) \iff x $

De Morgan’s Laws

YouTube Video

Video Materials

Augustus De Morgan Augustus De Morgan1

There is one rule, not listed above, where Boolean Algebra differs greatly from other forms of algebra. That is how it deals with the negation, or inverse, of an entire statement. For this, we refer to the work of Augustus De Morgan, pictured above. De Morgan was expanding upon the work of George Boole, and published some additional laws for Boolean Algebra, which we collectively refer to as De Morgan’s Laws.

In Algebra, when we negate an entire statement, we must change the signs of each number in the statement. Consider the example below:

$$ -(x + y) \iff -x + -y $$

However, in Boolean Algebra, the same rules does not quite work:

$$ \neg(x \land y) \neq (\neg x) \land (\neg y) $$

For example, if we assign the value True to $ x $ and False to $ y $, we would be able to reduce the expression in this way:

$$ \neg(x \land y) \neq (\neg x) \land (\neg y) $$ $$ \neg(T \land F) \neq (\neg T) \land (\neg F) $$ $$ \neg(F) \neq F \land T $$ $$ T \neq F $$

As we can see, the two statements are not equal. Instead, De Morgan’s Laws tell us that we must also invert the operation, changing $ \land $ to $ \lor $ and vice-versa. Let’s look at a corrected version of the above example:

$$ \neg(x \land y) \iff (\neg x) \lor (\neg y) $$ $$ \neg(T \land F) \iff (\neg T) \lor (\neg F) $$ $$ \neg(F) \iff (F) \lor (T) $$ $$ T \iff T $$

So, we must add the following two laws to our table above:

Name Example
De Morgan’s Law $ \land $ $ \neg(x \land y) \iff (\neg x) \lor (\neg y) $
De Morgan’s Law $ \lor $ $ \neg(x \lor y) \iff (\neg x) \land (\neg y) $

That gives us a full suite of laws we can use when working in Boolean algebra.

Daily Use of DeMorgan’s Law

We use DeMorgan’s all the time. When we say x is between 1 and 10, we can state that as either $ x \geq 1 \land x \leq 10 $ or $ \neg(x < 1 \lor x > 10) $. DeMorgan’s law provides the mathematical proof of this.


  1. File:De Morgan Augustus.jpg. (2018, January 1). Wikimedia Commons, the free media repository. Retrieved 19:51, January 10, 2019 from https://commons.wikimedia.org/w/index.php?title=File:De_Morgan_Augustus.jpg&oldid=275794962↩︎

Subsections of Boolean Algebra

A Worked Example

YouTube Video

Video Materials

Now that we’ve learned about many of the laws of Boolean algebra, let’s go through a worked example, just to see how we can find a Boolean expression from a truth table. Programmers perform this process many times while writing code, since most programs include several boolean expressions. Those expressions control how the program performs in certain situations, such as when to repeat a block of code or when to choose between multiple blocks of code.

Problem Statement

For this problem, let’s say we are working on a program to control smart lights. That program should turn the lights on if the light switch in the room is in the “ON” position. Similarly, it should turn on the lights if it detects there are people in the room using a motion sensor, regardless of whether the light switch is “ON” or “OFF”. However, it should respect a master switch, such that if the master switch is off, the lights can not be turned on at all.

How can we write a program that uses a Boolean expression to determine when to turn the lights on?

Truth Table

The first step is to create a truth table that describes the possible inputs and the desired outputs. For this example, we’ll assign input A to the light switch, input B to the motion sensor, and input C to the master switch. Using that, we can construct our truth table:

A B C Output
F F F F
F F T F
F T F F
F T T T
T F F F
T F T T
T T F F
T T T T

In this table, we see that there are exactly three situations that cause the lights to be turned on, which is where the Output column contains True. They are highlighted in bold in this example. First, on the fourth line of the table, if the motion sensor input B is True, and the master switch input C is also True, then the output should be True. Similarly, the sixth line shows that if the light switch A is True and the master switch C is True, then the output is True. Finally, the last line shows that if all inputs are True, the output is True and the lights should be on.

Does that cover all of the situations where the lights should be on? Most of the remaining lines are situations where the master switch C is False, so the lights should not be turned on in any of those situations. The only exception is the second line, where the master switch C is True, but none of the other inputs are True. In that case, the lights should remain off.

Venn Diagram

The next step is to make a Venn diagram. This step is not necessary, as we’ll see later, but it is a very helpful step to take when learning Boolean algebra for the first time. Each segment of the 3 variable Venn diagram corresponds to a particular line in the truth table, so we simply want to shade in each segment that corresponds to a line in the truth table where the output is True.

Let’s look at the first line where the output is True, which is the fourth line. In that line, we see that we need the part of the diagram that is contained in B and C, but not A. This is due to the fact that the inputs B and C are True, but input A is False on that line of the truth table. So, we’d shade in this segment of our Venn diagram:

Venn Diagram: B and C and Not A Venn Diagram: B and C and Not A

The next line in the truth table where the output is True is the sixth line. In this line, we need the part that contains A and C but not B:

Venn Diagram: A and C and Not B Venn Diagram: A and C and Not B

Finally, the last line in the truth table tells us that we must shade in the part of the diagram contained in all three circles, containing A and B and C together:

Venn Diagram: A and B and C Venn Diagram: A and B and C

Now, we can put those sections together to find our overall Venn diagram for this statement:

Venn Diagram: (A and C) or (B and C) Venn Diagram: (A and C) or (B and C)

So, we can see that there are three segments shaded in our Venn diagram, corresponding to three lines in the truth table where the output is True. This gives us a helpful visual way to look at our Boolean logic expression.

Boolean Expression from Venn Diagram

Now that we’ve created a Venn diagram for this example, we can use it to create our Boolean expression. For this step, we’ll need to break the shaded part of the Venn diagram down into parts we can describe, and put those pieces together in a way that they represent the diagram as a whole. Let’s see how that works.

First, looking at our diagram, one thing we might notice is that it can be broken up into two overlapping segments. The first segment is this one:

Venn Diagram: A and C Venn Diagram: A and C

This diagram shows the part of the diagram that is included in both A and C. This is exactly the same as the 2 variable Venn diagram we saw on the previous page, especially if we ignore the circle for B. So, this diagram represents the Boolean expression $ A \land C $. That’s one piece of the puzzle.

The other segment we might see is this one:

Venn Diagram: B and C Venn Diagram: B and C

Similarly, this diagram represents the Boolean expression $ B \land C $, since it is the part of the diagram that is included in both B and C.

If we overlap those two segments, we would see the following result (with overlapping segments shaded a bit darker):

Venn Diagram: (A and C) or (B and C) Venn Diagram: (A and C) or (B and C)

This is effectively the same diagram as we found above, isn’t it? That’s what we are aiming for!

So, we know that we need an expression that represents $ A \land C $ and $ B \land C $ overlapped. If we recall back to the basic Boolean operations, we should hopefully remember that the or operation does just that. So, we can use $ \lor $ to connect our two pieces, making the final Boolean expression:

$ (A \land C) \lor (B \land C) $

Now that we have our final expression, let’s go back to the problem statement and see if it matches. Recall that it says we would like the lights turned on if the light switch is on and the master switch is on, or if the motion sensor senses people and the master switch is on. If we replace the variables in the Boolean expression, we see the following:

$ (\text{Light Switch} \land \text{Master Switch}) \lor (\text{Motion Sensor} \land \text{Master Switch}) $

That is exactly what we want! So, we found the correct Boolean expression for our problem statement.

Shortcut to Booleans

Of course, once you’ve seen the end result and compared it to the problem statement, it may be easy to see how you could quickly go directly from the problem statement to a Boolean expression without creating a truth table or a Venn diagram. In fact, if the problem statement is clear enough, it may already be structured exactly like a Boolean expression itself.

In practice, many programmers become familiar enough with Boolean expressions that they can easily go directly from a problem statement to a matching Boolean expression without much effort. It may seem difficult at first, but you’ll quickly become familiar with that process as you continue to write programs. For now, don’t be afraid to take the time and work each example through until you become more comfortable with it.

It usually takes at least a year or more of coding practice before this process feels simple. So, don’t sweat it!

Boolean Expression from Truth Table

We can also derive our Boolean expression directly from the data in the truth table, along with some of the Boolean Algebra laws we covered earlier in this chapter. Let’s go through an example of how that process works, just to see that we can get the same result.

In our truth table, we see that we should have three situations that provide output. The first is when B and C are True, while A is False. So, we could write that as the Boolean expression $ \neg A \land B \land C $.

Similarly, we can do the same for the other two lines of output, which produce the expressions $ A \land \neg B \land C $ and $ A \land B \land C $, respectively.

Finally, we’d like our output to be True if at least one of those situations is True. So, we can use the or $ \lor $ operation to connect them together into a single statement:

$ (\neg A \land B \land C) \lor (A \land \neg B \land C) \lor (A \land B \land C) $

In fact, if we want, we can simply stop there. That statement is exactly equivalent to the one we found using the Venn diagrams above.

Further “Simplification”

However, it is very long and difficult to understand directly, and it doesn’t clearly reflect our problem statement. So, let’s see if we can reduce this expression using the laws of Boolean Algebra to an equivalent one that is easier to read.

First, we must use the law of Idempotence to duplicate one of our terms. From that law, we know that the following is true:

$ (A \land B \land C) \iff (A \land B \land C) \lor (A \land B \land C) $

So, we can replace the final term with this equivalent statement. We can do this here since it won’t affect the final outcome. It is the rough equivalent to adding $ 0 $ to an existing mathematical expression. So, our new expression is

$ (\neg A \land B \land C) \lor (A \land \neg B \land C) \lor (A \land B \land C) \lor (A \land B \land C) $

Next, we can use the law of Commutativity to rearrange the terms a bit. This is the same as rewriting $ 1 + 2 $ as $ 2 + 1 $, so it is a simple law to apply:

$ (\neg A \land B \land C) \lor (A \land B \land C) \lor (\neg B \land A \land C) \lor (B \land A \land C) $

We can also use the law of Associativity to add a few extra parentheses, just for clarity:

$ \bigg(\big(\neg A \land (B \land C)\big) \lor \big(A \land (B \land C)\big)\bigg) \lor \bigg(\big(\neg B \land (A \land C)\big) \lor \big(B \land (A \land C)\big)\bigg) $

Next, we can apply the inverse of the Distributive law on the first block of statements to “factor out” the statement $ (B \land C) $, leaving this result:

$ \big((B \land C) \land (\neg A \lor A) \big) \lor \bigg(\big(\neg B \land (A \land C)\big) \lor \big(B \land (A \land C)\big)\bigg) $

We can also do the same on the second block with $ (A \land C) $:

$ \big((B \land C) \land (\neg A \lor A) \big) \lor \big((A \land C) \land (\neg B \lor B)\big) $

Next, we can use the Complement law to reduce both $ (\neg A \lor A) $ and $ (\neg B \lor B) $ to True:

$ \big((B \land C) \land T \big) \lor \big((A \land C) \land T \big) $

Then, using the law of Identity, we know that $ x \land T \iff x $, so we can remove both instances of $ T $ in the expression:

$ (B \land C) \lor (A \land C) $

Once again, we can apply the law of Commutativity to rearrange the terms to reach this final result:

$ (A \land C) \lor (B \land C) $

There it is! We were able to use the laws of Boolean Algebra to prove that our Boolean expression found using the Venn diagrams is equivalent to the expression from the truth table. This process is very similar to the ones learned in an algebra class, but some of the laws are handled in a slightly different way.

Another Answer?

Here’s a quick challenge: can you use the laws of Boolean Algebra to reduce that statement even more? See if you can find the answer before reading below.

It turns out that you can! We can apply the inverse of the Distributive law one more time to get the following result:

$ C \land (A \lor B) $

This also works, and might even fit better with the original problem statement. If we restate it as “the light should be on if the master switch is on and either the light switch is on or the motion sensor detects people,” the problem statement clearly reflects this answer as well. Both are equally correct in this case.

Subsections of A Worked Example

An Alternative Approach

Venn Diagram Limitations

Not everyone finds the use of Venn diagrams easy or natural. Consider the following problem:

Determine if the value of the variable x is greater than 10, less than or equal to 30, and either divisible by both 2 and 3 or by 7.

A Venn diagram of this statement would look something similar to this example:

Venn Diagram Venn Diagram

Unfortunately, it does not result in a graphic that is easily translatable to a Boolean logic statement. So, we’ll probably have to take another approach.

Pseudocode

Instead, we can use the original problem statement as the structure to build a pseudocode representation that can eventually be translated to any programming language. We’ll begin by breaking down the original problem statement into smaller parts, as shown below:

Determine if the value of the variable x is: 
  greater than 10
  less than or equal to 30 
  and either  
    divisible by both 2 and 3
    or by 7.

We’ve also added in a bit of indentation, just to make it easier to see the various parts of the statement.

Clearly the ands and ors in the original statement can be replaced with their Boolean equivalents. We’ll use capitalized AND, OR and NOT operators in text. Also, we can deduce that statements in a comma separated list should also be combined with AND, so we can update our statement as follows:

Determine if the value of the variable x is: 
  greater than 10
  AND less than or equal to 30 
  AND either  
    divisible by both 2 AND 3
    OR by 7.

Next, we should try to make each statement closely resemble a written expression in code by adding in the implied subject of each statement and making them explicit. In most cases, we’ll add the variable x to the statement, as shown here:

Determine if the value of the variable x is: 
  x greater than 10
  AND x less than or equal to 30 
  AND either  
    x divisible by 2 AND x divisible by 3
    OR x divisible by 7.

Notice how the statement divisible by both 2 and 3 was split into two parts. Each Boolean logic comparator requires a variable on each side, so we cannot write x divisible by 2 AND 3 as a valid statement. Instead, we have to split it into two statements, x divisible by 2 AND x divisible by 3. We also have to expand the line OR by 7 to clarify that it is also checking if x is divisible by 7, so we updated it to be x divisible by 7.

Now we can plug in the associated Boolean comparators and mathematical operators. Recall that we can check if a number is divisible by another number using the modulo operator; if that value modulo the divisor is 0, then we know the number is evenly divisible by the divisor. We’re also going to use the double equals sign == to denote equality, which is used in most programming languages.

Determine if the value of the variable x is: 
  x > 10
  AND x <= 30 
  AND either  
    x % 2 == 0 AND x % 3 == 0
    OR x % 7 == 0.

Once we have that, we can go through and add in any missing parentheses. Notice that we originally used some tabs separate the various parts of the original statement, and those should closely align with where we need to add parentheses.

So, one possible way to interpret this statement is as follows:

x > 10 AND x <= 30 AND ((x % 2 == 0 AND x % 3 == 0) OR x % 7 == 0)

This line of pseudocode is certainly easier translate into a programming language than using the Venn Diagram!

Simplifying Boolean Algebra Statements

There is a technique in computer engineering used to express complicated Boolean expressions in the fewest number of operations. It is called the K-Map or Karnaugh Map technique. It is typically taught in introductory classes.

Certain applications areas of computer Science, particularly sub-fields such as artificial intelligence and control systems, end up producing long strings of Boolean algebra. Using both Karnaugh Maps and Boolean algebra techniques are used to reduce those statements.

Computer Circuits

While Boolean logic is very important when writing computer code, it is also a major part of working with computer hardware as well. In fact, most electronic hardware today uses chips and circuits that are built directly upon the fundamental building blocks of Boolean logic.

Relay and Switching Circuits

Claude Shannon Claude Shannon1

In 1937, a 21-year-old graduate student at MIT named Claude Shannon, pictured above, published his master’s thesis: “A Symbolic Analysis of Relay and Switching Circuits.” In that paper, he demonstrated how to build electric circuits which could perform all of the standard Boolean logic operations, using the presence or absence of an electric current or voltage to represent true and false in those circuits.

In addition, his work demonstrated that multiple circuits can be combined to perform simple mathematical operations on binary numbers, such as addition, subtraction, and even multiplication!

It is difficult to overstate the importance of Claude Shannon’s work in today’s modern world. Every electronic device we use is built upon the same fundamental concepts laid out by Shannon in the 1930s. In fact, one researcher described his work as “possibly the most important, and also the most famous, master’s thesis of the century.”2

Addition using Boolean Logic

Let’s look at an example of how Boolean logic can be used to perform mathematical operations. Here is a simple truth table showing addition using two binary bits as input. In this case, $ 0 $ represents false and $ 1 $ represents true:

A B Carry (and) Sum (xor)
0 0 0 0
0 1 0 1
1 0 0 1
1 1 1 0

The addition itself is performed by the xor operation. For example, if both inputs are $ 0 $, representing false, then the sum is $ 0 + 0 = 0 $, which is false as well. However, if one input is $ 1 $ and the other is $ 0 $, then the sum is $ 0 + 1 = 1 $ (which is the same as $ 1 + 0 = 0 $), so the xor operation would output true. In both of those cases, the carry bit is $ 0 $, since the sum does not require a bit to be carried forward to the left.

The interesting case is when both inputs are $ 1 $. In this case, the result is $ 1 + 1 = 2 $, where $ 2 $ is represented by $ 10 $ in binary. So, the sum result, represented by xor is false, but the carry bit, represented by the and operation, is true. So, the result would be $ 10 $, represented by the carry bit followed by the sum.

So, we can represent the mathematical addition operation $ A + B $ using these two Boolean logic expressions:

  • Sum: $ A \oplus B $
  • Carry: $ A \land B $

The corresponding circuit for this operation is known as a half adder.

We can then expand this truth table to include a third input, which is the carry bit from a previous addition.

A B Carry In Carry Out Sum
0 0 0 0 0
0 0 1 0 1
0 1 0 0 1
0 1 1 1 0
1 0 0 0 1
1 0 1 1 0
1 1 0 1 0
1 1 1 1 1

From this truth table, we can find the following Boolean logic expressions for the sum and carry bits:

  • Sum: $ (A \oplus B) \oplus C $
  • Carry: $ (A \land B) \lor (B \land C) \lor (A \land C) $

This circuit is known as a full adder.

So, given two binary bits as inputs $ A $ and $ B $, along with the carry bit from a previous addition $ C $, we can use these two Boolean expressions to find both the sum and carry bits as output.

To find the sum of larger numbers, we can simply chain together multiple instances of these full adder circuits together, moving through the binary number from left to right, just like we do for normal addition. In modern computers, the central processing unit, or CPU, contains circuits very similar to these to perform each mathematical instruction required to execute a program.

Wikibooks has a great page dedicated to this topic for additional information: Practical Electronics/Adders


  1. File:ClaudeShannon MFO3807.jpg. (2018, December 28). Wikimedia Commons, the free media repository. Retrieved 19:09, January 14, 2019 from https://commons.wikimedia.org/w/index.php?title=File:ClaudeShannon_MFO3807.jpg&oldid=332553066↩︎

  2. See https://en.wikipedia.org/wiki/A_Symbolic_Analysis_of_Relay_and_Switching_Circuits#cite_note-2 ↩︎

Chapter 7.J

Java Boolean Logic

Boolean Logic in Java

Subsections of Java Boolean Logic

Booleans in Java

The Java programming language supports Boolean values as a primitive data type named boolean. It also includes Boolean keywords true and false to represent each possible value. Notice that these keywords are not capitalized, unlike some other programming languages.

To declare a Boolean variable in Java, we can use the same basic syntax that we used for other variable types:

boolean b;

We can then assign a value of true or false to that variable:

boolean b;
b = true;

We can also combine the two statements into a single statement:

boolean b = false;

Boolean Operators

Java also supports most of the Boolean operators discussed earlier in the chapter. Let’s look at a few examples.

Not !

In Java, the not operator is the exclamation point !, placed before a Boolean value. It will invert the value of the variable, changing true to false and false to true.

Here is a quick example:

boolean b = true;
boolean c = !b;
System.out.println(c); // false

This program will output false, which is the inverted value of b.

And &&

To perform the and operation, Java uses two ampersands && placed between Boolean values. Let’s look at an example:

boolean a = true;
boolean b = true;
boolean c = false;
System.out.println(a && b); // true
System.out.println(b && c); // false

This program will output true on the first line, since we know that both a and b are true. On the second line, it will output false, since c is false, even though b is true.

Or ||

Similarly, Java uses two vertical pipes || to represent the or operator. On most keyboards, we can find that key above the Enter key. Here’s an example of how it can be used in code:

boolean a = true;
boolean b = false;
boolean c = false;
System.out.println(a || b); // true
System.out.println(b || c); // false

Once again, this program will output true on the first line, since a is true, even though b is false. On the second line, it will output false, since both b and c are false.

Short-Circuit Operators

According to the Java documentation, Java also supports using a single & for and, and a single | for or when dealing with Boolean values. Why shouldn’t we just use these operators instead?

It turns out that there is a fundamental difference in how the Java compiler handles these operators. The double-character operators && and || are called logical operators and short-circuit operators. In some scenarios, the system only needs to look at the first value in the statement to determine the result. For example, the statement x || (y || z) will always be true if x is true, without needing to consider the values in y or z. The same works for x && (y && z) when x is false, which will result in the entire statement being false. For larger Boolean expressions, the use of these short-circuit operators can make our programs run much faster and more efficiently.

The single-character operators & and | bit-wise comparison operators and are not short-circuited. So, the system will evaluate each part of the statement before determining the outcome. For boolean values, the bit-wise operators will evaluate to the same answer as the logical operators.

However you should not use them for this purpose. First, because they will slow your programs executions. Second, because it will obscure your intent to future readers of your program. At some distance point in the future, a programmer will see you used a bit-wise operator, assume there must be a reason you did not using the logical one, and lose valuable time trying to figure out why you did not use the logical operator.

You can read more about Bitwise Operations on Wikipedia. We will not use them in this course.

Exclusive Or

Java does not support a logical exclusive or, but we can build a similar statement using other operators.

boolean a = true;
boolean b = false;
boolean c = true;
System.out.println((a || b) && !(a && b)); // true
System.out.println((a || c) && !(a && c)); // false

In this example, the first line will be true, since a is true but b is false. However, the second line will output false, since both a and c are true .

Comparison Operators

We can also use the comparison operators ==, !=, <, <=, >, and >= to compare variables containing numbers, which will result in a Boolean value. Here’s an example showing those operators in action:

int x = 1;
int y = 2;
double z = 3.0;

System.out.println(x < y);  // true
System.out.println(x <= 1); // true
System.out.println(x > 2);  // false
System.out.println(x >= z); // false

System.out.println(x == 1); // true
System.out.println(x != y); // true

As we can see, each of the comparison operators works just as we’d expect, and outputs a Boolean value of either true or false, depending on the result of the comparison.

Chaining Operators is Not Allowed

Like most high level languages, Java does not allow the chaining of comparison operators. 10 <= x <=20, which is a pretty standard math notation for x is between 10 and 20, will not work. First the compiler will evaluate 10 <=x as a boolean, then it will throw a fit about trying to compare a boolean and and int for the <= 20 part.

You will need to write this as 10 <= x && x <= 20.

Order of Operations

Now that we’ve introduced some additional operators, we should also see where they fit in with the other operators in Java. Here is an updated list giving the appropriate operator precedence for Java, with new entries in bold:

  1. Parentheses
  2. Postfix: Increment ++ & Decrement -- after variable*
  3. Prefix: Inverse -, Not !, Increment ++ & Decrement -- before variable*
  4. Multiplicative: Multiplication *, Division /, and Modulo %
  5. Additive: Addition +, Subtraction -
  6. Relational: <, >, <=, >=
  7. Equality: ==, !=
  8. Logical And: &&
  9. Logical Or: ||
  10. Assignment: =, +=, -=, *=, /=, %=

Subgoals Review

Working with Boolean expressions in Java is the same as working with any other type of expression. So, we can use the same subgoals we learned in a previous chapter to help us evaluate and write new Boolean logic expressions. Here’s a quick review of those subgoals.

Analyzing Expressions

To analyze an expression involving variables and mathematical operators in code, here are some mental subgoals we can use:

1. Check Types

First, we must determine whether the data type of the expression is compatible with the data type of the variable we want to store it in. In Java, we must be very careful to make sure we are only storing whole numbers in the integer data type, and floating point values in the double data type.

2. Perform Prefix Operations

Next, we should look at the expression to determine if there are any prefixed operations that must occur first. In Java, for example, we could find a prefixed increment operator like ++x, so we’ll need to update the value of x before moving to the next step.

3. Solve Arithmetic Equation

At this point, we can solve the arithmetic equation using the order of operations for our language. This simply involves the process of substituting values for variables and performing the requested operations. However, once again we must be careful to make sure that the operands provided to each operator are valid and produce the correct data type. The example we saw earlier for handling a large equation showed us a great way to work through this process.

4. Confirm Data Type of Result & Store It

Once we’ve solved the arithmetic equation, we should be left with a variable on the left side of the equals sign and a single value on the right side. So, once again, we should confirm that the value on the right can be stored in the data type of the variable on the left.

5. Perform Postfix Operations

Finally, if the original expression included any postfix operations, such as a postfixed decrement like x-- in Java, we’ll need to update the value of x before moving on to the next line.

Writing Expressions

Here are the subgoals for writing a new expression:

1. Find Variable in Problem Statement

The first step is to determine which part of the problem statement will be represented by a variable. Sometimes this is obvious, and other times it is not. This may be a new variable that we are creating, or it could be an update to an existing variable.

2. Determine Name and Data Type of Variable

Once we’ve found a variable to work with, we must also determine the variable’s name and data type. Once again, this may be obviously found in the problem statement, but other times we must think a bit more about what type of data will be stored in the variable. Of course, we should also make sure the variable has a descriptive and memorable name if we are creating a new one.

3. Build Arithmetic Equation with Operators

Now that we’ve isolated our variable, we must build an arithmetic equation and operators required to produce the desired value. This may involve using additional variables in our equations as well.

4. Build Expression

Once we have our arithmetic equation, we can then build the overall expression. This usually consists of three parts: the variable on the left, an assignment operator in the middle, and the arithmetic equation on the right.

5. Make sure Operators and Operands are Compatible

Finally, once we’ve constructed the overall expression, we should check and make sure that all operators and operands are compatible. This means making sure we are using the correct operators and conversions to produce the desired data type as output.

Example

Let’s look at an example to see how we can use these steps to create an evaluate a Boolean expression to match a problem statement.

Consider the following problem statement:

Create a program to determine if a user has exactly 5 apples, or fewer oranges than bananas. If so, the program should output true, otherwise it should output false. For this program, assume the user has 4 apples, 6 oranges, and 8 bananas.

Let’s go through the subgoal steps above to write this program.

  1. First, we can read the problem statement to see that we should have at least three variables - one for apples, oranges, and bananas. In addition, we may need a fourth variable to store the Boolean result that we’d like to output.

  2. The second subgoal is pretty straightforward. We can easily create three integer variables, apples, oranges, and bananas for each type of fruit, and a Boolean variable result to store the result of our Boolean logic expression.

  3. Next, we’ll need to build our arithmetic equation. For this program, we need to determine if the user has exactly 5 apples, or apples == 5. We also need to know if the user has fewer oranges than bananas, or oranges < bananas. Finally, we can put those together using the or operator as indicated in the problem statement, so the final equation would be (apples == 5) || (oranges < bananas).

  4. Now we can build our program itself. Here’s one possible solution:

int apples = 4;
int oranges = 6;
int bananas = 8;

boolean result = (apples == 5) || (oranges < bananas);

System.out.println(result);
  1. Finally, we can verify that the operators and operands are compatible. Specifically, on either side of the or operator ||, we see that each side is a smaller expression that will result in a Boolean value, so the data type of each operand will be correct.

Using subgoals makes it very easy to work through this process, one step at a time.

Summary

In this chapter, we learned all about Boolean logic, the corresponding rules for Boolean algebra, and how we can use those concepts to create our own Boolean logic expressions that we can use in our computer programs.

In the next chapters, we’ll see how we can use these expressions to control how our programs operate with programming constructs that allow our code to select between different branches or even repeat instructions as needed.

Chapter 4

Conditionals

If, Then, or Else!

Subsections of Conditionals

Programs as Flowcharts

We’ve created many different computer programs by now, but they have all had one thing in common: they only have a single execution path in the program. This means that, each time we run the program, we’ll execute the exact same pieces of code and perform the same operations on each variable. We may have different initial values stored in those variables, but that is really the only difference.

To better visualize this, we can actually think of the execution path of a program just like a flowchart. For example, here is a flowchart showing the execution path of a program that asks the user to input a number and then prints the square of that number.

Squared Program Flowchart Squared Program Flowchart

However, what if we’d like our programs to be able to perform different actions, depending on the user’s input? Wouldn’t that be useful?

Branching Constructs

Let’s take a look at a second flowchart. For this program, we’ll have the user input a number. If the number is even, we’ll output even, but if it is odd, we’ll output odd. Let’s look at what this program might look like as a flowchart:

Even Odd Program Flowchart Even Odd Program Flowchart

This flowchart uses a diamond-shaped block to indicate a decision we’d like our computer program to make. Inside the block, we see the Boolean logic expression x % 2 == 0, which will determine whether x is evenly divisible by $ 2 $. If so, the result of the modulo operation would be $ 0 $, so the entire statement would evaluate to true, indicating that x is an even number.

We also see two arrows coming from this block, one on the left if the statement is false, and another, on the right, if the statement is true. Each path leads to a different output, before being joined together at the end.

Nearly every programming language supports a method for running different parts of code based on the result of some Boolean logic expression, just like in this example. Collectively, these methods are known as conditional constructs, sometimes referred to as conditional statements or just conditionals. In the example above, the diamond-shaped block represents a conditional construct in that program.

Complex Decisions

YouTube Video

Video Materials

Of course, we can make even more complicated decisions in our programs. Let’s take a look at one more flowchart and see if we can understand what this program does:

Big Flowchart Big Flowchart

At first glance, this flowchart may seem quite confusing. However, by looking closely at the decisions it makes and the corresponding output, we should realize that it is a program that plays the common “Rock, Paper, Scissors” game.

First, the program accepts two inputs, representing the symbols chosen by each player, and stores them in variables p1 and p2. Then, it checks to see if the inputs are equal. If so, the program declares the game to be a tie by outputting tie, and then it ends. If the inputs are not equal, then it can determine a winner.

Instead of trying to describe how the rest of the program works, let’s look at a few possible inputs and trace the program’s execution through the flowchart, just to see what it does.

First, let’s look at the case when player 1 chooses “rock” and player 2 chooses “paper”. So, our variables would be p1 = "rock" and p2 = "paper". Here’s a trace of the path that input would follow through the flowchart:

Rock Paper Flowchart Rock Paper Flowchart

As we can see, first the program will check to see if the inputs are equal. Since they are not, it will follow the false branch to the left. Then, it will determine if player 1’s input was "rock". Since that is the case, it will follow the true branch to the right. After that, the program will test if p2 == "paper", which is also true. So, the program will follow the right branch, and output p2 wins. We can see that this output is indeed correct, since the rules of the game states that “paper covers rock”, meaning that player 2’s choice of paper will beat player 1’s choice of rock.

Let’s look at one other example. This time, player 1 chooses “scissors” and player 2 chooses “paper”. Here’s a flowchart representing our program with those inputs:

Scissors Paper Flowchart Scissors Paper Flowchart

In this example, the program will first decide if the inputs are equal. They are not, so the program will once again follow the false branch to the left. Then, it will test to see if player 1’s input was "rock". In this case, the user input "scissors", so this test is false, and the program will choose the false branch to the left.

Next, it will try to determine if player 1’s input was "paper". Since that is also not the case, it will follow the false branch to the left once again.

At this point, our program knows that player 1 did not input either "rock" or "paper", so it can assume that the input must be "scissors". So, it will then check to see if player 2’s input was "rock". This is also false, so it will choose the false branch once again.

From earlier, it knows that player 2’s input is different from player 1, and player 2 did not input "rock". Since player 1’s input is assumed to be "scissors", that means that player 2 must have chosen "paper" as it is the only valid input left.

Therefore, this program has determined that player 1’s input was "scissors" and player 2’s input was "paper". By the rules of the game, “scissors cuts paper”, so player 1 wins. Our program will correctly output p1 wins!

Making Assumptions & Inferences

In the examples above we made a very important assumption, which allowed us to infer information about each user’s input. Can you find a possible problem with the programs as shown in the diagrams above?

In those examples, we assumed that the users would only input either "rock", "paper", or "scissors". Based on that assumption, if we have determined that a player’s input was neither "rock" nor "paper", as we did in the last example, then we can infer that the user must have chosen "scissors".

However, we did not specify how that assumption was enforced in our diagram. In a real-world program, how to handle bad input is often specified, which can lead to additional decision boxes in the flowchart that would test each user’s input against a list of valid inputs before accepting it. This would make the flowchart significantly larger and more complex, which is why they were omitted for this example.

As you complete the programming examples and projects in this course, you’ll need to pay special attention to the program’s specifications and any assumptions made about possible user inputs. It will often be the case that there is no instructions given for handling bad input. In this case, our program should do nothing since it wasn’t specified. Adding unspecified features is a bad programming practice, so we want to avoid adding any instructions that are not explicitly listed in the program specification. In a formal development process, a “bug” would be opened to fix the specification first, and then we would update the code to match the new specification.

In most cases, it is always better to specify how a program handles invalid input instead of relying on assumptions about what your users may or may not provide as input.

The customer may always be right, but users should never be trusted to follow instructions!

As we can see, the ability to make decisions in a computer program is one of the most important building blocks we need to build more elaborate and complex programs. In this chapter, we’ll learn about all of the different ways we can write computer programs to make decisions and perform different actions based on those decisions using conditional constructs.

Subsections of Branching Constructs

If Statements

The first type of conditional construct we’ll cover is the if statement. This type of statement allows us to build a block of code which will only be executed if the given Boolean expression evaluates to true.

Here’s a simple flowchart that shows an if statement as the conditional construct:

If Flowchart If Flowchart

This flowchart represents a program which will ask the user for a number as input, then print that number as output only if the number is greater than $ 0 $. Then, the program will print Goodbye and terminate. So, if the user inputs -5, the program will only print Goodbye. However, if the user inputs 7, then the program will first print 7 followed by Goodbye. As we can see, the program will execute the section of code that prints the value of the variable if and only if the value of x > 0 is true. Otherwise, it will skip that code and continue with the rest of the program.

Notice that this statement does not include any operations in the case that x > 0 is false. Instead, the false branch points downward, and merges with the true branch after the program outputs the value of x. This is the main difference between an if statement and an if-else statement, which we’ll cover on the next page.

Most programming languages implement an if statement in a format similar to this:

<before code block>
if <Boolean expression> {
    <if code block> or <True block>
    }
<after code block>

The program is initially executing code in the <before code block> section. Once it reaches the if keyword, it will then evaluate the <Boolean expression> to a value of either true or false. If the expression is true, then it will execute the code in the <if code block>, followed by the code in the <after code block>. If the expression is false, the program will simply continue executing code in the <after code block>, bypassing the other block entirely.

If vs. if-then

You may see the if statement referred to as the if-then statement. At the dawn of high level programming languages in 1957, one of the first languages developed was FORTRAN. FORTRAN was the first commercially available high level programming language, and it is still in use today in many scientific and mathematics heavy applications. Its syntax for this structure is

IF <condition> THEN <action>

This closely follows the first order logic representation <condition> --> <action>, or condition implies action. In propositional Logic, this is read to mean “if the condition is true, it follows that the action must also be true”.

In the early 1970s, a new high level programming language named C is developed, and it comes to dominate the programming landscape by the 1990s. C remains in active development, has a huge industry code base, and greatly influenced the syntax and style of later languages. Notably, C dropped the “THEN” part of the statement, and standardized the use of curly braces to separate blocks of code:

if (condition) {
    <action>
}

This sets the stage for one of the enduring arcane debates in computer science – which form is “correct”. Without arguing for which is best, we have chosen the style that most resembles Java and Python syntax.

If-Else Statements

Next, let’s look at the if-else statement. This statement allows us to choose between two different blocks of code, one to be executed when the Boolean logic expression evaluates to true, and a different block if the expression evaluates to false.

Here’s a simple flowchart that shows an if-else statement as the conditional construct:

If-Else Flowchart If-Else Flowchart

This program is very similar to the one shown on the previous page, with one major difference. Once again, this program will ask the user for a number as input, then print that number as output if the number is greater than or equal to $ 0 $. However, if the input value is less than $ 0 $, the program will instead print the inverse of that value by multiplying it by $ -1 $ before printing. Finally, the program will print Goodbye and terminate.

So, if the user inputs 7, the program will just print 7 followed by Goodbye. However, if the user inputs -5, then the program will calculate $ -1 * -5 $ and print 5, followed by Goodbye. As we can see, the program will choose which block of code to execute depending on the Boolean value that results from evaluating x >= 0. Once it has executed one of those two blocks, then the program will continue with the rest of the code.

Most programming languages implement an if-else statement in a format similar to this:

<before code block>
if (Boolean expression) { 
    <if code block> or <True code block>
}
else {
    <else code block> or <False code block>
}
<after code block>

The program is initially executing code in the <before code block> section. Once it reaches the if keyword, it will then evaluate the <Boolean expression> to a value of either true or false. If the expression is true, then it will execute the code in the <then code block>, followed by the code in the <after code block>. If the expression is false, the program will execute code in the <else code block>, followed by the code in the <after code block>.

It is important to note that it is impossible for the program to execute both the <True code block> and the <False code block> during any single execution of the program. It must choose one block or the other, but it cannot do both. So, if we want each block to perform the same action, we’ll have to include the appropriate code in both blocks.

Other Branching Statements

Many programming languages also provide other conditional constructs that perform these same operations in different ways. However, each of these constructs can also be built using just if and if-else statements, so it is not necessary to use these constructs themselves to achieve the desired result. In many cases, they are simply provided as a convenience to the programmer in order to make the code simpler or easier to understand.

Switch

Some programming languages, such as C and Java, include a special type of conditional construct known as a switch statement, sometimes referred to as a case statement. Instead of using a Boolean value, which can only be true or false, a switch statement allows our program to choose a block of code to execute based on the many possible values of a variable, traditionally an integer value.

This flowchart shows what a switch statement might look like in a program:

Switch Statement Flowchart Switch Statement Flowchart

In this example, the program will examine the value of the variable x and use it to choose a case, which is a code block that would be executed. Here, it would simply output a different response, depending on the value of x. Notice that the flowchart is essentially using several if statements to accomplish this task, which is essentially what the computer would do when executing this program. In addition, the program includes a special default case, which is executed if the value of x does not match any of the other cases.

In code, a Switch Statement may have this general structure:

switch <variable>:
  case <value 1>: <code block 1>
  case <value 2>: <code block 2>
  case <value 3>: <code block 3>
  ...
  default: <default code block>

Depending on the programming language used, it is possible to execute multiple code blocks inside a switch statement, so we may need to consult the documentation for our chosen programming language to understand how these statements work.

Ternary Conditional Operator

Many programming languages also include a special operator, known as a ternary conditional operator, sometimes referred to simply as a ternary operator, that is effectively a shortcut for a simple if-else statement that produces a single value. Here’s a flowchart showing an example:

Ternary Operator Flowchart Ternary Operator Flowchart

In this example, the program accepts two variables, x and y, as input. Then, it will set the value of a third variable, z, to the maximum value of x and y. It does so by testing if x > y. If so, it will set z = x; otherwise it will set z = y.

In general, there are two different ways that this operator is implemented. In Java and other programming languages similar to C, it looks like this example:

<variable> = <boolean expression> ? <true expression> : <false expression>

In Python, the ternary conditional operator looks just a bit different:

<variable> = <true expression> if <boolean expression> else <false expression>
Convention & The Ternary Operator

The appropriate use of the ternary operator is hotly debated. If it is used, it should be used to make reading the code more clear. The code a = a if a > 0 else -a can be understood to find the absolute value of a.

Some monstrosity like a = foo(b) >= bar (c) ? f(b>c?2.0 * c:b+1) : g(c)!=1 ? ++c:c+1; is better written in if-else statements.

We suggest that if you use the ternary operator, be very judicious in its application. You may want to read the article in Agile Software Craftsmanship for more discussion.

Remember the goal is readable, understandable code, not diabolically clever code.

Chapter 6.J

Java Conditional Statements

Conditional Statements in Java

Subsections of Java Conditional Statements

If Statement

Now that we’ve covered many different types of conditional constructs, let’s dive right in and see how they can be used in Java.

First, let’s look at the if statement. In Java, the syntax for an if statement is shown below:

if (<Boolean expression>) {
    <true block>
}

As expected, Java will first evaluate the <Boolean expression> to a single Boolean value. If that value is true, it will execute the instructions in the <true block>, which can be one or more lines of code, or even additional constructs as we’ll see later. If that value is false, then the program will simply skip over the <true block> and continue executing the code immediately below the if statement.

It is very important to remember that the Boolean expression is enclosed by parentheses ( ), while the block of code that should be executed if that expression is true is enclosed by curly braces { }, just like a class body or method body.

Let’s take a look at a few code examples, just to see how this construct works in practice. First, let’s consider the program represented by this flowchart from earlier in the chapter:

If-Then Flowchart If-Then Flowchart

This flowchart corresponds to the following code in Java. In this case, we’ll assume x is hard-coded for now:

int x = 1;
if (x > 0) {
    System.out.println(x);
}
System.out.println("Goodbye");

As we can see, this program uses x > 0 as the Boolean expression inside of the if statement. If it is true, then it will output the value of x. So, it is very simple to write code that matches the execution paths shown in a flowchart representing the program.

Here’s one more example. Let’s assume we’d like to write a program that will calculate both the sum and product of two variables, x and y, but only if they are both greater than 0. That program may look like this:

int x = 5;
int y = 7;
if ((x > 0) && (y > 0)) {
    int sum = x + y;
    int product = x * y;
    System.out.println("Sum: " + sum);
    System.out.println("Product: " + product);
}

As we can see, we can create more complex Boolean statements using the various Boolean operators we’ve already learned. In addition, we can include multiple lines of code inside the curly braces.

If-Else Statement

The if-else statement in Java is very similar to the if statement. In Java, the syntax for an if-else statement is shown below:

if (<Boolean expression>) {
  <true block>
} else {
  <false block>
}

As expected, Java will first evaluate the <Boolean expression> to a single Boolean value. If that value is true, it will execute the instructions in the <true block>, which can be one or more lines of code, or even additional constructs as we’ll see later. If that value is false, then the program will execute the code in the <false block> instead. In essence, the if-else statement simply adds a second code block and the else keyword after an if- statement.

Let’s take a look at a few code examples, just to see how this construct works in practice. First, let’s consider the program represented by this flowchart from earlier in the chapter:

If-Then Flowchart If-Then Flowchart

This flowchart corresponds to the following code in Java. In this case, we’ll assume x is hard-coded for now:

int x = 1;
if (x >= 0) {
  System.out.println(x);
} else {
  System.out.println(-1 * x);
}
System.out.println("Goodbye");

As we can see, this program uses x >= 0 as the Boolean logic expression inside of the if-else statement. If it is true, then it will output the value of x. If it is false, the program will output the value (-1 * x), which represents the inverse of x.

Here’s one more example. In this program, we’d like to calculate the difference between two numbers, but we’d only like to output a positive number. So, our code may look something like this:

int x = 3;
int y = 8;
if (x > y) {
  int difference = x - y;
  System.out.println(difference);
} else {
  int difference = y - x;
  System.out.println(difference);
}

In this program, we are simply checking to see if x > y. If so, we know that x - y will be a positive number. Otherwise, we can assume that y - x will be either a positive number or $ 0 $. In either case, we see that this program will produce the correct output.

Missing Curly Braces?

In Java, it is possible to have an If or If-Else statement without curly braces. In that case, the next line of code immediately following the if or else will be the only line considered inside of that branch.

Consider this code for example:

int x = 4;
if (x == 5)
System.out.println(true);
else
System.out.println(false);

This code is valid, and will compile and run properly. However, it is very difficult to read because of the indentation (or lack thereof).

In addition, if we want to add another line of code to each branch, we might accidentally do the following:

int x = 4;
if (x == 5)
x = 0;
System.out.println(true);
else
x++;
System.out.println(false);

This code will not compile and run properly, because the else statement cannot be attached to the appropriate if statement. So, we’d need to add curly braces to make this code make sense to the compiler.

Beyond that, it can be very difficult to read code that is not properly indented, regardless of the use of curly braces. So, it is a best practice to always include curly braces in your conditional statements and indent each block, even if those changes aren’t necessarily required in some cases.

Variable Scope

Now that we’ve learned how to create new code blocks in our programs using constructs such as the if and if-else statements, we must take a minute to discuss one of the major limitations of those code blocks.

The scope of a variable refers to the possible areas in a program’s code where that variable can be accessed and used. This is very important to understand once we begin introducing additional code blocks in our programs, because variables declared inside of a code block cannot be accessed outside of that block.

Local Variable Scope in Java

In general, Java follows these rules when determining if a local variable is accessible.

  1. A local variable in Java may not be accessed before it is first declared.
  2. A local variable in Java may not be accessed outside of the code block in which it is declared.
  3. A local variable in Java may be accessed inside of any code blocks placed inside of the code block in which is declared.
  4. A local variable in Java may not have the same name as an existing variable contained in an enclosing code block.

Later on in this course we’ll learn about class member variables, which have some different rules that govern their scope. For now, we’ll only worry about local variables, which are variables declared within a method body.

Accessing Before Declaring

Let’s look at a few examples for each rule. First, a variable may not be accessed before it is declared. So, we cannot do the following:

public static void main(String[] args){
  x = 5;
  int x;
}

If we do, the compiler will output an error similar to the following:

error: cannot find symbol
    x = 5;
    ^
  symbol:   variable x

Instead, we must make sure the variable is declared before it is accessed, as in this correct example:

public static void main(String[] args){
  int x;
  x = 5;
}

Accessing Outside Code Block

Next, here’s an example where the code is trying to access a variable outside of the code block where it is initially declared:

public static void main(String[] args){
  int x = 5;
  if (x < 10) {
    int y = x + 5;
  }
  System.out.println(y);
}

Once again, the compiler cannot find the variable, and we’ll get an error similar to the following:

error: cannot find symbol
  System.out.println(y);
                     ^
  symbol:   variable y

If we think about this error, it actually makes sense. What if the value of x is greater than $ 10 $? In that case, the program will never execute the line declaring the variable y, so it won’t even exist. Therefore, if we’d like to solve this problem, we can try to declare our variable outside of the if statement’s code block, as in this second example:

public static void main(String[] args){
  int x = 5;
  int y;
  if (x < 10) {
    y = x + 5;
  }
  System.out.println(y);
}

However, this example will also cause the compiler to give us an error:

error: variable y might not have been initialized
  System.out.println(y);
                     ^

In this case, we’ve declared the variable y, but we have not initialized it to a value. Once again, if the value of x is greater than $ 10 $, and the code block inside the if statement is not executed, we won’t know what value should be stored in y. So, the compiler will detect that error and warn us that y may not have been initialized. To solve this error, we simply must assign a value to y before attempting to use it:

public static void main(String[] args){
  int x = 5;
  int y = 0;
  if (x < 10) {
    y = x + 5;
  }
  System.out.println(y);
}

That example will compile and run as expected.

Surprisingly, the Java compiler is advanced enough to determine if a variable would be initialized by all possible paths, as in this example:

public static void main(String[] args){
  int x = 5;
  int y;
  if (x < 10) {
    y = x + 5;
  }else{
    y = x - 5;
  }
  System.out.println(y);
}

This example will compile and run without any problems, since the variable y is initialized in both blocks of the if-else statement. In short, there is no possible execution path that does not initialize y, so it is accepted by the compiler.

Accessing Inside a Code Block

We’ve already seen several examples of this already, but here’s a clear example of accessing a variable from a code block within the block where the variable was declared:

public static void main(String[] args){
  int x = -1;
  if (x < 0) {
    x = -1 * x; 
  }
  System.out.println(x);
}

In this example, the variable x is declared inside of the main method’s body. Then, it is accessed inside of the body of the if statement, which is itself within the body of the main method. Later in this chapter we’ll see examples of chaining and nesting conditional constructs, which will give us more examples of how this works.

Same Variable Names

Finally, Java does not allow us to use the same variable name twice in the same code block, or in any code blocks enclosed within that block. For example, we could try to do something like this:

public static void main(String[] args){
  int x = 5;
  if (x < 10) {
    int x = 15;
    System.out.println(x);
  }
}

If so, the compiler will detect that we’ve already declared variable x in the main method body, so it won’t allow us to declare it again inside the if statement’s body. Instead, it will give us the following error message:

error: variable x is already defined in method main(String[])
    int x = 15;
        ^

We can resolve this error by simply renaming one of the variables:

public static void main(String[] args){
  int x = 5;
  if (x < 10) {
    int y = 15;
    System.out.println(y);
  }
}

We may also choose to use the same variable without declaring it again:

public static void main(String[] args){
  int x = 5;
  if (x < 10) {
    x = 15;
    System.out.println(x);
  }
}

However, we’ll need to remember that it will change the value of the variable outside the block as well.

Lastly, while we cannot use the same variable name as an existing variable, we can use that name later on in our program, as long as there is not already a variable declared with the same name that is accessible. Here’s an example of that:

public static void main(String[] args){
  int x = 5;
  if (x < 10) {
    int y = 15;
    System.out.println(y);
  }
  int y = 12;
  System.out.println(y);
}

Surprisingly, this program will compile and run without any errors. In this case, we are allowed to declare a variable named y inside of the if statement’s code block because we have not yet declared a variable with that name anywhere in our program. Later, we are allowed to declare another variable named y, this time directly within the main method’s body. This is because we are outside of the if statement’s code block, so the previously declared variable named y no longer exists. So, we can reuse the name here without causing any problems.

However, this is widely regarded as poor coding practice, as it may make our code very difficult to read and understand. So, it is always better to try and avoid reusing variable names whenever possible, just to make it clear in our code which to variable we are referring.

Chaining & Nesting

YouTube Video

Video Materials

One of the most powerful features of the conditional constructs we’ve covered so far in this course is the ability to chain them together or nest them within each other to achieve remarkably useful program structures. The ability to use conditional constructs effectively is one of the most powerful skills to develop as a programmer.

Zero, One, Negative One

A great example of the many ways to structure a program using conditional constructs is building a simple program that does three things:

  1. If x is less than $ 0 $, output -1
  2. If x is equal to $ 0 $, output 0
  3. If x is greater than $ 0 $, output 1

Let’s look at two different ways we could structure this program using the conditional constructs we’ve already learned.

Try It!

See if you can build each of these examples in a file named Conditionals.java, either in Codio or on your own computer. Doing so is a great way to get additional practice working with conditional constructs.

Don’t forget to add the correct class declaration to the file as well! It is not included in the code examples below.

Chaining If Statements

First, we could chain together several if statements to create this program. As a flowchart, the program might look like this:

Chaining If Statements Chaining If Statements

Here’s one way that program could be written in Java. Once again, we’re just using hard-coded variables for now:

public static void main(String[] args) {
  int x = 3;
  if (x < 0) {
    System.out.println(-1);
  }
  if (x == 0) {
    System.out.println(0);
  }
  if (x > 0) {
    System.out.println(1);
  }
}

Just like we see in the flowchart, this program contains three if statements chained together, one after another. If we run this program with various inputs, we should see that it produces the expected result.

Nesting If-Else Statements

Next, we can achieve the same result using if-else statements. Here’s what that program would look like as as flowchart:

Nesting If-Else Statements Nesting If-Else Statements

We can also write that program in Java. Here’s one possible way to do it:

public static void main(String[] args) {
  int x = 3;
  if (x < 0) {
    System.out.println(-1);
  } else {
    if (x == 0) {
      System.out.println(0);
    } else {
      System.out.println(1);
    }
  }
}

In this example, we’ve nested an if-else statement inside of the second block of another if-else statement. So, if the first Boolean expression, x < 0, is true, we’ll output -1 and end the program. However, if it is false, we’ll go into the false branch of the first statement. Then, we’ll see another Boolean expression, x == 0. If that expression is true, we’ll output 0. Otherwise, we’ll output 1. Once again, this program should produce the correct result.

Of course, we could include a third if-else statement, as shown in this example:

public static void main(String[] args) {
  int x = 3;
  if (x < 0) {
    System.out.println(-1);
  } else {
    if(x == 0){
      System.out.println(0);
    }else{
      if(x > 0){
        System.out.println(1);
      }else{
        //this should never happen
        System.out.println("ERROR!");
      }
    }
  }
}

As we discussed earlier in this chapter, we can safely infer that x must be greater than $ 0 $ based on the two previous Boolean expressions. However, what if we’ve made a logic error in our program, and that assumption is not valid? By including the last if-else statement, we can have our program print an error in the unlikely chance that the value of x is not properly handled by any other option.

In fact, here’s how easy it is to cause just that sort of logic error. Consider the following example:

public static void main(String[] args) {
  int x = 3;
  if (x < -1) {
    System.out.println(-1);
  } else {
    if (x == 0) {
      System.out.println(0);
    } else {
      if (x > 1) {
        System.out.println(1);
      } else {
        //this should never happen
        System.out.println("ERROR!");
      }
    }
  }
}
Spot The Error

Can you spot the logic error in the example above? Try running the code with different values for x and see what happens.

In this example, we’ve updated the Boolean expression in the first if-else statement to x < -1. Similarly, we’ve changed the Boolean expression in the third statement to x > 1. That change seems logical, right?

What if the value of x is exactly $ 1 $ or $ -1 $? In that case, it will fail all three logic expressions, and the program will output ERROR! instead. By including the third if-else statement, we can detect logic errors that may not easily be found otherwise. Without that statement, the program would most likely output 1 even when the input is -1, which is clearly a negative number.

So, in many cases, it may be better to include additional if-else statements to explicitly check all possible cases, instead of relying on assumptions and inferences, which may lead to unintended logic errors.

Inline Nesting

Finally, it is acceptable to minimize nested if-else statements if the nested statement is exclusively in the false branch. Here’s an example:

public static void main(String[] args) {
  int x = 3;
  if (x < 0) {
    System.out.println(-1);
  } else if (x == 0) {
    System.out.pr intln(0);
  } else if (x > 0) {
    System.out.println(1);
  } else {
    //this should never happen
    System.out.println("ERROR!");
  }
}

Many programming languages refer to this structure as an if-else if-else statement. In this program, if the first Boolean expression is false, it immediately moves to the next Boolean expression following the first set of else if keywords. The process continues until one Boolean expression evaluates to true, or the final else keyword is reached. In that case, we know that none of the Boolean expressions evaluated to true, so the final false branch is executed.

While other programming languages include an explicit keyword for else if, Java does not. Instead, we are simply omitting the curly braces that surround the false block on each if-else statement. The Java compiler treats the entire if-else statement contained in that block as a single line, so the curly braces are not explicitly required in this case.

This is the one and only case where it is acceptable to remove those unnecessary curly braces in most Java style guides. Some programmers find this inline structure simpler to read and follow, as it avoids the problem of code being nested several layers deep. Others feel that it is dangerous to remove any explicit curly braces, and prefer the nested structure instead.

Communicating Mutual Exclusion

It should be obvious that only one branch of an if-else if-else will ever be executed, because the branches are mutually exclusive. It is a excellent practice to always use the if-else if-else structure whenever you know the logic should be exclusive. Consider a simple program we call the “Goldilocks Porridge Temperature Tester”.

if (isTooHot) { 
   System.out.println("porridge is too hot");
}
if (isTooCold) {
   System.out.println("porridge is not hot enough");
}
if (isJustRight) {
    System.out.println("porridge is perfect");
}

A future reader of the program, unfamiliar with the children’s tale, would see there are 3 boolean variables (isTooHot, isTooCold, isJustRight) and, depending on their values,up to 3-lines might get printed. This is because we cannot safely assume that just one of them will be true. In fact, they could all three be true!

However, if we rewrite the program to use if-else if-else instead of just if-else statements:

if (isTooHot){ 
   System.out.println("porridge is too hot");
}else if (isTooCold) {
   System.out.println("porridge is not hot enough");
}else if (isJustRight) {
    System.out.println("porridge is perfect");
}

the future reader would know that only one line should ever be printed.

Subsections of Chaining & Nesting

Switch Statement

Next, let’s look at the switch statement in Java. As we learned earlier, this statement allows our programs to choose branches based on any number of possible values of a variable. Here’s a flowchart showing what such a program might look like:

Switch Statement Switch Statement

In Java, we could write that program in many ways. This is one possible solution:

public static void main(String[] args) {
  int x = 2;
  switch (x) {
    case 1: System.out.println("A");
      break;
    case 2: System.out.println("B");
      break;
    case 3: System.out.println("C");
      break;
    default: System.out.println("Error!");
      break;
  }
}

In this program, the switch statement will evaluate the value of x, then look for the case keyword that exactly matches that value. In this example, x == 2, so it will choose the second case and output B.

If we change the value of x to $ 4 $, then we can see that none of the case keywords match. In that instance, the program will instead choose the default case and print Error!.

Fall Through

The switch statement above introduces a new keyword, break, which we’ll cover in detail in a later chapter. The break keyword causes the program to stop executing code in the current statement, and the continue executing the code following that statement. So, when the program reaches a break statement in the example above, it stops executing any additional code in the switch statement and continues running the code following that statement.

It is possible to create a switch statement that does not include break keywords. In that statement, it will continue executing any cases below the chosen case until it reaches the end of the statement or the default keyword.

For example, let’s say we’d like to write a program that will print all the numbers from a given starting number up to 5. So, we could use a switch statement to do that as in this program:

public static void main(String[] args){
  int x = 2;
  switch (x) {
    case 1: System.out.println("1");
    case 2: System.out.println("2");
    case 3: System.out.println("3");
    case 4: System.out.println("4");
    case 5: System.out.println("5");
      break;
    default: System.out.println("Error!");
      break;
  }
}

When we compile and run this program, we’ll receive the following output:

2
3
4
5

The program will start at case 2:, since x == 2, and print 2. Since there is not a break keyword in that case, it will continue to the next case, printing 3, then 4, then 5 before it finally reaches a break keyword.

Further Reading

Switch Statements are not used as often as other conditional constructs, but they can be a useful to a program in certain instances. There are many other unique ways they could be used. To learn more, refer to the official Java documentation on The switch Statement.

Ternary Statement

Java also includes the ternary conditional operator, which can be used as a shortcut for an if-else statement.

First, consider the flowchart we saw earlier in this chapter:

Ternary Operator Flowchart Ternary Operator Flowchart

In Java, this flowchart could be represented by the following code:

public static void main(String[] args){
  int x = 3;
  int y = 5;
  int z = (x > y) ? x : y;
  System.out.println(z);
}

In this program, the expression (x > y) ? x : y; is the ternary conditional operator. It first calculates the value of the Boolean expression (x > y). If that expression is true, then the entire expression evaluates to x. If it is false, then the expression evaluates to y. So, if we compile and run this program, it should output 5.

We can also include the ternary conditional operator anywhere we’d normally use a value. This is because, just like any other operator, the ternary conditional operator results in a single value when evaluated. For example, it could be used directly within the println() method:

public static void main(String[] args){
  int x = 3;
  int y = 5;
  System.out.println((x > y) ? x : y);
}

Subgoals

Now that we’ve seen how to work with conditional constructs in Java, let’s break down our thought process a bit into subgoals once again.

Evaluating Conditional Constructs

Here are the subgoals we can use for evaluating conditional constructs:

1. Diagram Which Statements Go Together

First, when we see a conditional construct in code, we must determine which statements go together. Specifically, we need to know which statements are in the true branch, and which statements are in the false branch. This may seem pretty straightforward, but if the code contains many nested statements or poor formatting, it can be very difficult to do.

Here’s a quick example:

if(true){
  if(false){
    System.out.println("one");
  }
}else{
  System.out.println("two");
}

In this example, we see that there are three distinct branches. First, we have the true branch of the outermost if-else statement, which includes the inner if statement. That statement itself has a true branch that will print one to the terminal. The outermost statement also has a false branch, which will print two to the terminal.

2. Determine if Boolean Expression is true or false

Once we’ve identified our conditional construct, the next step is to determine if the Boolean expression inside of the if statement is true or false. Sometimes this step is simple, but other times it can be tricky. Thankfully, we can refer back to the subgoals we’ve already seen for evaluating expressions if we need a bit of help.

3. Follow the Correct Branch

After we find the value of the Boolean expression, we can simply follow the true branch if the Boolean expression evaluated to true. If the Boolean expression evaluated to false, we can follow the false branch if it exists. If the conditional construct is an if statement, we can simply ignore the rest of the conditional construct and move on to the next part of the program.

Writing Conditional Constructs

We can also use subgoals to help us write new conditional constructs.

1. Define How Many Mutually Exclusive Paths are Needed

First, when building a new conditional construct, we must determine how many paths are needed. In effect, this will be one more than the number of conditional constructs we’ll end up using in most cases.

2. Order Paths from Most Restrictive/Selective to Least Restrictive

Next, we can reorder the paths from most restrictive to least restrictive. The most restrictive path would be the one that is least likely to occur, while the least restrictive path is the one that will be taken most often. We’ll see how this works a bit more clearly in the example on the next page.

3. Write if Statement with Boolean Expression

Once we have all of our paths identified and ordered, we can start writing our if statements for each path. Each if statement will need a Boolean expression to help us determine which branch to follow. Generally, we’ll place the most restrictive paths inside of the less restrictive paths, but it all depends on the problem and what order makes the most sense.

4. Write true Branch Code

Then, for each conditional construct, we must fill in the code on the inside of the true branch. This could be a block of code, or even another conditional construct.

5. Write false Branch Code

Similarly, we must fill in the code on the inside of the false branch, if needed. This could be a block of code, or even another conditional construct.

6. Repeat 3-5 For All Paths

Finally, once we’ve created a conditional construct, we may have to repeat these steps again and again for each path that we identified in subgoal 1.

On the next page, we’ll see how we can apply these subgoals in a worked example.

A Worked Example

YouTube Video

Video Materials

We’ve covered quite a bit of new material so far in this chapter. Let’s work through a complete example from start to finish, just to see how we can put all of those pieces together to make a very powerful program.

Problem Statement

First, let’s start with a problem statement. Here’s an interesting problem that we can try to solve:

Write a program that receives a positive integer as input from the user that represents a year, and prints whether that year is a leap year or not. If the year is a leap year, it should print output similar to 2000 is a Leap Year. If not, it should print output similar to 2001 is not a Leap Year.

While this sounds like a simple problem, there are actually several rules we’ll have to handle. According to the United States Naval Observatory, the Gregorian calendar (the calendar in use throughout much of the world) calculates whether a year is a leap year based on the following rule:

Every year that is exactly divisible by four is a leap year, except for years that are exactly divisible by 100, but these centurial years are leap years if they are exactly divisible by 400. For example, the years 1700, 1800, and 1900 are not leap years, but the year 2000 is. - Source

So, we’ll be writing a program that can handle all of those rules to determine if a year is a leap year or not.

Getting Started

To begin building this program, we need to first build the basic skeleton of a program. For this example, store your code in a file called Example.java

Codio Tutorial

If you are following along with this course in Codio, this example will be a graded exercise in that tutorial. So, you may wish to make sure Codio is open and follow along with this example from there.

First, we’ll need to include a class declaration and a main method declaration in the file. Our code should be similar to this:

public class Example{
  
  public static void main(String[] args){
    
  }
  
}

Breaking Down the Problem Statement

Before we start writing more code, let’s break down the problem statement a bit and see how we can use it to help us identify parts of the program we need to write.

Here’s our problem statement again:

Write a program that accepts a positive integer from the command line, that represents a year as a command line argument, and prints whether that year is a leap year or not. If the year is a leap year, it should print output similar to 2000 is a Leap Year. If not, it should print output similar to 2001 is not a Leap Year.

Looking at this problem statement, we see that we need at least one variable to store the year that is provided as input from the user. Similarly, we need at least one conditional construct, which will allow us to print whether the given year is a leap year or not. Here’s the problem statement again, with those parts highlighted:

Write a program that accepts a positive integer (variable) from the command line, that represents a year, and prints whether that year is a leap year or not (conditional construct). If the year is a leap year (true branch), it should print output similar to 2000 is a Leap Year. If not (false branch), it should print output similar to 2001 is not a Leap Year.

Similarly, we can look at the second part of the problem statement and break it down as well:

Every year that is exactly divisible by four is a leap year, except for years that are exactly divisible by 100, but these centurial years are leap years if they are exactly divisible by 400. For example, the years 1700, 1800, and 1900 are not leap years, but the year 2000 is. - Source

Looking at this, we see that there are actually three conditional constructs we need to build. Let’s mark them in the statement:

Every year that is exactly divisible by four (conditional construct 1) is a leap year, except for years that are exactly divisible by 100 (conditional construct 2), but these centurial years are leap years if they are exactly divisible by 400 (conditional construct 3). For example, the years 1700, 1800, and 1900 are not leap years, but the year 2000 is. - Source

Of course, we’ll have to be careful to make sure that each branch of these three conditional statements produces the correct output. Below, we’ll see how we can construct code for this problem.

Reading Input

Generally, one of the first things our program should do is read and process the input. So, we’ll add the few lines we need to import the Scanner library, create a Scanner object, and read an input from the user:

import java.util.Scanner;

public class Example{
  
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        int year = scanner.nextInt();

        // MORE CODE GOES HERE  
    }
}

We’ve used code similar to this in several of our projects at this point, so it should be familiar to us, even if we haven’t written it before. We’ll explain more about the structure of this code later in this course. For now, we can simply copy and paste this example and build upon it.

Once we have that code in place, we are ready to begin working on the actual logic of our program. The code examples below will just include the logic portion of the program, which can be placed where the MORE CODE GOES HERE line is in the structure above.

Verifying Input

Now that we’ve read the input, we can start writing the logic of our program. However, the problem statement has one very important part in it that we’ll need to handle as well:

Write a program that accepts a positive integer that represents a year…

So, we’ll need to make sure the user has input a positive integer into our program. We can do that using a simple if-else statement:

if(year <= 0){
    System.out.println("Error");
}else{
  
}

In this case, we might be tempted to use an if statement instead. However, we need to make sure our program only calculates a result if the input is positive. If it is negative, we should just print an error. Since that means our program needs two distinct branches, we should use an if-else statement here.

Calculating Output

Once we’ve read our input, we need to calculate our output. Let’s handle the three rules one at a time.

Divisible by $ 4 $

For the first rule, we must check to see if the year is divisible by $ 4 $. We can use the modulo operator to do this. Remember that the modulo operator performs a division and returns the remainder. So, if the remainder is $ 0 $, then we know that the number is evenly divisible by the divisor.

In code, we could do the following:

if(year <= 0){
    System.out.println("Error");
}else{
    if(year % 4 == 0){
        //divisible by 4
    }else{
        //not divisible by 4
        System.out.println(year + " is not a Leap Year");
    }
}

In each of these if-else statements, let’s place a quick comment in the code to keep track of what we know within each branch.

Not Divisible by $ 100 $

Next, we need to handle the rule that any year divisible by $ 100 $ is not a leap year, even if it is divisible by $ 4 $. Of course, we can easily determine mathematically that all years divisible by $ 100 $ are also divisible by $ 4 $, so we don’t have to worry about the other case in this instance. So, we can add another if-else to our program:

if(year <= 0){
    System.out.println("Error");
}else{
    if(year % 4 == 0){
        //divisible by 4
        if(year % 100 == 0){
            //divisible by 4 and 100
            System.out.println(year + " is not a Leap Year");
        }else{
            //divisible by 4 but not 100
            System.out.println(year + " is a Leap Year");
        }
    }else{
        //not divisible by 4
        System.out.println(year + " is not a Leap Year");
    }
}

In this case, we chose to next our if-else statement inside of the previous statement. So, if the year is divisible by $ 4 $ and also divisible by $ 100 $, we print Not a Leap Year. If it is divisible by $ 4 $ and not divisible by $ 100 $, which is the false branch of the innermost if-else, we know that it must be a leap year, so we can print Leap Year.

Unless Divisible by $ 400 $

There’s one more rule we must add, which is the rule that a year divisible by $ 400 $ must be a leap year, even though it is also divisible by $ 100 $. So, we must add one additional if-else statement. But where?

If we think back through the rules, we know that this rule is only in effect when the year is both divisible by $ 4 $ and $ 100 $. So, we’ll need to add one more statement in the true branch of the innermost if-else:

if(year <= 0){
    System.out.println("Error");
}else{
    if(year % 4 == 0){
        //divisible by 4
        if(year % 100 == 0){
            //divisible by 4 and 100
            if(year % 400 == 0){
                //divisible by 4 and 100 and 400
                System.out.println(year + " is a Leap Year");
            }else{
                //divisible by 4 and 100 but not 400
                System.out.println(year + " is not a Leap Year");
            }
        }else{
            //divisible by 4 but not 100
            System.out.println(year + " is a Leap Year");
        }
    }else{
        //not divisible by 4
        System.out.println(year + " is not a Leap Year");
    }
}

Now we’ve created a program that should be able to tell us if a year is a leap year or not.

Other Solutions

Of course, there are many other ways this program could have been structured that would produce the same output. For example, instead of using nested if-else statements, we could rearrange them a bit to make them inline if-else if-else statements, as in this example below:

if(year <= 0){
    System.out.println("Error");
}else if(year % 400 == 0){
    //divisible by 400
    System.out.println(year + " is a Leap Year");
}else if(year % 100 == 0){
    //divisible by 100 but not 400
    System.out.println(year + " is not a Leap Year");
}else if(year % 4 == 0){
    //divisible by 4 but not 100
    System.out.println(year + " is a Leap Year");
}else{
    //not divisible by 4
    System.out.println(year + " is not a Leap Year");
}

In fact, with a bit of thinking, we could reduce most of this program to a single Boolean logic expression, as in this example

if(year <= 0){
    System.out.println("Error");
}else if(((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)){
    System.out.println(year + " is a Leap Year");
}else{
    System.out.println(year + " is not a Leap Year");
}

Any of these solutions is just as correct as the one we built above. It really only depends on which solution makes the most sense to us and is the easiest for us to write and debug.

Subsections of A Worked Example

Tracing & Testing Branches

With the introduction of conditional statements, our programs are starting to become more complex. Each time we run the program, it will produce different output based on the input, and some inputs may cause the program to execute different pieces of code entirely. So, we need to develop some more sophisticated testing strategies to help us debug our programs.

Every program should be thoroughly tested. By creating a large number of different test inputs, with each one testing a different part of the program, we can ensure that every line of code is executed at least once when all of our test inputs are used. This is referred to as test coverage.

Complete Coverage

Consider the following simple piece of code:

1
2
3
4
5
6
7
8
Scanner scanner = new Scanner(System.in);
// read an integer from input
int a = scanner.nextInt();
if (a % 2 == 0){
  System.out.println(a + " is odd");
} else {
  System.out.println(a + " is even");
}

To test this program, we have to provide as single value as input. If the value 2 is entered as input on line 3, then the program will execute the true branch of the if statement on line 5, but it will not execute the false branch on line 7. So, that input only executes some of the lines in the program.

However, if we also test the program with the input 1, we’ll see that it now executes line 7 that was skipped in the previous test.

So, to fully test this program, we should use both 1 and 2 as inputs. This set of inputs will achieve complete coverage of the program.

However, achieving complete coverage by providing enough test inputs is not enough to say our program is correct. For that, we need to analyze the outputs that are provided by each test.

Code Trace

Let’s briefly trace through the program and see what output is produced when we provide the value 2 as input.

On line 4, we see an if statement that checks to see if the value of a % 2 is equal to 0. Since a is currently storing the value 2, we can calculate that 2 % 2 is indeed equal to 0, so we should go to the true branch and execute the code on line 5.

However, if we look at what this line of code does, it prints "2 is odd" as output. That is clearly incorrect! So, our program has a logic error in it somewhere.

As it turns out, we accidentally reversed the true and false branches of the if statement. So, to correct our program, let’s switch them:

1
2
3
4
5
6
7
8
Scanner scanner = new Scanner(System.in);
// read an integer from input
int a = scanner.nextInt();
if (a % 2 == 0){
  System.out.println(a + " is even");
} else {
  System.out.println(a + " is odd");
}

Now when we provide the input 2 we should get the correct output. We can also do the same exercise for the input 1 to verify that it is also correct.

At this point we’ve achieved complete coverage and also proven that each of those inputs produces the correct output. However, there is still one more set of values we may want to consider

Edge Cases

Unfortunately, just achieving complete code coverage is not enough to guarantee that we have tested all values that should be tested in our program.

In our code, we see that the if statement has the expression a % 2 == 0. This creates a boundary where some values will go into the true branch and other values will go into the false branch, and they all seem to revolve around the value 2 in the expression. So, we should choose our input values carefully to check the values on either side of the boundary value 2, just to be sure that it is correct.

So, we may want to add the value 3 to the set of values tested in our program, just to be sure that values greater than 2 also work correctly. If we look at our corrected code, we can see that it indeed will produce the correct output, since 3 % 2 is not equal to 0.

Boundary Values

Sometimes it is infeasible to use all boundary values. Consider the expression a > b where a and b are integers. There are an infinite number of combinations of a and b to test, and testing all of these conditions is not realistic. So, instead we should make sure we test at least once where a < b is true and once where a > b is true, as well as a situation where a == b is true. These three tests are important in order to make sure that the boundary created by a > b is properly tested.

However, when one side of a comparison is a constant number, such as the 1 in a / b > 1, it is generally considered good practice to check the values at and near the boundary, such as 0, 1, and 2 in this example.

Perfect Testing Is Impossible

Except for the most trivial programs, it is impossible to exhaustively test a program for correctness. However, using boundary testing and coverage testing as a guide, we will be able to develop fairly robust test sets for programs in this course.

In practice, exhaustive comprehensive testing is not even attempted in most situations. There is a special type of programming, called high-assurance or safety-critical programming that makes use of specialized languages, structures and techniques to logically prove certain properties are always true. The symbolic math and logic background required for this types of programming lies beyond the scope of this course.

Summary

In this chapter, we learned all about conditional constructs and how we can use them in our programs. They allow us to build programs that execute different pieces of code depending on inputs provided by the user or the values of variables.

This is one of the first major steps toward building larger and more complex programs. In fact, once we learn how to write programs that can repeat steps, we’ll have covered all of the basic building blocks of programming!

Chapter 5

Loops

It comes back around!

Subsections of Loops

Wash, Rinse, Repeat

At this point, we should be able to write a computer program that can accomplish many simple tasks. We’ve seen programs that determine if a number is even or odd, who wins in a game of “Rock, Paper, Scissors,” and whether a given year should be a leap year or not.

However, there’s one simple task that all of our current programming skills cannot easily handle. Let’s take a look at a version of that task and see if we can figure out how to solve it:

Write a program that accepts a positive integer between 1 and 5 as input, and then prints all integers starting with 1 up to the given number.

On the surface, this seems to be a pretty easy problem. We can solve it using a few if statements, as shown in this diagram below:

Counting Up Flowchart Counting Up Flowchart

Following this flowchart, we can see if that if our input is 3, it would first print 1 since x >= 1 is true, then 2, then 3 in a similar way. After that, the next check is x >= 4, which is false, so the program would stop printing. So, this program meets our given specification.

Now, let’s see if we can generalize this program a bit:

Write a program that accepts any positive integer as input, and then prints all integers starting with 1 up to the given number.

Could we write this program using our current techniques? Unfortunately, we’d quickly find that it is impossible to handle. We’d need to create at least one if statement for each possible integer, which would make our code infinitely long.

So, is there a better way to approach this problem?

Loop Constructs

Thankfully, there is. In addition to conditional statements, programming languages include a second important control construct: looping constructs, which allow us to solve problems like this one. A looping construct is a programming construct that allows us to repeat a piece of code as many times as we’d like. In this way, we can perform repeated actions without making our source code infinitely long. Most programming languages refer to this process as looping, which makes sense once we see how it looks in a flowchart.

Let’s go back to that problem statement:

Write a program that accepts any positive integer as input, and then prints all integers starting with 1 up to the given number.

Using a looping construct, we can solve this problem using a program that may resemble this flowchart:

Looping Program Flowchart Looping Program Flowchart

This flowchart includes a secondary variable i, sometimes referred to as a loop counter or an iterator variable, to help us keep track of which number we are on. Let’s walk through this program with an input of 6 to see how it works.

Initially, since our input is 6, we know that x = 6 and i = 1. Next, we reach the first Boolean expression, which will evaluate x >= i. Since 6 >= 1 is true, we follow the True branch. Notice in this diagram that the True branch points down, entering the loop-body. The False branch goes to the right, which is different from the conditional constructs we saw in the previous chapter.

Following the True branch, we see that our program will print the value in variable i, which is 1; then it will perform i = i + 1, making 2 the new value of i.

Finally, it loops back up to the Boolean expression x >= i again, but this time evaluates it with the current values for x and i, which would be 6 >= 2. Since this evaluates to true, we follow the True branch once again. It will print the current value of i, which is 2, then increment i to 3.

We loop again, and evaluate 6 >= 3, which is still true, so we repeat the process again.

After several loops, or iterations of the loop, we eventually reach the situation where i = 6. In this case, we’ll print the number 6, then increment i to 7. Now, when we evaluate the Boolean expression x >= i, we find 6 >= 7, which is false, so the program now follows the False branch and ends.

In total, it will have printed the value stored in i on each iteration, so it will print the numbers 1 through 6, which is exactly what the problem statement requires.

Try It Yourself!

Pick a value for x, and see if you can follow each step in the flowchart to confirm that this program will print out all the numbers from 1 to the value you’ve chosen. It is very important to have a clear understanding of how this program functions before moving on in this chapter.

The ability of a computer program to loop, or repeat the loop body based on the loop condition, is a very powerful tool for any programmer. Loops allow us to build programs that can handle all kinds of inputs and still operate correctly. We can also handle situations where we may not know how many inputs we need, or how many steps we need to take, since we can tell our program to use a loop counter to determine how many steps we need to repeat.

Loop Flowchart elements Loop Flowchart elements

While Loops

YouTube Video

Video Materials

The simplest form of a loop in our computer programs is commonly known as a While loop. In essence, we continue to repeat the steps inside the loop while a particular Boolean expression evaluates to true. The program on the previous page is a great example of a While loop:

While Loop Flowchart While Loop Flowchart

In this program, we repeat the steps inside the loop, which print the value of i and increment that value by 1, while the Boolean expression x >= i evaluates to true. Whenever we reach the Boolean expression and it evaluates to false, we exit the loop and continue with the program.

In code, most programming languages implement a While loop in a format similar to this:

<before code block>
while <loop condition>
    <loop code block>
<after code block>

This program begins by executing code in the <before code block>. Once it reaches the <loop condition>, a Boolean expression, it will evaluate that expression. If the expression evaluates to false, then it will completely skip over the <loop code block> and go directly to the <after code block>

However, if the <loop condition> evaluates to true initially, it will execute the code in the <loop code block>. Once it is done executing that code block, the program will loop back to the <loop condition> and evaluate it again. If it evaluates to true, it will perform the steps in the <loop code block> again. It will continue to do so while the <loop condition> evaluates to true.

However, if the <loop condition> ever evaluates to false, then the program will move on to the <after code block> and continue executing from there.

Also, it is important to remember that the <loop condition> is only evaluated once each time the program loops. So, even if the outcome of the expression may change while the code in the <loop code block> is executing, it only checks the value of the <loop condition> once it is completely done executing that block of code. However, as we’ll see a bit later in this chapter, there are ways to leave the <loop code block> at any time.

Do-While Loops

Some programming languages also implement another type of loop, known as a Do-While loop. Here’s a flowchart showing what a Do-While loop might look like:

Do While Loop Flowchart Do While Loop Flowchart

In code, a Do-While loop looks similar to this:

<before code block>
do
    <loop code block>
while <loop condition>
<after code block>

These loops are very similar to a While loop, but instead of checking the <loop condition> first, the code inside the <loop code block> is executed before any test is done. In this way, it guarantees that the code in the <loop code block> is executed at least once. Then, the <loop condition> is evaluated, and if it evaluates to true the code in the <loop code block> is executed again. If it evaluates to false, then the program moves on to the <after code block>

If we look at the flowchart for the Do-While loop above, can we determine if it matches the problem statement from earlier?

Write a program that accepts any positive integer as input, and then prints all integers starting with 1 up to the given number.

Let’s assume the user chose the number 3 as input. First, the program will set i = 1, then it will print 1 and set i = 2. Next, it will evaluate the Boolean expression x >= i, or 3 >= 2, which evaluates to true, so the loop repeats.

The program will then print 2 and set i = 3, and then it will evaluate 3 >= 3, which is also true. So, it will loop once again, printing 3 and setting i = 4. Finally, it will evaluate 3 >= 4, which is false, and the program will terminate. So, in this case, the program works as expected.

However, there are some cases where a While loop and a Do While loop may produce different output, even if the same code and Boolean expression are used. We’ll see an example of that later in this chapter.

Subsections of While Loops

For Loops

YouTube Video

Video Materials

Most programming languages also support another type of looping construct, known as a For loop. A For loop usually includes an explicitly defined loop counter , a loop condition and loop counter incrementor as part of the loop definition, and is usually used to make sure that a loop only runs a set number of times. This is especially handy for instances where we need to accept exactly $ 5 $ inputs, or if the program should perform a calculation exactly $ 10 $ times.

Many programming languages support a variety of different ways to define how the loop counter in a For loop functions. For these examples, we’ll use a simple definition for the loop counter where it is just an integer that begins at one value and increments by $ 1 $ (the loop counter incrementor) each time until it reaches a second value (the loop condition).

Here’s a flowchart showing what a For loop may look like:

For Loop Flowchart For Loop Flowchart

In this flowchart, we use the shorthand i : [1, x] by 1 to show that the loop counter i starts at the value $ 1 $, and then each loop it is incremented by $ 1 $ until it is larger than x. So, if x = 6, the loop counter i will have the values 1, 2, 3, 4, 5, and 6, and the loop will run exactly $ 6 $ times.

When reading this aloud, we might say i : [1, x] as “For values of i from $ 1 $ through x incrementing by $ 1 $. So, we could say this whole loop is “For values of i from $ 1 $ through x incrementing by $ 1 $, print i.” That should exactly match the original problem statement given earlier:

Write a program that accepts any positive integer as input, and then prints all integers starting with 1 up to the given number.

In code, there are many different ways that For loops are usually defined. We’ll learn how our programming language implements these loops later in this chapter.

Subsections of For Loops

Loop Control

YouTube Video

Video Materials

Once our code is inside of a loop, sometimes we might find situations where we need to either stop looping immediately, or perhaps start the loop over again. Thankfully, most programming languages include two special keywords that can be used in those situations.

Break

The first of these keywords, break, is used to immediately exit a loop from within. The program will immediately jump to the first line of code below the loop and continue executing from there. This flowchart gives a good example of what this might look like:

Break Flowchart Break Flowchart

This program will accept an integer as input, and then print the smallest positive integer that is not a factor of the given input. Let’s walk through this program and see if we can figure out how it works.

For this example, let’s say the user has chosen 30 as the input. At the beginning, the program will set i = 1. Then, it will evaluate the Boolean expression true to determine if it should enter the While loop. Since this expression is always true, the loop will be repeated continuously, unless we use the break keyword to exit the loop. These loops are sometimes referred to as infinite loops, and can be very dangerous to use unless we are very careful.

Inside the loop, it will evaluate x % i != 0 as part of an If statement. In this case, 30 % 1 != 0 is false, so the loop will continue on the False branch of the statement and increment i.

Then, it will loop around again and go right back to the If statement, which will evaluate 30 % 2 != 0 to false as well, so it will once again increment i.

This process will continue until i = 4, at which point 30 % 4 != 0 is true. In that case, it will follow the True branch of the If statement, which will print 4 as output. Then, the next statement is a break keyword. As soon as the code reaches that statement, it will break out of the loop and continue to the next line of code below the loop. In this case, there is no more code, so the program will just terminate.

The break keyword is very important when working with loops, as it allows us to perform additional checks inside of the loop using If statements to determine if the loop should stop at any point.

Continue

The other of these keywords, continue, is used to stop executing the current iteration of the loop, but instead will return to the beginning of the loop and start the process over again with the current variables. Here’s a flowchart for a program that uses the continue keyword:

Continue Flowchart Continue Flowchart

This program is the inverse of the previous program. It will print all positive numbers that are factors of the given input. In this case, it was built using a For loop instead of a While loop. Let’s walk through this program and see how it works.

In this example, we’ll assume the user has chosen 8 as the input. So, the program will start with i = 1 and evaluate the Boolean expression x % i != 0. In this case, that expression is 8 % 1 != 0, which is false. So, the program will output 1 and loop back to the beginning.

The same process applies for the second iteration, where i = 2. Since 8 % 2 != 0 is false, the program will also output 2 and loop.

However, when i = 3, something different happens. In this case, 8 % 3 != 0 evaluates to true, since $ 8 $ is not equally divisible by $ 3 $. Inside of the If-Then statement is the keyword continue, which just tells the loop to stop executing the current iteration and go back to the beginning of the loop. Since this loop is a For loop, the value of i will be updated to 4, and the loop will begin again.

Continuing through the entire program, we’ll see that it outputs 1, 2, 4, and 8, while the other numbers are skipped. This is exactly the expected output.

In many cases, the continue keyword is used along with an If statement to show which iterations or variable values should be ignored or skipped in a loop.

Using Break & Continue

There are some areas of programming, such as video games that use a “game loop”, as well as a few programming languages, where break and continue are commonly used. However, as a general rule, it is not recommended to use break or continue very often in your code, since it can make understanding and following the code much more complex. Instead, try to use the loop conditions to determine when a loop should terminate, and rewrite your code if needed.

Subsections of Loop Control

Input Streams

Reading input from either the keyboard or a file are handled as streams. A stream is an abstract concept in computer science for data that is organized as a queue, where items that are added to the stream first are also the items that are read first. Put another way, when reading input from the keyboard, the program reads the keys in the order they are pressed. This is similar to a conveyor belt.

Conveyor Belt Conveyor Belt1

Some streams, like the keyboard, are “infinite” and have no defined end. When we type the characters dog followed by the ENTER key on the keyboard, the bytes [x64, x6F, x67, x0B] representing the ASCII characters dog\n are placed in the keyboard’s input stream. Those bytes can then be read out of the stream by a program when it wants to receive keyboard input from the user. However, once those bytes are read, the stream does not close or end. Instead, it waits for new keyboard input to arrive from the user.

The process for reading files is similar. When the program opens a file, it can then read the bytes stored in the file from first to last until it reaches the end. Files, however, are “finite” meaning that they have a defined end. This is represented by a special ASCII character called the EOF or “End of File” character, represented by the bye x05 in the stream.

Streams as Objects

Programming languages such as Java and Python have many libraries and classes for working with streams. These classes are used to create stream objects that can read values from the stream using various methods. In this class, we’ve already seen this through the Scanner class in Java or the input() method in Python, which allow us to read input from the keyboard.

We can also read from files using many of the same methods. This is the power of abstraction in computer programming - if we can represent multiple items in a similar way, then we can write code that allows us to interact with either type of object.

Finally, the process of writing output to a file or to the terminal is also handled through streams. This allows us to both read and write from the same file or the terminal - we just use a stream to accomplish that!

Standard Streams

Most operating systems make the following streams automatically available to any running program:

  • stdin - this is the standard input stream that is used to receive input from the keyboard.
  • stdout - this is the standard output stream that displays program output on the terminal to the user.
  • stderr - this is the standard error stream that is used to present errors from the program.

In Java, these are available under the System class as System.in, System.out and System.err, respectively. We’ve already used the System.out.println() method many times to print output to the terminal, and we typically create a Scanner object that uses System.in to read input from the user. So, we’ve already used these streams many times in our programs!

Chapter 7.J

Java Loops

Loops in Java

Subsections of Java Loops

While Loop

Now that we’ve added terminal entry to our tool set, let’s look at how we can use each of these looping constructs in Java. First, here is the general syntax for a While loop in Java:

while(<Loop Condition>){
  <loop code block>
}

As expected, Java will first evaluate the <Loop Condition> to a single Boolean value. If that value is true, it will execute the instructions in the <loop code block>, which can be one or more lines of code, or even additional constructs. Once the <loop code block> execution is complete, the program will return to the top of the loop and evaluate the <Loop Condition> again. It will repeat this process until the expression evaluates to false. Of course, if the expression is false initially, the code in the <loop code block> will not be executed at all.

It is very important to remember that the Loop Condition is enclosed by parentheses ( ), while the block of code that should be executed if that expression is true is enclosed by curly braces { }, just like in an If statement, class body or method body

Let’s take a look at a quick example, just to see how this construct works in practice. First, let’s consider the program represented by this flowchart from earlier in the chapter:

Looping Program Flowchart Looping Program Flowchart

This flowchart corresponds to the following code in Java. In this case, we’ll assume x is hard-coded for now:

int x = 8;
int i = 1;
while(x >= i){
  System.out.println(i);
  i = i + 1;
}

By walking through the execution of this program, we see that it will output all integers from $ 1 $ through $ 8 $. If the user inputs a number less than $ 1 $, the program will not produce any output at all.

Do-While Loop

Java also includes another form of a While loop, known as a Do-While loop . Here is the general syntax for a Do-While loop in Java:

do{
  <loop code block>
}while(<Loop Condition>);

In this code, Java will first execute all of the code in the <loop code block> without evaluating any Boolean expressions. The main use of a Do-While loop is to guarantee that the code inside the loop is executed at least once. Then, it will evaluate the <Loop Condition> to a Boolean value. If that value is true, then the program will repeat the code in the <loop code block> once again. If it is false at any time it is evaluated, then the program will continue running the code directly below the Do-While loop.

It is very important to remember that the Loop Condition is enclosed by parentheses ( ), while the block of code that should be executed if that expression is true is enclosed by curly braces { }, just like in an If statement, class body or method body. In addition, notice that Java also requires a semicolon ; after the Boolean expression.

Let’s take a look at a quick example, just to see how this construct works in practice. First, let’s consider the program represented by this flowchart from earlier in the chapter:

Do While Loop Flowchart Do While Loop Flowchart

This flowchart corresponds to the following code in Java. In this case, we’ll assume x is hard-coded for now:

int x = 8;
int i = 1;
do{
  System.out.println(i);
  i = i + 1;
}while(x >= i);

By walking through the execution of this program, we see that it will output all integers from $1$ through $8$.

However, this program will perform differently than a While loop if the user inputs a number less than $1$. Since this program is built using a Do-While loop, it will always execute the code inside the loop first, so the program will always print at least 1 to the terminal. After that, the program will evaluate x >= i, which will be false if x is less than $1$, so it will terminate. Notice that this is different than the While loop on the previous page, which will not produce any output at all in this instance.

Therefore, it is very important to understand how each loop functions and choose the appropriate loop for each task. In practice, Do-While loops are used very rarely, but they can be useful in some instances.

Uncommonly Used

In practice, Do-While loops are not commonly used in Java. We’re including them here just in case you see them in other code you find, but we generally don’t recommend using them in your own code unless the situation calls for this loop structure.

For Loop

The syntax for a For loop in Java is a bit complex, and has many different parts. Here’s the general format:

for(<initializers>; <loop condition>; <updaters>){
  <loop code block>
}

Let’s break this syntax down into each individual part to understand how it works.

First, the <initializers> section is used to create and initialize any variables that we’d like to use as loop counters inside of the loop. For example, we could use int i = 0 in that section to create a single integer variable i and set its initial value to 0. We may also declare multiple variables of the same type, separating each with a comma or ,. In that instance, we could say int i = 0, j = 1, which would declare two new integer variables, i and j, and set their values to 0 and 1, respectively. Finally, we can choose to leave that section blank, as it is not required at all. In either case, we must end that section with a semicolon ; before moving on to the next section. This section is executed just once, before the first iteration of the loop itself. We’ll explore a full example below.

The <loop condition> section is the same as in a While loop. It must evaluate to a single Boolean value, either true or false, which is used to determine if the loop continues executing or not. This section must also end with a semicolon ;, and is generally required in a For loop.

Finally, the <updaters> section can include one or more statements used to increment (update) the values of the loop counter variables. This section is executed at the end of each iteration of the loop, before the Boolean expression is evaluated again. Generally, we would include code such as i++ or i = i + 2 in this section. Similar to the <initializers> section, multiple update statements can be included in this section, separated by a comma ,. So, we could use i++, j++ to increment the values of both i and j in the same loop.

Let’s look at an example. Here’s a flowchart from earlier in this chapter containing a For loop:

For Loop Flowchart For Loop Flowchart

This flowchart corresponds to the following code in Java. Once again, we’ll assume x is hard-coded for now:

int x = 8;
for(int i = 1; i <= x; i++){
  System.out.println(i);
}

In this For loop, we can clearly see the three parts. First, we have int i = 0 as the initializer. It creates a new variable and gives it an initial value. Then, we see i <= 8 is our loop’s Boolean condition. Finally, we have i++ as the lone updater, since it updates the value of the loop counter i by incrementing it by $ 1 $.

To understand how this For loop functions in practice, let’s look at an equivalent While loop:

int x = 8;

//initializers
int i = 1;

while(i <= x){
  System.out.println(i)
  
  //updaters
  i++;
}

These loops are exactly identical in terms of how the code is executed. In a For loop, the initializers are performed once at the beginning, before the loop really starts. Then, we evaluate the Boolean expression and determine if we should enter the loop and perform those operations. At the end of each loop iteration, the updaters are executed to update the values of any loop counters, before the loop repeats back to the Boolean expression.

For Loops and Variable Scope

For loops in Java have one important caveat when it comes to variable scope. As expected, any variables declared in the initializers section of a For loop may be accessed from within the For loop itself, but they may also be accessed in the Boolean expression or the updaters section as well. They cannot, however, be accessed outside of the For loop.

However, any variables declared inside the For loop cannot be accessed in either the Boolean expression or the updaters. In effect, a For loop in Java has two levels of scope, one containing the variables declared in the initializers, and another for just the code inside the loop. Generally this doesn’t pose a problem, but it is an important distinction to be aware of.

Here’s one more example of a For loop in Java, using three loop counters instead of just one:

int sum = 0;
for(int i = 0, j = 1, k = 20; i + j < k; i++, j++, k--){
  sum += i + j + k;
}

Notice that there are three variables initialized in the initializer, which is int i = 0, j = 1, k = 20. In addition, there are three variables updated in the updater, which is i++, j++, k--. While it is uncommon for most programmers to use multiple loop counters in a single For loop, it is important to understand how it can be done.

Missing Curly Braces?

In Java, it is possible to have loop constructs without curly braces, just like we saw for conditional constructs in an earlier. In that case, the next line of code immediately following the while or for will be the only line repeated inside of the loop.

Consider this code for example:

int x = 0;
while(x < 5)
  x++;

This code is valid, and will compile and run properly. However, just like with conditional constructs, if we want to add a second line to the inside of the loop, we’ll need to remember to add curly braces for our code to work properly.

In addition, we can omit parts of a For loop, as in this example:

int x = 0;
for( ; x < 5 ; )
  x++;

Here, we’ve omitted both the initializers and updaters of the For loop. Those parts are considered optional, and either one can be left out. However, in this case, it may make more sense to convert this to a While loop instead.

Loop Control

Java also includes both the break and continue keywords. They are pretty straightforward and easy to follow.

Break

Here’s the flowchart showing a program with a break statement from earlier in this chapter:

Break Flowchart Break Flowchart

This flowchart corresponds to the following code in Java. Once again, we’ll assume x is hard-coded for now:

int x = 8;
int i = 1;
while(true){
  if(x % i != 0){
    System.out.println(i);
    break;
  }
  i = i + 1;
}

This code shows us an example of an infinite While loop, sometimes referred to as a While-True loop. In this case, we are simply using the keyword true as our Boolean expression, so the loop will always continue to run unless we use the break keyword to leave it. So, in this code, once x % i != 0 evaluates to true, we reach the break keyword and then exit the loop.

Continue

Here’s the flowchart showing a program with a continue statement from earlier in this chapter:

Continue Flowchart Continue Flowchart

This flowchart corresponds to the following code in Java. Once again, we’ll assume x is hard-coded for now:

int x = 8;
for(int i = 1, i <= x; i++){
  if(x % i != 0){
    continue;
  }
  System.out.println(i);
}

In this example, we see a For loop that contains a continue statement inside of it. Here, it is important to remember that, even though the continue statement tells the program to go back to the beginning of the loop, the updater i++ will still be executed before evaluating the Boolean expression i <= x. This is one of the unique features of a For loop compared to a similar While loop. If we rewrote this program using a While loop, we’d have to remember to update the value of i manually before the continue keyword, as in this example:

int x = 8;
int i = 1;
while(i <= x){
  if(x % i != 0){
    i++;
    continue;
  }
  System.out.println(i);
  i++;
}

Notice that we had to include an extra i++ before the continue keyword. Otherwise, the loop would repeat without updating the value of i, causing it to become infinitely stuck.

Try It!

Try the preceding code and see what happens when you remove the i++ statement directly above the continue keyword. Does it cause any problems?

Hint: When using a program via the terminal, you can press CTRL + C to stop a running program if it locks up or starts infinitely looping.

Loop Subgoals

Of course, we can identify some subgoals for working with loops in Java as well. Let’s take a look at them and see how we can use them to help understand loops a little bit better.

Evaluating Loops

Here are the subgoals for evaluating a loop in Java:

1. Identify Loop Parts

The first and most important part of evaluating a loop is to identify each part of the loop structure. Here is a list of things to look for:

  1. Start Condition - variables that are initialized when the loop is first reached (For loops only)
  2. Update Condition - variables that are updated after each loop iteration (For loops only)
  3. Loop (termination) Condition - the Boolean expression that will terminate the loop when it becomes false
  4. Loop Body - the lines of code which will be repeated on each loop iteration

Depending on which type of loop we are looking at, we may not find all of the parts listed above.

2. Trace the Loop

Once we’ve identified all of the parts of the loop, we can then trace the loop to see how it updates values on each iteration. The easiest way to do this is to write down the values of each variable before the loop starts, and then update those values as the loop iterates. We’ve already seen a few examples for how to trace a loop in this chapter, and we’ll do one more on the next page.

Writing Loops

We can also use subgoals to help write loops. Here are the subgoals we’ll use:

1. Determine Purpose of the Loop

Before writing a loop, we must decide what we’re using it for. Once we know that, then we can determine which type of loop would be best. For example, if we are using the loop to repeat steps until a particular Boolean condition is false, we’ll probably want to use a While loop. If we want to make sure the loop executes at least once, we may want to use a Do-While loop instead. Finally, if we are iterating a specific number of times, or across a particular data structure (as we’ll see in a later chapter), we should probably use a For loop.

2. Define and Initialize Variables

Once we’ve determined which loop structure we’re going to use, we’ll need to define and initialize any variables needed by the loop. Specifically, we may want to define the initial value of our iterator variable to some value if we are using one.

3. Determine Termination Condition

Next, we’ll need to determine the Boolean condition that should cause the loop to terminate. For example, we may want the loop to repeat until our iterator variable i becomes 5. So, we’ll want to invert that Boolean statement to find the statement that can be used inside the loop to determine whether it should continue.

Therefore, the termination condition i == 5 should become the continuation condition i < 5. We could also use i != 5, however we could run into an issue where the value of i skips over 5 for some reason in our code, creating an infinite loop. By using i < 5 instead, the loop will terminate as soon as i becomes 5 or greater, which is safer overall.

4. Write Loop Body

Once we’ve set up the loop itself, we can write the code we’d like repeated inside of the loop body. One thing we must be very careful about is making sure we are properly updating our loop’s iteration variable toward the termination condition. If we forget to do that, we may run into a condition where the loop will not terminate at all, causing our programs to lock up. In a later chapter, we’ll discuss concepts such as loop invariants that will help us with this step.

Input Loops

Now that we know how to use loops, let’s discuss a common structure for reading and parsing user input from the terminal using a loop.

Reading Terminal Input

In many of our prior projects, we’ve seen the Scanner class used to read input from the keyboard’s input stream, which is System.in in Java. Typically we use code similar to this:

import java.util.Scanner;

public class ReadInput {

    public static void main(String[] args){
        // Create the Scanner object to read from the terminal
        Scanner scanner = new Scanner(System.in);

        int x = scanner.nextInt();
        double d = scanner.nextDouble();

        System.out.println(x + " + " + d + " = " + (x + d));
    }
}

Let’s look at some of the important lines of code in this short example:

  • import java.util.Scanner; - this line at the very top of our program is an import statement. It tells Java that we’d like to import, or use, the Scanner class from the java.util library, which is part of the standard Java Development Kit (JDK). These lines must be at the top of our file, before any class declarations. We’ll learn more about importing and using library classes a bit later in this course.
  • Scanner scanner = new Scanner(System.in) - this line serves two purposes. First, it creates a new instance of a Scanner object, which is stored in the variable scanner. Notice that these names are case-sensitive! The capitalized Scanner is the name of the class, which in this case is used like a data type similar to int or double. The lowercase scanner is the variable name where the object created from that class is stored. We’ll learn about objects and classes a bit later in this course. The last part new Scanner(System.in) creates a new Scanner object and tells it to read input from the System.in stream, which is the connected to the keyboard in the terminal.
  • scanner.nextInt() - this method will read the next piece of input from the keyboard and try to convert it to an integer. If it can, it will store it in the variable x. However, if the next piece of input is not an integer, this will cause an error and crash the program. We’ll learn how to catch and handle these errors a bit later in this course, but for now we’ll have to assume that our users are providing input that matches the expected structure.
  • scanner.nextDouble() - similar to the previous line, this will read the next piece of input and try to convert it to a floating-point value. If it can, it will store it in the variable d.

Of course, there are much more advanced ways to use a Scanner in Java. We’ll learn more in a later chapter, or we can always refer to the Java Documentation for the Scanner class.

Reading Multiple Lines

Many times, we want our programs to be able to read multiple lines of input, and to continue to read input until the end of the stream is reached. In that case, we can use a While loop and a few different methods in the Scanner class to accomplish this.

Consider this code example:

import java.util.Scanner;

public class ReadManyLines {

    public static void main(String[] args){
        // Create the Scanner object to read from the terminal
        Scanner scanner = new Scanner(System.in);

        int countLetters = 0;
        int countLines = 0;

        // Repeat while the Scanner thinks there is another line to be read
        while(scanner.hasNextLine()){
            // Read the next line of input
            String input = scanner.nextLine();
            
            // Check to see if the line is empty after removing all leading and trailing whitespace
            if (input.trim().length() == 0){
            
                // If the line is empty, break out of the while loop
                break;
            }
            
            // parse the line of input and perform the calculations needed
            countLetters += input.length();
            countLines++;
        }

        // all input has been read at this point
        System.out.println("I read " + countLetters + " characters of input across " + countLines + " lines");
    }
}

This program will read input from the terminal until no more input is provided, and it will count the total number of lines and characters read from the terminal’s input stream. To do this, it uses a few new methods of the Scanner class:

  • scanner.hasNextLine() - this method will return true if there is more input to be read from the input stream. So, we use this in the While loop to repeat until there is no more input to be read.
  • scanner.nextLine() - this method reads an entire line of input from the input stream. It will keep reading characters until it reaches a newline character \n. Remember that when the user presses the ENTER key on a keyboard, that will add a newline character \n to the keyboard’s input stream, signalling the end of a line of input.

This works well in many situations, but there is one important thing we must remember - the keyboard input stream System.in is an infinite stream! This means that is never ends, and the scanner.hasNextLine() method will always return true when reading input from the terminal.

So, how do we get our program to end? In most cases, we will simply add an If statement inside of the loop to check and see if the line of input is empty, and then break out of the loop if it is. In this code, we use the Boolean expression input.trim().length() == 0 to check and see if the String input is empty. We’ll learn more about these string methods in a later part of this course.

Avoiding Dangling Newlines

This new method of reading input, which uses scanner.nextLine() to read an entire line of input instead of scanner.nextInt() or scanner.nextDouble() to just read a single element from the input, comes with one major caveat that developers must be aware of - the dangling newline.

Consider this code:

import java.util.Scanner;

public class DanglingNewline {

    public static void main(String[] args){
        // Create the Scanner object to read from the terminal
        Scanner scanner = new Scanner(System.in);

        // Repeat while the Scanner thinks there is another line to be read
        while(scanner.hasNextLine()){
            // Read the next line of input
            String name = scanner.nextLine();

            // Check to see if the line is empty after removing all leading and trailing whitespace
            if (name.trim().length() == 0){
            
                // If the line is empty, break out of the while loop
                break;
            }

            // Read an integer from input
            int age = scanner.nextInt();
            System.out.println("Greetings " + name + "! Your age is " + age);
        }
    }
}

This program will read the name and age of a user from input, one per line. So, let’s assume that we are providing the following input:

Willie
42
Wildcat
37

When we run this program, however, we see that an error occurs:

Dangling Newline Dangling Newline

Why did this happen? Let’s look at the stream that would be read by the program, complete with the newline characters:

Willie\n42\nWildcat\n37\n

Now, let’s step through the program and check all the Scanner methods that read the input to see where the error occurs! First, the scanner.hasNextLine() method will return true since the stream still has input to be read. Next, the scanner.nextLine() method will read the next line of input, up to and including the newline \n characater. So, it will read "Willie" from the stream and store it in name, and then remove it and the following newline. Now we have this in the stream:

42\nWildcat\n37\n

Good so far! After that, the scanner.nextInt() method will try to read the next item in the stream and convert it to an integer. It sees the characters "42" followed by a newline, and it knows that it can convert "42" to an integer, so it will read those characters and store the value 42 in the age variable. However, the nextInt() method does not remove the newline character from the stream. So, we’ll be left with this content in the stream:

\nWildcat\n37\n

Here’s where things go wrong. The scanner.hasNextLine() method will still return true since there is input to be read. So, the scanner.nextLine() method will read input until it reaches a newline character \n. In this case, it sees that immediately, so it will store the empty string "" in the name variable and then remove the newline from the stream. So, we are left with this content in the stream.

Wildcat\n37\n

This means that, when the scanner.nextInt() method is called, the first thing it reads is not a number, so it throws an InputMismatchException and crashes the program!

This happens because had a dangling newline that was left in the input stream. To fix this, the easiest way is to always read an entire line of input, and then use other methods to parse and convert that line as needed. In general, we want to avoid mixing the Scanner methods that read entire lines and the methods that just read individual elements of input. In most cases, we should use one or the other.

In this case, we can change the scanner.nextInt() to Integer.parseInt(scanner.nextLine()) to read the entire next line of input and then convert it to an integer:

import java.util.Scanner;

public class DanglingNewline {

    public static void main(String[] args){
        // Create the Scanner object to read from the terminal
        Scanner scanner = new Scanner(System.in);

        // Repeat while the Scanner thinks there is another line to be read
        while(scanner.hasNextLine()){
            // Read the next line of input
            String name = scanner.nextLine();

            // Check to see if the line is empty after removing all leading and trailing whitespace
            if (name.trim().length() == 0){
            
                // If the line is empty, break out of the while loop
                break;
            }

            // Read an integer from input
            int age = Integer.parseInt(scanner.nextLine());
            System.out.println("Greetings " + name + "! Your age is " + age);
        }
    }
}

With that change in place, the program will run correctly!

Dangling Newline Dangling Newline

Prompting for Input

Lastly, it is usually considered good practice to include prompts that tell the user what kind of input is expected. For example, we can update the previous code to include some prompts, using the System.out.print() method:

import java.util.Scanner;

public class DanglingNewline {

    public static void main(String[] args){
        // Create the Scanner object to read from the terminal
        Scanner scanner = new Scanner(System.in);

        System.out.print("Enter a name: ");

        // Repeat while the Scanner thinks there is another line to be read
        while(scanner.hasNextLine()){

            // Read the next line of input
            String name = scanner.nextLine();

            // Check to see if the line is empty after removing all leading and trailing whitespace
            if (name.trim().length() == 0){
            
                // If the line is empty, break out of the while loop
                break;
            }

            System.out.print("Enter an age as an integer: ");
            // Read an integer from input
            int age = Integer.parseInt(scanner.nextLine());
            System.out.println("Greetings " + name + "! Your age is " + age);

            System.out.print("Enter a name: ");
        }
    }
}

Notice that the first prompt has to be printed before the while loop - this is because the scanner.hasNextLine() method will wait until a line of input has been provided before it allows the program to continue, even if we haven’t actually read that line of input yet. So, we have to print our prompt before checking for a line of input to be read from the terminal.

While this is considered best practice in the real world, we typically will not include these prompts for input in our programs in this course. The major reason for removing the input prompts is to ensure that the automated grader is just reading the output produced by your program and not the prompts for input.

Without this change, we’d have to be very careful to make sure that our input prompts also perfectly matched what the automated grader was expecting. Since we really want the focus to be on the output produced and not the input prompts, we’ve made the decision to simply not include input prompts in our programs in this course.

My Program is Stuck

There are some instances where it may look like your program has stopped working, especially when dealing with loops and user input. If that happens, try these steps:

  1. Try typing on the keyboard. If text appears in the terminal, that means your program is just waiting for you to provide input.
  2. If no text appears when you type on the keyboard, try the keyboard shortcut CTRL + C (or CMD + C on a Mac) to interrupt the program.
  3. If that does not work, close the terminal window or tab where the program is running. That should stop any programs running in that terminal.
  4. If Codio or your system appears to still be running slow, as a last resort try restarting the computer where the program was running. In Codio, click the Project menu and choose Restart Box… to restart the underlying Linux system where your program is running.

Subsections of Input Loops

Accumulator Pattern

One common use for loops is the accumulator pattern. An accumulator simply computes some values based on a large amount of data, such as the sum, maximum, minimum, average, or count. In programming, a pattern is simply a common structure that is used to solve a recurring problem in code. Since many programs end up needing a loop that acts an accumulator, we’ve developed a common pattern that can be used in our code to solve this problem.

The simplest example of the accumulator pattern is a program that will sum up a set of values. Consider this code:

import java.util.Scanner;

public class Accumulator {

    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);

        // Initialize accumulator variables
        int sum = 0;

        // Read and parse input
        while(scanner.hasNextLine()){
            String input = scanner.nextLine();
            if (input.trim().length() == 0){
                break;
            }
            int x = Integer.parseInt(input);

            // Update accumulator variables
            sum += x;
        }

        // Display results
        System.out.println("The sum of these values is " + sum);
    }
}

In this example, we see the general structure for the accumulator pattern:

  • Before the loop, we initialize any accumulator variables to their default values. Since we are computing the sum, we should start at 0. However, when computing other values, such as the product, we may need to initialize these values to 1, -1, or some other value. It doesn’t always make sense to start at 0.
  • Inside of the loop, we read the input and parse it. Then, we will update the accumulator variables before repeating the loop.
    • Many times we also include a conditional statement here and only update the variables if the Boolean condition is true. For example, we could easily modify this program to only sum up the even values using a conditional statement.
  • After the loop ends, we can then display the accumulated results. In some cases, such as when we are computing the average of a list of values, we may have to perform some final calculations here before displaying the result.

We’ll see this pattern appear many times in our programs from this point onward, so it is helpful to make note of it and observe when it is used in practice.

A Worked Example

YouTube Video

Video Materials

Updated Code

The code in this example video contains a slightly older version of the input code that also includes the ability to read from a file, which can be ignored. We recommend using the code shown below instead. The rest of the video is still applicable to this example.

Now that we’ve learned some of the different looping constructs in Java, let’s work through a completed example to see how we can use loops to build more advanced programs.

Problem Statement

For this example, we’d like to build a program that matches the following problem statement:

Write a program that will accept a series of integers from the keyboard, one per line. It will continue to accept integers from the user until the user inputs 0. If the user inputs a negative number, the program should print "Error! Positive Integers Only" and continue to receive input. Once the user inputs 0, the program should print the sum and average of the positive integers input by the user.

Flowchart

Before we start coding, let’s try to draw a flowchart for this program, just to make sure we understand the control flow it will use. We know that we need to get keyboard input, convert it to an integer then take some actions based on its value.

Initial Flow Chart Initial Flow Chart

We also know we will have to repeat this an indeterminate number of times, and a While loop is the preferred construct to use for this. So lets add a While loop. While we are at it, make sure that if 0 is input for x, we do nothing and let the program flow back to the start of the while loop. So, our final flowchart may look like this.

Revised Flow Chart Revised Flow Chart

There are a few important items to note in this flowchart:

  1. We added a conditional statement inside of the loop to check if x > 0. If this is false, we’ll print the error and loop back to the top. This is keeps us from adding 0 or values less than 0 to our sum.
  2. The x < 0 and x > 0 paths are mutually exclusive. We should probably consider using If-Else If-Else statements to communicate this intentional exclusivity when we write our code.
  3. The flowchart is a “complete” example that shows the overall control flow of the program, but it skips many details:
    1. How can we check if x != 0 that first time if we don’t get a value for x until we are in the loop?
    2. How do we calculate the average?
    3. Where does the Scanner code to handle input go?

Keep in mind that a flowchart is an abstraction of the code we need to write; it is just a model containing some important subset of the details. This one is sufficient to ensure we use the right loop and if statements.

Code the Boiler Plate

Lets start by adding the boiler plate code for setting up the class, the main() method and input. Let’s store this program in Example.java

import java.util.Scanner;

public class Example{
  
    public static void main(String[] args){
        // Create the Scanner object to read from the terminal
        Scanner scanner = new Scanner(System.in);
          
        /* -=-=-=-=- MORE CODE GOES HERE -=-=-=-=- */
      
    }
  
}

For the rest of this example, we’ll look at a smaller portion of the code. That code can be placed where the MORE CODE GOES HERE comment is in the working copy above.

Handling Input

Next, we want to build a program that can accept a series of integers from the user. Since we don’t know how many we’ll get, we’ll probably want to use some sort of a While loop. So, let’s add in a While loop and start building from there:

while(){
  
}

Inside of that loop, we know we need to read an integer from the user, so we can add the code for that as well:

while(){
  int x = Integer.parseInt(scanner.nextLine());
}

Next, we need to determine when the loop should terminate. In this case, we can go back to the problem statement above, where we see the line:

It will continue to accept integers from the user until the user inputs 0.

So, we might be tempted to do something like this:

while(x != 0){
  int x = Integer.parseInt(scanner.nextLine());
}

However, that code has a very important error in it. We haven’t declared x outside of the While loop, so when we try to compile this code we’ll get a compiler error. So, let’s resolve that error:

int x = 0;
while(x != 0){
  x = Integer.parseInt(scanner.nextLine());
}

When we compile and run this fragment, we should see that it never prompts for input. We need to initialize x to some value, so we quickly chose to initialize it to $ 0 $. However, by doing so, we should hopefully see that it will never enter the loop, since x != 0 will immediately be false. So, let’s set x = 1 instead for now:

Sentinel Values

In the above loop, “zero” may be referred to as a sentinel value, which is a value to watch for and alter the program’s behavior when it occurs. It is important to ensure we don’t inadvertently initialize our variable to a sentinel value.

int x = 1;
while(x != 0){
   x = Integer.parseInt(scanner.nextLine());
}

That’s a good start! As we continue to work on this program, we’ll revisit the structure of this code and see that there might be a better way to do it. For now, let’s press on ahead.

Invalid Inputs

Next, we can handle the error messages for any invalid inputs. From the problem statement:

If the user inputs a negative number, the program should print “Error! Positive Integers Only” and continue to receive input.

This case is pretty simple. We want to check if the user has entered a number less than $ 0 $. If so, we should just print out an error message, but continue to receive input. The word “continue” gives us an important clue toward how we can accomplish this task. Here’s one way to build this test into our program:

int x = 1;
while(x != 0){
  x = Integer.parseInt(scanner.nextLine());
  if(x < 0){
    System.out.println("Error! Positive Integers Only");
    continue;
  }
  //logic here
}

In this code, if the user enters a negative number, we simply use an If statement to find that error, print out the error message, and then the continue keyword will cause the program to loop back to the beginning and read another input. Of course, we can do this without the continue keyword as well, using an If-Else statement instead:

int x = 1;
while(x != 0){
  x = Integer.parseInt(scanner.nextLine());
  if(x < 0){
    System.out.println("Error! Positive Integers Only");
  }else{
    //logic here
  }
}

Either approach works equally well. Some developers prefer to avoid the use of continue and break keywords because they make it more difficult to understand loops, while other developers prefer to avoid having the logic of the loop nested several layers deep in many If-Else statements. It is really up to developer preference and the overall style guide that is in effect.

For this example, we’ll use the code with the continue keyword, just to get a better understanding of how it works.

Program Logic

Once we’ve handled our user inputs, we can include our program’s logic. In this case, we need to calculate both the sum and average of all of the numbers entered by the user. Calculating the sum is pretty simple! We can just include a sum variable and add each input to that variable:

int x = 1;
int sum = 0;
while(x != 0){
  x = Integer.parseInt(scanner.nextLine());
  if(x < 0){
    System.out.println("Error! Positive Integers Only");
    continue;
  }
  sum += x;
}
System.out.println("Sum: " + sum);

To calculate the average of a set of numbers, we must remember the formula $ \text{Average} = \frac{\text{Sum}}{\text{Count}} $. Since we already are tracking the sum, we can just add another variable to keep track of the count of inputs:

int x = 1;
int sum = 0;
int count = 0;
while(x != 0){
  x = Integer.parseInt(scanner.nextLine());
  if(x < 0){
    System.out.println("Error! Positive Integers Only");
    continue;
  }
  sum += x;
  count++;
}
System.out.println("Sum: " + sum);
System.out.println("Average: " + (double)sum / count);

Notice in the code above we are casting sum as a double when we calculate the average. Otherwise, the program will perform floored integer division, which isn’t what we want in this case.

Now, let’s see if this works.

Try It!

See if you can complete the program using the example code above in a file name Example.java. Does it work correctly?

There is a very important logic bug in the code above. See if you can figure out what it is before continuing!

Logic Bug

The code above contains a very important logic error. To find that error, let’s run the program by entering the numbers 1 and 3, followed by 0 to end the program. Here’s the output we should receive:

Logic Error Output Logic Error Output

Notice that the sum of 4 is correct, but the average is 1.333333 instead of 2. Why is that?

If we look closely at our program above, we notice that the program will still increment count when we input 0 to stop the program. So, it believes that we’ve entered three numbers, when we actually only entered two. Therefore, we need to figure out some way to prevent the program from incrementing count when we input 0.

There are several ways we can accomplish this. One way is to simply wrap the program logic in another If-Then statement, as in this example:

int x = 1;
int sum = 0;
int count = 0;
while(x != 0){
  x = Integer.parseInt(scanner.nextLine());
  if(x < 0){
    System.out.println("Error! Positive Integers Only");
    continue;
  }
  if(x != 0){
    sum += x;
    count++;
  }
}
System.out.println("Sum: " + sum);
System.out.println("Average: " + (double)sum / count);

We could also use a break keyword to exit the loop as soon as we realize that the user’s input is 0:

int x = 1;
int sum = 0;
int count = 0;
while(x != 0){
  x = Integer.parseInt(scanner.nextLine());
  if(x == 0){
    break;
  }
  if(x < 0){
    System.out.println("Error! Positive Integers Only");
    continue;
  }
  sum += x;
  count++;
}
System.out.println("Sum: " + sum);
System.out.println("Average: " + (double)sum / count);

Either approach works. Again, it just depends on how we’d like to style our code so that it is clear and easy to understand.

In addition, we could rearrange the code just a bit to make the While loop’s Boolean expression a bit clearer:

int sum = 0;
int count = 0;
int x = Integer.parseInt(scanner.nextLine());
while(x != 0){
  if(x < 0){
    System.out.println("Error! Positive Integers Only");
  }else{
    sum += x;
    count++;
  }
  x = Integer.parseInt(scanner.nextLine());
}
System.out.println("Sum: " + sum);
System.out.println("Average: " + (double)sum / count);

In this example, we read an input from the user before entering the loop. So, if the user initially inputs 0, it skips the loop entirely, which is fine. If the input is not 0, then it will enter the loop and check to see if it is negative. If it is, it will print the error message, but if not, it will update the sum and count accordingly. Finally, at the end of the loop, it will read another input from the user, then immediately loop back to the beginning and make sure that the user has not input 0 before starting the next iteration. Also, notice that this code does not include any break or continue keywords.

However, this code does include two lines that read input from the user. This violates one principle of writing good code, which is DRY, or Don’t Repeat Yourself. If at all possible, we want to avoid writing two lines of code that perform the same action. So, while this code may be a bit simpler to read, it may also be a bit more difficult to update later. For example, what if a future developer needs to change this program to read floating point numbers instead of integers? If that developer does not update both lines that read input from the file, it could make the program unusable!

Finally, it may be best to simply include several If-Else If-Else statements to make everything clear in the code, as in this example:

int x = -1;
int sum = 0;
int count = 0;
while(x!= 0){
  x = Integer.parseInt(scanner.nextLine());
  if(x < 0){
    System.out.println("Error! Positive Integers Only");
  }else if (x > 0) {
    sum += x;
    count++;
  }
}
System.out.println("Sum: " + sum);
System.out.println("Average: " + (double)sum / count);

This code is probably one of the best ways to accomplish this task. We use a clear string of If-Else If-Else statements to show that there are two possible operations inside of the While loop. Either the input is negative, in which case we print an error message and restart the loop; or the input is positive and accepted for our calculations. IF the input is 0, we allow the program to flow up to while loop condition–which will terminate the loop.

Subsections of A Worked Example

Summary

Loops are one of the most important building blocks in computer programming. We can use both loops and conditional constructs to control the flow of execution in our programs, allowing us to develop very advanced pieces of software from these simple parts.

Going forward, we’ll cover information about a few other important data types for dealing with larger amounts of data, as well as how we can build our programs to detect and deal with errors more gracefully.

Chapter 6

Methods

Little Steps to Solve Big Problems!

Subsections of Methods

Writing Big Programs

Throughout this course so far, we’ve written several programs. But we may start to notice that we are repeating lines of code over and over again.

As programs get bigger, it can also be difficult to manage all of the code and make sure we are not missing something. A small typing error in one part of the code can become very difficult to find when there is so much code to check.

For example, consider a program that needs to write the same output to multiple output files. Right now, if we wanted to write that program, it might contain code that performs these steps:

open file1
write to file1
write to file1
close file1

open file2
write to file2
write to file2
close file2

open file3
write to file3
write to file3
close file3

open file4
write to file4
write to file4
close file4

As we can see, much of the code in that program is repeated, with only small parts changed. Is there a different way we could write this program to make it simpler?

Methods

YouTube Video
Pseudocode in Video

The video above uses pseudocode to introduce the concept of methods. We are transitioning away from using pseudocode in this course. The intent of the video should be clear, but don’t worry too much about the actual syntax of the examples in this video. We’ll use Java code elsewhere in the text.

Video Materials

The answer lies in the use of methods in our code. A method is a piece of code that can be used by our program to perform an action. However, the biggest benefit of using a method comes from the fact that we can use methods multiple times, helping us avoid repeated code in our programs. We can also provide input to our methods and receive output from our methods, allowing a single method to perform work on a wide variety of data.

Let’s look at an updated version of our previous example - a program that writes the same output to multiple files:

public static void main(String[] args){
    outputToFile("file1.txt");
    outputToFile("file2.txt");
    outputToFile("file3.txt");
    outputToFile("file3.txt");
}

public static void outputToFile(String filename){
    BufferedWriter writer = new BufferedWriter(new FileWriter(filename));
    writer.write("This is the first line of " + filename);
    writer.newLine();
    writer.write("This is the end of the file");
    writer.newLine();
    writer.close();
}

In the Java code above, we have defined two methods, one called main just like we’ve seen many times before, and another method named outputToFile that we can use to write data to a file with the name stored in the filename parameter. The body of the functions are delimited with {}. Just like conditional statements and loops, all method definitions must have a body.

The first method, named main, is the actual code that runs when our program is executed.

The other method, outputToFile, actually performs the work of outputting to the file.

To use a method, we’ve included code that looks like outputToFile("file1.txt"); in our main method. That line is known as a method call or method invocation, which will then execute the code inside of the outputToFile method. So, we might say that we are using that line to “call outputToFile” or “call the outputToFile method”. Either way is correct!

Of course, we also need to be able to provide input to our method, as we do in this example. The next page will describe how that works in more detail.

Function vs. Method vs. Subroutine

Informally, programmers may use the terms function, method, subroutine, and, to a lesser extent, procedure and other terms, to refer to many similar things. In general, they can be used interchangeably in most cases, since it is pretty clear what they are referring to, but for new programmers it can be a bit difficult to understand all of the different terms that are used.

So, to make things a bit clearer, we’ll try to stick with the definitions below for each of these terms:

  • Subroutine: A piece of code that can be executed as part of a program, which may return a value. This is and old term and not generally used.
  • Function: A synonym for subroutine, more common. Python comes with several built in functions.
  • Method: A subroutine that can be executed as part of a class. We’ve been using many methods in our programs already. We’ll learn more about classes in a later module.
  • Procedure: A subroutine that doesn’t return a value, but we’ll generally avoid using this term

To make matters more complex, some languages use all of these terms, each with very precise definitions.

Both Python and Java are pretty loose in their usage, and we will generally use the term “method” to mean any callable code snippet, but may use function and method interchangeably.

For more information on this, feel free to read a relevant post on StackExchange.

Subsections of Methods

Parameters & Arguments

When we are writing methods in our code, we may need to provide some input to our methods. This allows our method to perform the same action using different data each time, making them much more flexible. So, let’s look at how to do that in theory and discuss some of the terminology we’ll need to understand first.

Parameters

When we define a method, we can also list a number of parameters, or inputs, that the method can accept. Depending on the language we are using, we may need to provide either a name, or possibly a type and a name for each parameter. In addition, many languages allow us to accept variable length parameters, which we’ll cover in detail later in this module.

So, in our code, we can define a method that accepts parameters in this way:

public static void foo(String parameter1, String parameter2){
    System.out.println(parameter1);
    System.out.println(parameter2);
}

Typically, each parameter is listed in the method’s definition. So, this example defines a method named foo that accepts two parameters, parameter1 and parameter2. Together, they make up the method’s signature, which allows our programs to find them. Therefore, no two methods may share the same signature. Instead, they must either use a different name, or a different number of parameters. In statically typed languages such as Java, we can also vary the types of each parameter instead, not just the number of parameters.

Arguments

Of course, when we want to call a method in our code, we must provide values for each parameter. Those values are known as arguments to the method. In code, it might look something like this:

public static void main(String[] args){
    foo("abc", "xyz");
}

In that example, we see foo("abc", "xyz"), which is calling our method named foo. Inside, it provides two arguments, one for each parameter of the method. So, inside of our method, the variable parameter1 will be "abc", and parameter2 will be "xyz". Pretty straightforward, right?

Parameters vs. Arguments

Of course, many programmers use the terms parameters and arguments interchangeably as well, but we’ll try to stick to the following definitions:

  • Parameter: an input variable defined as part of a method’s definition or signature
  • Argument: a specific value provided to a method as part of a method call or invocation

Returning a Value

Finally, we may also want to get a result back from our methods, especially if it is performing a calculation or some other task for us. So, we can use a special keyword known as return to provide a value as output from our method. In general, if you call a method that returns a value, you want to catch that value in a variable.

Let’s look at an example:

public static void main(String[] args){
    output = max(5, 42, 3)
    System.out.println(output);
}

public static int max(int a, int b, int c){
    if (a >= b && a >= c){
        return a
    } else if (b >= a && b >= c){
        return b
    } else {
        return c
    }
}

In this example, we have defined a method named max which will return the largest value of its three parameters, a, b, and c. So, in our main method, we are calling output with arguments 5, 42, and 3, which will be stored as a, b, and c, respectively.

Then, in the max method, we use an If-Else statements to determine which one is larger than the other two, and then return that value. So, if we look closely at the code, we should be able to see that it will return 42 as the largest value.

As we can see, our code can contain multiple return statements. However, the method will stop executing as soon as it reaches the first return statement, and will therefore only return a single value. This is really handy if we know the answer we need to output; we can just use the return keyword to stop what we are doing and provide the output.

So, once our max method is complete, the value 42 will be returned. In our main method, that value will be stored in the output variable. So, values returned from a method can be used in an assignment statement, just like any other value. In fact, we can treat a method call just like a variable! As soon as our program reaches a method call, it will stop what it is doing, execute the method, and replace the method call with the returned value. It’s a really handy way to organize our code.

That covers the basics of how a method is created in our code. On the next few pages, we’ll discuss some related topics that will help us understand how these methods work and how we can structure our code to take advantage of methods.

Variable Scope

Another topic we must revisit is variable scope. Recall from an earlier chapter we learned how variables may only be referenced after they’ve been defined, and in many cases only within the method or block they are defined in. Now that we are dealing with multiple methods, we must once again discuss variable scope and how it applies to this situation.

Method Scope

In general, all of the scope rules we’ve learned still apply. For example, a variable declared in a method can only be accessed within that method. In that way, different methods may use the same variable names to refer to different variables. In addition, as we’ve seen in the earlier examples, a method may define parameters using the same variable names as the variables that are used as arguments to that method. It may seem confusing to some, but to others it makes perfect sense.

Class Scope

Most programming languages also allow us to create variables at the class level, inside a class but outside of any method. Those variables can then be referenced within any of the class’s method, allowing us to share data between methods without using parameters and return values.

Here’s a quick example in code:

public class MathOperations {

    double PI = 3.1415926535;

    public static void main(String[] args){
        double r = 3.0;
        double area = calculateArea(r);
        System.out.println(area);
    }

    public static double calculateArea(double r){
        return r * r * PI;
    }

} 

In this example, we have created a class variable named PI to store the value of $ \pi $ for our entire program. Then, we can use that variable just like any other inside of the calculateArea method.

Of course, we can assign and edit class variables just like any other variable:

public class Foo{
    static int people = 1;

    public static void main(String[] args){
        people = people + 1;
        foo();
        System.out.println(people);
    }

    public static void foo(){
        people = people * 3;
    }
}

In this example, the main method will print $ 6 $ as the value of people. Since that variable is declared in the class scope, it can be accessed and changed by any method.

Shadowing Variables– Using the Same Variable Names

Sometimes we may want to have a variable inside of our methods use the same name as a class variable, this is a type of shadowing. Shadowing occurs whenever an inner scope name hides (in its shadow) an outer scope variable of the same name. Here’s a modified version of the code above showing that situation:

public class Foo{
    static int people = 1;

    public static void main(String[] args){
        int people = 1;
        people = people + 1;
        foo();
        System.out.println(people);
    }

    public static void foo(){
        people = people * 3;
    }
}

In this example, the variable people is redeclared inside of the main function. So, it is an entirely different variable than the class variable named people. Because of this, whenever we use the variable people inside of main, we are referring to the method scope or local scope variable people. Therefore, the class-scope value is not updated by the main method, and main will simply print $ 2 $ instead of $ 6 $.

Shadowing is not necessarily bad, and may be unavoidable in large programs using multiple imported modules. It is something that a developer should always be aware of, since it can have unintended consequences.

DRY: Don't Repeat Yourself

One major mantra among programmers is Don’t Repeat Yourself (DRY), and it’s a very important concept to keep in mind when writing programs that can use methods. In essence, anytime we find ourselves writing the same or similar code multiple times in our program, we may want to ask ourselves if it would be better to make that piece of code a method. That way, we only have to write it once, and if there are any problems with that code, we only have to remember to change it in one place instead of everywhere we’ve used it.

DRY Example

To really understand how DRY can improve our code, let’s look at a quick example:

import java.lang.Math;

public class Dry{

    public static void main(String[] args){
        int a = 1;
        int b = 0;
        int c = -4;
        double rootOne = (-b - Math.sqrt(b * b - 4 * a * c)) / (2 * a);
        double rootTwo = (-b + Math.sqrt(b * b - 4 * a * c)) / (2 * a);
        System.out.println(rootOne);
        System.out.println(rootTwo);

        a = 2;
        b = 7;
        c = 3;
        rootOne = (-b - Math.sqrt(b * b - 4 * a * c)) / (2 * a);
        rootTwo = (-b + Math.sqrt(b * b - 4 * a * c)) / (2 * a);
        System.out.println(rootOne);
        System.out.println(rootTwo);
    }
}

Of course, this is a very simple example calculating the roots of a quadratic equation.

$$ax^2 + bx + c$$

However, we see that this program repeats many lines of code to perform the same basic calculation with different values. We can easily move that calculation to a new method, and then use parameters to set the values. So, let’s apply the DRY principle to simplify this program:

import java.lang.Math;

public class Dry{

    public static void main(String[] args){
        quadratic(1, 0, -4);
        quadratic(2, 7, 3);
    }

    public static void quadratic(int a, int b, int c){
        rootOne = (-b - Math.sqrt(b * b - 4 * a * c)) / (2 * a);
        rootTwo = (-b + Math.sqrt(b * b - 4 * a * c)) / (2 * a);
        System.out.println(rootOne);
        System.out.println(rootTwo);
    }
}

There! We’ve moved all of the code for calculating the roots for the quadratic equation, printing each root at the end. Then, we can simplify the code in main by simply calling that method anytime we want to calculate the roots .

This could be further reduced by “pre calculating” the discriminant since it is used twice in the quadratic function.

import java.lang.Math;

public class Dry{

    public static void main(String[] args){
        quadratic(1, 0, -4);
        quadratic(2, 7, 3);
    }

    public static void quadratic(int a, int b, int c){
        d = discriminant(a, b, c);
        rootOne = (-b - Math.sqrt(d)) / (2 * a);
        rootTwo = (-b + Math.sqrt(d)) / (2 * a);
        System.out.println(rootOne);
        System.out.println(rootTwo);
    }

    public static int discriminant(int a, int b, int c){
        return b * b - 4 * a * c;
    }
}

We can also extend this code to make use of the fact that the value of the discriminant tells us how many different roots a quadratic equation will have. By moving code to functions, we can quickly find additional ways to expand upon and improve our programs.

Properly following the DRY principle when writing code will make our programs simpler, easier to maintain, and hopefully easier to debug. In effect, it is definitely a good idea to add a new method to our code anytime we find ourselves typing something twice, or copy and pasting code.

Writing Testable Code

Another big idea related to writing methods in our code relates to testing. Specifically, how can we write methods that are easily testable?

Unit Testing

We won’t go deep into the details in this course, but most programming languages support unit testing, which is the use of automated testing tools to make sure each piece of code works as intended. They are very important for two big reasons:

  1. A properly written set of tests will help confirm that the code works as intended in the first place
  2. Tests that previously passed but fail after an update to our code may highlight an unintended side effect of the changes we made

In fact, many of the automated grading tools in this course make extensive use of unit testing to confirm that our code is working properly, both within this tutorial textbook and in the standalone projects. Now that we are writing code that contains methods, unit tests will become an even bigger part of the grading process in this course.

Writing Testable methods

There are entire textbooks written about the theory of writing code that can be easily tested, and not everyone agrees exactly what the best model is. However, there are a few things that we can keep in mind as we write methods to make them easily testable:

  1. Each method should only perform one action: a method should only do one thing. For example, if a program needs to read data from a file and then write that data to another file, those are two different actions and could be considered two separate methods.
  2. Avoid non-deterministic effects: where possible, avoid a method that returns different values for the same inputs. This could include choosing a random value, or using the system time or other uncontrollable input in the method. These methods are difficult to test due to their random nature. If your method needs a random number, have that number passed in as a parameter.

In the examples later in this course, we’ll see explicitly how to follow some of these guidelines when writing our own code. For now, they are just handy things for us to keep in mind.

Testing Your Code

Read Carefully!

Many students fail to read this section carefully and understand what it means. From this point forward, the grading tools that you have access to before you submit a project ARE NOT the same grading tools used to fully grade your project. This means that the grader in the project may tell you that you have completed the project, but when you submit it you could receive a significantly lower grade.

WHY?

One important skill for a programmer to learn is properly testing your code. So, from this point forward, we’re not going to give you all of the test cases in the project. Instead, you’ll have to carefully read the project description, identify what test cases should be used, and then create and run those test cases yourself.

Consider this - for most of the history of programming, students had to learn without ANY access to automated testing and grading tools, and in fact they may not have been able to run their program many times at all due to the high cost of computing time on early systems. So, students and professional programmers alike had to learn how to properly test and verify their programs either by hand or by careful use of test cases.

To help you develop this skill, we are “taking off the training wheels” and giving you more room to fail. As always, if you find that you are stuck or unsure where to begin, don’t be afraid to contact your instructors for assistance.

Developers are also Testers

Up to this point in the class we’ve had various automated grading tools available to test our code and make sure it works correctly. However, it is important to understand that these buttons are just running code written by other developers (in this case, the instructors of the course), and these tests can have the same limitations as any other program. For example, it is often impossible to guarantee that these tests will accurately test all possible inputs or reach 100% code coverage. Likewise, depending on how the solution is written, the edge cases and boundary conditions may be different than the ones that were originally used for testing.

At this point, we have learned enough programming syntax and terminology to start running our programs manually and developing our own tests. So, from this point forward, many of the projects will not give us access to the full automated grading process before we submit it. In effect, it is now our job to properly test our program using various inputs, both the ones provided and ones that we develop ourselves, in order to be sure that it is working properly.

After we submit the project, the full autograder will still be used to grade our work. The autograder will confirm that your project matches the specification within a reasonable limit. However, we won’t be able to go back in and make any changes after we submit the project. So, we’ll have to be extra careful and make sure our programs are fully tested before we submit them.

Write a test Method

One thing that we can do is add an explicit test method to any class in our program. Our test method may contain any code needed to test our program. We are always allowed to add additional methods to any class as needed - we won’t be penalized for that in grading.

For example, we could add a test method to the previous example to test some of the functions:

import java.lang.Math;

public class Dry{

    public static void main(String[] args){
        quadratic(1, 0, -4);
        quadratic(2, 7, 3);
    }

    public static void quadratic(int a, int b, int c){
        d = discriminant(a, b, c);
        rootOne = (-b - Math.sqrt(d)) / (2 * a);
        rootTwo = (-b + Math.sqrt(d)) / (2 * a);
        System.out.println(rootOne);
        System.out.println(rootTwo);
    }

    public static int discriminant(int a, int b, int c){
        return b * b - 4 * a * c;
    }

    public static void test(){
        int answer1 = discriminant(1, 3, 1);
        System.out.println("Answer 1 was " + answer1 + " and should be 5");
        int answer2 = discriminant(3, 3, 3);
        System.out.println("Answer 2 was " + answer1 + " and should be 27");
    }
}

In the method, we see that we are calling the discriminant method with a few different values. The expected answer values we see in the print statements can be manually calculated and verified using a calculator. It is important that we calculate these manually instead of relying on our program to calculate them - if we did, how would we ever know if it was actually incorrect?

Using a test Method

Once we’ve written a test method, we have to change our program so that it calls the method. The simplest way to do this is to just add that function call at the very top of the main() method:

    public static void main(String[] args){
        test();
        quadratic(1, 0, -4);
        quadratic(2, 7, 3);
    }

Since the test method may produce output or change the functionality of our program, it is always important to remember to REMOVE the test method from our main method before we submit the program.

This Seems Like A LOT More Code

It is a lot more coding. To test a method, we must really understand its function and how it fits with other methods. Understanding how methods work and work together is a key computational thinking skill.

It is very common practice in industry to write several times more lines of code in test methods than the actual method that is being tested. This can be reduced a bit by hard-coding some of the correct answers and just checking a few boundary values, as shown above. However, for more complex projects, we may end up writing many complex tests. This is usually covered in an advanced programming class, so we won’t spend much time on it here.

The advantage testing each method as you write them is that the amount of code you have to search for errors is small. An error you detect must be in the method you just coded.

By the end of the course, projects will be 3-5 classes each with 3-5 methods. If you only do functional checks of the completed project, isolating an error to single method in this case is extremely time consuming.

Chapter 9.J

Java Methods

Methods in Java

Subsections of Java Methods

Methods

YouTube Video

Video Materials

Now that we’ve covered the basic ideas of adding methods to our programs, let’s see how we can do that using the Java programming language.

Declaring Methods

We’ve already seen how to create methods in our programs, since each program in Java already includes a method named main. In general, a method declaration in Java needs a few elements. Let’s start at the simplest case:

public static void foo(){
    System.out.println("Foo");
    return;
}

Let’s break this example method declaration down to see how it works:

  1. First, we use the keyword static at the beginning of this method declaration. That keyword allows us to use this method without creating an object first. We’ll cover how to create and work with objects in a later module. For now, each method we create will need the static keyword in front of it, just like the main() method.
  2. Then, the second keyword, void, determines the type of data returned by the method. We use a special keyword void when the method does not return a value. We’ve already seen this keyword used in our declaration of the main method.
  3. Next, we have the name of the method, foo. We can name a method using any valid identifier in Java. In general, method names in Java always start with a lowercase letter.
  4. Following the method name, we see a set of parentheses () that list the parameters for this method. Since there is nothing included in this example, the method foo does not require any parameters.
  5. Finally, we see a set of curly braces {} that surround the code of the method itself. In this case, the method will simply print Foo to the terminal.
  6. The method ends with the return keyword. Since we aren’t returning a value, we aren’t required to include a return keyword in the method. However, it is helpful to know that we may use that keyword to exit the method at any time.

We’ll cover how to handle method parameters and return values later in this module. For now, we’ll just look at creating simple methods that neither require parameters nor return values.

Calling Methods

Once we’ve created a method in our code, we can call, or execute, the method from anywhere in our code using the following syntax:

foo();

We simply use the name of the method, followed by parentheses, wherever we’d like to call that method. Again, we’ll see how to pass arguments to the method and store the return value later in this module.

Example

Let’s look at a complete sample program to see how this all fits together.

public class Methods{
    public static void main(String[] args){
        System.out.println("Main 1");
        foo();
        System.out.println("Main 2");
        foo();
        System.out.println("Main 3");
        return;
    }
  
    public static void foo(){
        System.out.println("Foo 1");
        return;
    }
}

When we run this program, we should see the following output:

Main 1
Foo 1
Main 2
Foo 1
Main 3

We can also look at a flowchart diagram of this program to help understand how it works:

Method Call Flowchart Method Call Flowchart

As we can see in this diagram, the program starts in the main() method. Inside, it prints Main 1, then calls the method foo(). So, we can follow the dashed line over to foo(), where it will print Foo 1 and return back to main along the same dashed line. Then, we’ll print Main 2 in main(), before calling foo() once again. This time, we’ll follow the dotted line to foo(), where we’ll once again print Foo 1 before returning back to main() and printing Main 3.

Method Signature

The line public static void main(String[] args) is often referred to as a method signature. It contains all the vital information necessary to use the method: its name, what it returns, and what type of parameters it requires. Even public static inform the programmer on where and how to invoke the method, but we’ll cover these key words when we cover classes.

Subsections of Methods

Parameters

YouTube Video

Video Materials

Methods are a very useful way to divide our code into smaller chunks. However, many times we need to provide input to our methods. This allows us to reuse the same code, but with different values each time we call the method. So, let’s review how to add parameters to our methods in Java.

Method Parameters

In Java, we can add parameters to a method declaration by placing them in the parentheses () at the end of the declaration. Each parameter is similar to a variable declaration, requiring both a type and an identifier. We can also define multiple parameters, separated by commas ,.

For example, let’s extend our definition of foo() from the previous page by adding a String parameter named message:

static void foo(String message){
    System.out.println("Message: " + message);
}

Then, when we call that method, we are required to provide an argument of the correct type. That argument will be stored as the parameter’s variable in foo():

foo("Hello World!");

Another Example

Here’s another example. In this case, we are writing two methods, foo() and bar(). They accept multiple parameters, and in main() we call each method using arguments for each parameter.

public class Parameters{
    public static void main(String[] args){
        int x = 5;
        int y = 10;
        int z = 8;
        bar(x, y, z);
        foo(y, true);
    }
  
    static void foo(int output, boolean longMessage){
        if(longMessage){
            System.out.println("The value was " + output);
        }else{
            System.out.println("Val: " + output);
        }
    }
  
    static void bar(int a, int b, int c){
        System.out.println(a + ", " + b + ", " + c);
    }
}

First, let’s look at bar(). When we call this method from main(), we are using x, y, and z as arguments. So, inside of bar(), the value stored in x will be stored in a, y will be stored in b, and z will be stored in c. The parameters and arguments are matched up based on the order they are provided to the method call. So, bar() will output 5, 10, 8 when it is called with those parameters.

The call to foo() is very similar. It only contains two parameters, but each one is a different type. So, when we call that method, we must make sure that the first parameter is an integer, and the second one is a Boolean value.

Subsections of Parameters

Overloading

YouTube Video

Video Materials

There are several other things we can do with parameters in our methods, allowing us to use them in new and more flexible ways.

Method Overloading

Java allows us to create multiple methods using the same name, or identifier in the same scope, as long as they have different parameter lists. This could include a different number of parameters, different data types for each parameter, or a different ordering of types. The names of the parameters, however, does not matter here. This is called method overloading.

For example, we could create a method named max() that could take either two or three parameters:

public class Overloading{
    public static void main(String[] args){
        max(2, 3);
        max(3, 4, 5);
    }
  
    static void max(int x, int y){
        if(x >= y){
            System.out.println(x);
        }else{
            System.out.println(y);
        }
    }
  
    static void max(int x, int y, int z){
        if(x >= y){
            if(x >= z){
                System.out.println(x);
            }else{
                System.out.println(z);
            }
        }else{
            if(y >= z){
                System.out.println(y);
            }else{
                System.out.println(z);
            }
        }
    }
}

In this example, we have two methods named max(), one that requires two parameters, and another that requires three. When Java sees a method call to max() elsewhere in the code, it will look at the number and types of arguments provided, and use that information to determine which version of max() it should use.

Of course, we could just use the three argument version of max() in both cases:

public class Overloading{
    public static void main(String[] args){
        max(2, 3);
        max(3, 4, 5);
    }
  
    static void max(int x, int y){
        max(x, y, y);
    }
  
    static void max(int x, int y, int z){
        if(x >= y){
            if(x >= z){
                System.out.println(x);
            }else{
                System.out.println(z);
            }
        }else{
            if(y >= z){
                System.out.println(y);
            }else{
                System.out.println(z);
            }
        }
    }
}

In this case, we are calling the three parameter version of max() from within the two parameter version. In effect, this allows us to define default parameters for methods such as this. If we only provide two arguments, the code will automatically call the three parameter version, filling in the third argument for us.

Variable Length Parameters

Finally, Java allows us to define a single parameter that is a variable length parameter. In essence, it will allow us to accept anywhere from 0 to many arguments for that single parameter, which will then be stored in an array. Let’s look at an example:

public class Overloading{
    public static void main(String[] args){
        max(2, 3);
        max(3, 4, 5);
        max(5, 6, 7, 8);
        max(10, 11, 12, 13, 14, 15, 16);
    }
  
    static void max(int ... values){
        if(values.length > 0){
            int max = values[0];
            for(int i : values){
                if(i > max){
                    max = i;
                }
            }
            System.out.println(max);
        }
    }
}

Here, we have defined a method named max() that accepts a single variable length parameter. To show a parameter is variable length we use three periods ... between the type and the variable name. We must respect three rules when creating a variable length parameter:

  1. Each method may only have one variable length parameter
  2. It must be the last parameter declared in the method declaration
  3. Each argument provided to the variable length parameter must be the same type

So, when we run this program, we see that we can call the max() method with any number of integer arguments, and it will be able to determine the maximum of those values. Inside of the method itself, values can be treated just like an array of integers.

Subsections of Overloading

Return

YouTube Video

Video Materials

Lastly, one of the most useful things we can do with methods in our code is return a value from a method. This allows us to use a method to perform an action or calculation that results in a single value that we can use elsewhere in our code. We can even use these method calls just like we use variables in other arithmetic expressions. Let’s take a look at how that works.

Returning a Value

To return a value from a method in Java, we use the special keyword return, followed by an expression representing the value we’d like to return. We must also declare the type of that value in our method declaration, taking the place of the void keyword we’ve been using up to this point.

Here’s an example program showing how to use the return keyword and store that returned value in a variable.

public class Return{
    public static void main(String[] args){
        int returnValue = last(1, 3, 5, 7, 9);
        System.out.println(returnValue);  // 9
    }
  
    static int last(int ... items){
        if(items.length > 0){
            return items[items.length - 1];
        }
        return -1;
    }
}

Let’s review this program carefully to see what parts of the program are important for returning a value:

  1. First, instead of void, we use the keyword int in the declaration of our last() method, static int last(int ... items). This is because the method must return a value with the type int.
  2. Inside of the method, we see two instances of the return keyword. Each instance is followed by a value or expression that results in an integer, which is then returned from the method. As soon as the method reaches a return keyword, it immediately stops executing and returns that value. So, if the items variable length parameter is empty, the method will return $-1$. Otherwise, it will return the last item in the items parameter.
  3. In the main() method, we see that we’ve included the method call to last() on the right-hand side of a variable assignment statement. So, once we reach that line of code, the program will call the last() method and store the returned value in the returnValue variable in main()

Compiler Messages

The Java compiler is a very crucial part of making sure that each method we create returns a value correctly. When we compile our code, the compiler checks to make sure that each method that includes a return type other than void will return a value along all code paths. That means that if one branch of an If-Else statement returns a value, then either the other branch or code below it should also return a value.

In addition, it will make sure that the type of the value returned matches the type that is expected by the method’s declaration.

Finally, just like every other variable assignment in Java, when we store the result of a method call in a variable, Java will also make sure that the variable storing the value has a type that is compatible with the type being returned from the method.

So, if we receive error messages from the Java compiler regarding invalid return types or values in our methods, we’ll need to carefully check our code to make sure we aren’t violating one of those rules.

Subsections of Return

A Worked Example

Video Materials

Let’s try one more example to get some practice building code that contains multiple methods. This program will convert volumes measured in U.S. standard cups into either fluid ounces, tablespoons, or teaspoons. A program that makes these conversions is useful for anyone cooking or baking.

Problem Statement1

For this example, we’ll need to build a program that matches this problem statement:

Write a program that accepts interactive keyboard input. It should first ask the user to enter a number of cups, and then have the user select the desired conversion from a list of options. The program will then calculate the correct value and display it with the correct units.

The program should contain one class named Converter, but may contain several methods.

Please enter the number of cups to convert as a floating-point value: .5
Select conversion: 1 (ounces), 2 (tablespoons) or 3 (teaspoons): 3
24.0 teaspoons

That seems like a pretty straightforward problem statement. Let’s see how we might structure the program.

Methods

First, we could look at the problem statement and try to divide the program into a number of methods to perform each action. In this case, it looks like we have a few important actions that could be made into methods:

  1. Getting the user input
  2. Performing the conversion
  3. Printing the converted value and units

Based on that breakdown, we can structure the class so it has the following methods:

  • void main(String[] args) - this is the usual main method for Java. In this case, we’ll handle input and output in this method
  • String convert(double cups, int units) - this method will help us select the proper conversion to be performed based on user input
  • double toOunces(double cups) - this method will convert the given number of cups to fluid ounces
  • double toTablespoons(double cups) - this method will convert the given number of cups to tablespoons
  • double toTeaspoons(double cups) - this method will convert the given number of cups to teaspoons

Control Flow

Now that we have an idea of what methods we need, let’s discuss the overall control flow of the program and the order in which the methods will be used.

The program will start in the main method, just like any other Java program. That method will prompt the user to input a number of cups to be converted, and also will ask the user to choose which conversion to be performed. Once we have those two inputs, we can then perform the computation in the program.

At that point, the main method will call the convert method and provide the two inputs as arguments to that method call. We’ll use the convert method to determine which of the other methods to call, based on the units parameter. That method will then call the appropriate conversion method (either toOunces, toTablespoons or toTeaspoons) and then use the value returned by that method to build the output string.

Each conversion method is very simple - it just uses a bit of math to convert the value in cups to the appropriate value for a different unit of measurement, and then it will return that value.

Scaffolding the Program

Now that we’ve decided what methods to include, we can go ahead and start building the overall structure for our program. It should contain a single class named Converter and the methods listed above. Finally, since we are reading input interactively from the terminal, we’ll also need to remember to import the java.util.Scanner class. So, our overall structure might look like this:

import java.util.Scanner;

public class Converter{

    public static void main(String[] args){
        // Create scanner to read input
        Scanner scanner = new Scanner(System.in);
        // more code here
    }

    static String convert(double cups, int units) {
        // more code here
        return "";
    }

    static double toOunces(double cups){
        // more code here
        return -1.0;
    }

    static double toTablespoons(double cups){
        // more code here
        return -1.0;
    }

    static double toTeaspoons(double cups){
        // more code here
        return -1.0;
    }
}

Notice that each method signature includes the modifiers public and static along with the return type, name of the method, and a list of parameters expected. For every method that returns a value, we’ve also included a default return statement so that the code will compile at this point. Methods that have void as a return type, such as the main method, don’t need to include a return statement.

Also, the order in which the methods are declared inside of a class does not matter in Java. By convention, the main method is typically either the first or the last method declared in the class.

Conversion Methods

Next, we can start filling in the code for the methods. Typically we’d either want to start with the main method, or start with the methods that will be called last in the control flow. In this example, let’s start with the methods that will be called last, which are the conversion methods toOunces, toTablespoons, and toTeaspoons.

We can start with the toOunces method. A standard cup is 8 fluid ounces. So, our method would include this code:

public static double toOunces(double cups){
    return cups * 8.0;
}

That method turns out to be very simple! We can use the same process to write the other two methods. Some helpful conversions:

  • 1 cup is 8 fluid ounces
  • 1 cup is 16 tablespoons
  • 1 cup is 48 teaspoons

Testing Methods

At this point we’ve written some code, and we may want to test these methods just to make sure they are working before moving on. So, we can write some code in our main method to quickly call these methods and check their return values. Here’s a quick example:

public static void main(String[] args){
    // testing code - DELETE BEFORE SUBMITTING
    System.out.println("1 cup should be 8 ounces : " + toOunces(1.0));
    System.out.println("1 cup should be 16 tablespoons : " + toTablespoons(1.0));
    System.out.println("1 cup should be 48 teaspoons : " + toTeaspoons(1.0));

    // Create scanner to read input
    Scanner scanner = new Scanner(System.in);
    // more code here
}

If we put that code in the main method and run it, we should see output similar to this:

Converter Example Converter Example

That’s great! That means our methods are working and seem to be returning the correct values. We may want to try a few other values besides 1 cup just to be sure that the output exactly matches what it should be.

convert Method

The convert method contains the logic for selecting the appropriate conversion method, calling it, and then returning a formatted string to be printed. This method requires two parameters: the cups value to be sent to the conversion method, and the units selection from the user that can be used to determine which method to call.

Since the units item is a mutually-exclusive choice, it makes sense to use an if-else if-else structure in this method:

static String convert(double cups, int units) {
    if(units == 1){
        return toOunces(cups) + " ounces";
    } else if (units == 2){
        return toTablespoons(cups) + " tablespoons";
    } else if (units == 3){
        return toTeaspoons(cups) + " teaspoons";
    } else {
        // error condition
        return "";
    }
}

Main Method

Finally, we can write our main method. It should prompt the user for the number of cups and the units to be converted to. It will then call the convert method and print the answer. We should delete our testing code from the main method at this point.

public static void main(String[] args){
    // Create scanner to read input
    Scanner scanner = new Scanner(System.in);
    System.out.print("Please enter the number of cups to convert as a floating-point value: ");
    double cups = Double.parseDouble(scanner.nextLine());
    System.out.print("Select conversion: 1 (ounces), 2 (tablespoons) or 3 (teaspoons): ");
    int units = Integer.parseInt(scanner.nextLine());
    String output = convert(cups, units);
    System.out.prinltn(output);
}

With that code in place, we should be able to compile and test our program!


  1. Idea adapted from Gaddis, Tony “Starting out with JAVA”, 5th ed, Pearson: New York 2012 ↩︎

Subsections of A Worked Example

Summary

As the programs we develop become larger and more complex, we’ll definitely rely on the ability to create methods to help make our code easier to read, debug, and maintain. methods allow us to write small, self-contained pieces of code that can be reused over and over again, or break large operations into smaller, simpler steps.

From this point forward, nearly any program we write will contain multiple methods. In fact, it is very rare to see any programs beyond just simple scripts that don’t contain at least a few methods.

In the next few modules, we’ll also see how to build classes that represent real-world objects, as well as how to add methods inside of those classes called methods to represent how those objects method in the real world.

Chapter &

Objects

Modelling the Real World in Code!

Subsections of Objects

Modeling the Real World

Papercraft Image of Trees, Birds and Clouds Papercraft Image of Trees, Birds and Clouds1

Let’s take a look at the world around us. There are many things we might see. A computer. A keyboard. A chair. A desk. If we look outside, we may even see more things. A tree. A bird. A cloud.

From a certain point of view, the entire world is made up of things, each with unique features and actions that help define how it differs from other things.

As we continue to write more and more complex programs, it would be very useful to have a way to represent these things in our own software. Thankfully, we can! In this chapter, we’ll learn all about classes and objects, which form the basis of the object-oriented programming paradigm, one of the most common and popular programming paradigms today.

Let’s get started!

Object-Oriented Programming

Data Centric

At its heart, object-oriented programming is about the data. We may talk about how a class stands for a blueprint or outline of a real world item, but what we mean is the data that describes and defines that real world object. It is how we choose to model this data, how we allow access to it and manipulate it, that drives the object-oriented paradigm.

There are four pillars to object-oriented programming, all having to do with data and how it can be accessed and changed.

Encapsulation

Encapsulation refers to data and method hiding. The idea is things outside of the object should not directly access the object’s data. This ensures the data remains consistent with object we are modeling. A Car class might have a speed attribute, and there might maximum values for changing the speed–a max braking or acceleration. Additionally there might be a maximum speed.

Encapsulation says, when you ask the Car object how fast it is going, the object does not give you access to its data, but instead provides you a copy. When you want it to speed up, you don’t change the object’s speed directly; instead you use the object’s accelerate method, and the object changes its own speed.

Inheritance

Inheritance is the idea that like objects share traits, and can go from the generic to the specific. We might have Bird class, with generic sing and move methods, and a wingspan attribute. But then we might have a Parrot class, which is a kind of bird, so it too can sing and has a wingspan; but in this case a Parrot might also have a talk method, and an additional colorScheme variable. Inheritance is a way to link classes so that the the subclass is a super set of the base class – it has all the superclass’s stuff and more.

Abstraction

Abstraction deals with leaving things un-defined. In our Bird class, maybe there is no body (no code) to the move method, just the fact that such a method should exist. The concept that birds move is independent of a particular kind of bird. This would allow a Penguin class to code move as swimming, Ostrich to code it as running, and Parrot as riding on pirate shoulders.

Polymorphism

Polymorphism is the idea that the same method can give you different behaviors. Say we code the Bird class with the beautiful song of the musician wren. But the Eagle class, also a kind of Bird, might override this with a loud screech. Overriding is when a subclass replaces a superclass method. A Parrot and Eagle are both instances of the Bird superclass, but you get different behaviors (sounds) when you invoke their sing method.

That Seems Complicated

It is a lot, but it can be learned a little at a time. This later modules will deal with some basics of encapsulation, and introduce inheritance. Abstraction and polymorphism will be taught more deeply in later courses.

Instance & Driver Classes

Thus far in this course we have always had just one class, and in most cases one method. So, while we have been following some object-oriented conventions like starting in main(), our designs have not followed an object-oriented programming paradigm. This was deliberate, as the first few modules are necessary to cover the basics of program control.

We will begin bending our designs toward object-oriented programming with this module by introducing instance and driver classes. The instance class holds certain data and all the methods to access and manipulate that data. The driver class (typically only a main() method) holds the logic for how and when to use the data. The driver generally only has indirect accesses to the instance’s data through its methods1

Driver instance image Driver instance image

One of the things that makes OOP so powerful is this simple driver-instance idea can be used to model fairly complex real-world things. For example when you use a web browser for research. You act as the driver, you know how to uses the browser and the information you want. The browser knows how to interact with the internet and all the little details to fetch and display information.

Real world driver instance example Real world driver instance example


  1. this restriction is relaxed in this tutorial. ↩︎

Classes

The first step in creating a program that can represent things in the real world is to determine which things we’d like to include in our program, and then create classes that can describe the different types of objects.

Class

In programming, a class describes an individual entity or part of the program. In many cases, the class can be used to describe an actual thing, such as a person, a vehicle, or a game board, or a more abstract thing such as a set of rules for a game, or even an artificial intelligence engine for making business decisions.

In object-oriented programming, a class is the basic building block of a larger program. Typically each part of the program is contained within a class, representing either the main logic of the program or the individual entities or things that the program will use.

Every Class is a Type

Every time we define a new class, we create a new type.

public class Dog {
    // class definition for Dog
    String name;

    public Dog(String aName){
        this.name = aName;
    }
}

public class Driver {
    public static void main(String[] args) {
        Dog x = new Dog("rover");  // Dog is now a data type
        Dog y = new Dog("spot");   // We use it when declaring a variable
    }
}

Example

Teacher and Student Clipart Image Teacher and Student Clipart Image

For example, let’s consider a program that could be used to store the information about students and teachers at a school. For this program, we could create 2 instancs classes: Student, Teacher, and a driver class called Main. To help represent this program, we can use a UML Class Diagram like this:

UML Class Diagram showing Main, Student, and Teacher class UML Class Diagram showing Main, Student, and Teacher class

In the diagram above, we see three boxes, one labeled for each class. Below the names we see entries for the fields and methods in the class, which we’ll discuss on the next page.

Obviously, the Student class can be used to represent a single student in school. Likewise, we’ll use the Teacher class to represent a teacher. Finally, we’ve also included a Main class, which will store the actual logic for the program we’re creating. However, right now the classes are just names, and aren’t very useful in that form.

UML Diagrams

The Unified Modeling Language or UML is a standard way to visualize the structure and design of a software program. UML includes many different types of diagrams, including class diagrams, use case diagrams, sequence diagrams, and more.

In this course, we’ll use some simple class diagrams to help describe the structure and layout of classes in our programs. Those diagrams are very simple to read and understand, and you won’t be asked to create any diagrams of your own right now. If you take some later programming courses, we’ll cover more information about UML diagrams and how to work with them there.

If you want to learn more, here are a few helpful links:

Object Features

YouTube Video

Video Materials

To make our classes more useful, we must give them features to help define the properties and actions of that class. So, let’s look at each of those in turn and discuss how we might use those in our programs.

Object Attributes

First, each class can have a set of attributes, sometimes known as fields, to describe the data stored by that class. In programming, these would be the variables stored within the class itself. These attributes represent the different properties of the thing the class represents, helping to distinguish it from other things in the world.

For example, an Ingredient for a recipe might have a name of the ingredient, an amount and a units. These might be “flour”, 4 and “cups”.

Inside a Unified Modeling Language (UML) class diagram, instance attributes go in the section immediately under the class name. Typically both attribute’s proposed identifier and type are included.

UML Class Diagram showing Ingredient UML Class Diagram showing Ingredient

Object Methods

In addition, each class can have a set of methods or actions that it can perform. In programming, these are the methods stored available to the object to help manipulate or provide the object’s data. Lets assume we have and object ingrd1 of type Ingredient with the following attributes: name = "flour", amount = 3.0, unit = "cup".

These methods may represent actions taken directly on the attributes. Our Ingredient class has three methods:

  • toString(): returns a string describing the object; Something like amount + units + " of " + name
    • ingrd1.toString() would return the string “3.0 cup of flour”
  • scale(factor): returns a new ingredient object scaled to the provide factor
    • ingrd2 = ingrd1.scale(2.0) results in
      • ingrd2 with name = "flour", amount = 6.0, unit = "cup".
      • ingrd1 is unchanged
  • convert(units): returns nothing. Changes the object’s unit attribute to the provided value and adjusts the objects amount attribute so that is correct for the new units
    • ingrd1.convert("ml") results in ingrd1 now containing name = "flour", amount = 709.1, unit = "ml"

Here, each instance method is listed in the lower part of the UML class diagram. It is annotated with the types of its expected parameters and return value or void if the method does not return any value1

Designing Classes

When designing a class for a program, it is important to make sure that each class includes the attributes and methods needed to represent the object fully within the program. However, we also don’t need to include every single attribute and method we can think of. Sometimes it is best to be as simple as possible, only including the ones that will be used within the program. This helps make our code simple and easy to read.

A great way to start is to make a list. We can ask ourselves questions such as “what information is needed to identify a single student?” or “what actions can a teacher perform in this program?” Typically the answers to those questions will help us build our classes, and eventually build our entire program.

Modeling

Classes are not typically modeled in pseudo code. The design function usually creates and UML diagrams from which developers work. Developers usually use pseudo code to develop individual methods or work through the logic of complex method call-chains.


  1. Some UML diagrams may also use void instead of an empty parameter list if the method takes no parameters. ↩︎

Subsections of Object Features

Instantiable Classes

A class can serve many functions, the most basic of which is to serve as a blueprint for the data and methods used to access and manipulate that data. We will refer to these as “object” or “instance” classes. An object class is a class with the primary purpose of encapsulating and manipulating related data.

When you instantiate an object of a class, you create a variable of that type. The class definition is only a only the specification for each of those items. So, let’s look at the next step, which is to create objects based on the classes we’ve defined.

Objects

Once we’ve created a class, we can then use it to instantiate an object based on that class. Let’s break that statement down a bit.

The word instantiate comes from the word instance, which means “an example or single occurrence of something.” So, we’re creating a single example of a class, which we call an object.

Most high level programming languages create an object by calling a special function generically called a constructor, which usually has the same name as the class from which you are trying to create an instance. For example, we can define our Ingredient class:

public class Ingredient {
  String name;
  double amount;
  String unit;

  public Ingredient (String aName, double anAmount, String aUnit){
    this.name = aName;
    this.amount = anAmount;
    this.unit = aUnit;
  }
}

Then, elsewhere in our code, we can instantiate Ingredient objects by calling the constructor - effectively, we just call the data type itself as if it were a function along with the new keyword, which will return a new instance of that object!

Ingredient flour = new Ingredient("Flour", "1.0", "cups");
Ingredient sugar = new Ingredient("Sugar", "2.0", "cups");

Here flour and sugar are both variables of type Ingredient, but they represent different things and would each contain their own name, amount and unit.

Some Vocabulary

Object-oriented programming introduces a large number of new terms, each with very specific uses. Here is a quick overview of some of the new terms we’ve learned so far:

  • Object-Oriented Programming: A programming paradigm that uses objects as the basis of the program’s structure
  • Class: A specification of a new data type in a program, including fields and methods, that are used to describe a single part of a program
  • Object: A unique instance of a class, representing a single concrete item in the program
  • Instantiation: The process of creating an object based on a class
  • Attribute: A single variable defined in a class or stored in an object; also called a field
  • Method: An action that can be performed by a class or object; typically to access or change an Attribute; also called an action
Class Descriptions

Classes are a versatile programming constructs. Their combination of data and methods make them great containers for related information and procedures. Some generic groupings would be:

  • Object