15-100 Lecture 4 (Wednesday, September 6, 2006)

Concatenating Strings: The +-operator

We played around a bit with the println() method. The list below shows some of what we saw. Notice that each line has a "comment" after it showing the output that we'd see on the screen. Remember, Java ignores everything on a line after a "//".

Notice that we can combine Strings using the "+" operator.

Mathematical operators

Let's consider one of the examples from above:

What happens if we remove the ""-quotes?

Without the ""-quotes, the output is the mathematical result. When the +-operator is applied to a number, it adds. The same is true of other common operators:

Variables and Types

Often times what seems to be the same value can be interpreted differently because of what is understood about its "type". For example, when we added "1" and "2", the result was "12". This is because we understood the types to be Strings, or collections of characters. As a result, when we added them, we did it by concatenation.

But, when we added 1 and 2, without any quotes, we used a mathematical interpretation of the +-operator. The result was the number 3. This is because we interpreted the 1 and the 2 to be numbers, so we used a mathematical interpretation of the +-operator.

In general, anything ""-quoted is interpreted to be a String. Any number, without quotes, and without a decimal point is considered to be an integer, or, in the language of Java, an "int".

Here's a fun example:

Notice the comment above. The division of 7 by 2 results in 3. This is because the division is "integer division". The "int" type cannot hold a fraction -- not at all. As a result, it can't round up. It doesn't even truncate, really -- it just never even stores the fractional remainder.

If you can imagine long division. You've got the integer component on top of the division bar. And, all the way at the bottom, you've got the remiander. It only sees or keeps what is on top of the bar.

Variables

All of the examples we've done so far have involved "literal" or "immediate" values. In other words, the data values have been stored right in the middle of our code, right where they were to be used.

But, what if we want to store them earlier? To do this, we can ask java to give us a place to put them. Then, we can use them from there later. Asking java for this space is known as "declaring a variable".

We call the space a "variable", because its value can vary -- we can not only read it later on -- we can change it, too.

Here, check out an example:

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

      int numerator;
      int denominator;
      int result;

      numerator = 6;
      denominator = 3;

      result = numerator/denominator;

      System.out.println (denominator); // 2
      System.out.println ("The result is " + denominator); // The result is 2

    }
  }
  

Notice these three lines:

      int numerator;
      int denominator;
      int result;
  

These three lines demonstrate the syntax for declaring variables:

    type variableName;
  

There are several different types available in Java. And, you can create your own. We'll begin exploring these next class.

Assignment

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

      int numerator;
      int denominator;
      int result;

      numerator = 6;
      denominator = 3;

      result = numerator/denominator;

      System.out.println (denominator); // 2
      System.out.println ("The result is " + denominator); // The result is 2

    }
  }
  

Notice these three lines:

      numerator = 6;
      denominator = 3;
      result = numerator/denominator;
  

Do you see the use of the =-operator? It works just like it did in high school math class. It copies the value on the right into the variable on the left.

A Bit of a Contraction

In many cases the initial variable is known at the time it is being declared. In these cases, we can combine the declaration with the "initialization", or the intitial value assignment. The exmaple below, rewritten from above, illustrates this:

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

      int numerator = 6;
      int denominator = 3;
      int result = numerator/denominator;

      System.out.println (denominator); // 2
      System.out.println ("The result is " + denominator); // The result is 2

    }
  }
  

Variable declarations

So far, we have been playing with simple arithmetic. We declared new "variables" capable of holding simple numbers -- numbers without decimal components. By declaring a variable, we asked Java to give us a space, that we could identify by name, to store a value. The space is called a "variable" because its value can change or vary.

The declaration looked like this:

int number;

In the example above, "int" is the "type" of variable. The variable can hold only numbers without fractional (decimal) components. The "number" is just the name we are assigning to the variable.

This is a specific example of the more general case of a variable declaration:

type identifier;

By the way, it is often the case that, at the time that a variable is declared, its initial value is known and should be assigned. This is known as "initialization". Below is an example:

int number; number = 3;

This is such a common idiom in programming that the language provides a bit of a shortcut. The two lines can be contracted as follows:

int number = 3;

Integer Types and Limits

So far, we've worked with one type of number, the int. But, there are plenty more. For example, there are actually three different types that represent numbers without mantissas: "short", "int", and "double".

These types don't actually vary in the abstract -- they all store integer numbers. But, they differ in some computer-centric details. Storing information, like storing physical things, takes space. The ability to store bigger numbers requires more space than the ability to store smaller numbers. It is important to realize that when I write, "larger numbers" and "shorter numbers", I am refering to their magnitude, a.k.a. absolute value, not their signed value.

short variables take up less space than int variables, which in turn take up less space than long variables. The flip side is that longs can store bigger numbers than ints, which can, in turn, store larger numbers than shorts.

The details aren't important for this class, but, for reference, here are the sizes and ranges:

short16 bitsabout +/-30,000
int32 bitsabout +/- 2 billion
long64 bitsabout +/- 9 * 1063

Fractional Types

Similarly there are two different types that can represent numbers with decimals: float and double. In terms of their size, they are basically like int and double, respectively.

Below are a couple of examples of declarations:

  float numberWithFraction;
  double widerPreciseNumberWithFraction;
  

Fractional Types and Approximation

It is a bit harder to describe the limits of what the fractional types can store than it was for the integer types. These types are called "floating point types" because, the position of the decimal pace isn't fixed. It "floats". It is farther to the right for larger numbers and farther to the left for smaller numbers. The result is that large numbers can't keep as much detail after the decimal as small numbers.

Consider this example, which illustrates the concept. Notice how the larger numbers store less detail than the smaller numbers.

  .12345
  1.2345
  12.345
  123.45
  1234.5
  12345.
  

So, with the fractional types, double can support some combination of larger numbers and/or more precision.

Funniness in Computation

Given that the amount of storage for a variable is fixed at the time that it is declared, this "floating point" allows the computer to make the best use of the storage. It keeps the most important information that it can, truncating the details it can't hold.

And, this works out well in most cases. It means that, for example, when looking through a telescope, we don't waste memory on millimeters when we care about kilometers. And, similarly, when working through a microscope, we don't waste memory on kilometers when we want the most accurate microscopic measurements that we can get.

The only time we can get into trouble is when we mix numbers of different sizes in arithmetic. This can generate funniness. For example, multiplying a very small number by a very large number, and then dividing by the very small number will likely not result in the original number. This is becuase, when the number becomes large by multiplication, detail is lost. This detail can't be replaced by division. The result is close to, but not the same as, the original.

Type Casting

When we are making use of variables, Java tries to help keep us out of trouble. One way that programmers sometimes get into trouble is by accidentally assigning variables of one type to variables of another type. This can be a problem if the type on the left, the one to which the value is being assigned, is smaller or more restricted, or just outright different than the type on the right.

In general, when the two types are different, it may be for one of three circumstances:

If the types are simply not compatible, Java will not allow the assignment, one cannot assign a char to any of the number types, or vice-versa. There really isn't a good way of interpreting that assignment.

If the type on the left is larger, or more precise, than the type on the right, the assignment is allowed. For example, an int can always be assigned to a long, or an int to a float. This is because no information can possibly be lost in the conversion.

But, if the types are compatible -- but the type on the left is smaller or less precise, the situation is a bit more sticky. The types can be meaningully converted. For example, a double might lose precision if converted to a float. Or, a long might become meaningless if it is muddled cutting it down to fit into an int. But, the programmer might know that the values are in range or that the loss of precision isn't important.

As a result, Java doesn't allow these types -- unless the programmer explicitly acknowledges the conversion via what is known as a "type cast". A type cast is an explicit request by the programmer for a value to be interpreted using a different type. The cast is only good exactly where it is made and doesn't remain in force beyond the single use.

Below is an example of a type cast. Note the conversion is specified using a parenthetical notation:

  double preciseNumber;
  float lessPreciseNumber;

  ...

   preciseNumber = (float) lessPreciseNumber;
  

Mixed Mode Operation

What happens if integer types and floating-point types are mixed in the same computation? For example, an integer co-efficient, a scalar, is applied to a complex fractional computation?

The integer components are viewed as fractional components. The result is of the fractional type. This happens automatically.

Having said that, I will say that I don't like it. I don't like automatic conversions, because they confuse non-expert programmers. For example, the mathematician or engineer who dabbles in coding. And, sometimes they even confuse the expert programmer.

So, instead, when writing mixed-mode computation, I, myself, always add casts. I think it makes the code easier to understand -- need them, or not:

  double numerator;
  double denominator;
  double result;
  int scalar;

  ...

  result = (double)scalar * numerator/denominator;
  

Other types: char and String

I'd also like to mention a couple of other types. First, the char. A char holds a single character, such as a number, letter, or symbol. The value is understood to be a "character", so if, for example, it happens to be a digit -- it cannot be added.

Additionally, there is the String. A String is a collection of characters, such as a sentence, word, or paragraph. Notice that the String begins with a capital "S". This means that it is a "class" not a simple, "primitive" type. We'll talk more about what that means later.

Literals: Notes on Notation

When we include "literal" numbers embedded within our code, if there is no fractional component, they are assumed to be of type int. If we want them to be of type long or short we need to add an "l" or "s", respectively. Similarly, fractional numbers are assumed to be doubles. So, if we want them to be floats, we need to append an "f".

Another option is to use an explicit type cast. In other words, we can leave the literal as an int, but convert it to the right type. I sometimes like this option, because I think it makes the code more readable.

Please consider the examples below:

  int intNumber = 5; 
  long longNumber= 5l;
  short shortNumber= 5s;
  short shortNumber2= (short) 5;

  float floatNumber = 4.5; // Syntax error; 4.5 is a double
  float floatNumber = 4.5f; // OK
  float floatNumber = (float) 4.5f; // Also OK

  
  

As a result, we'll mostly be using int and double types as opposed to long and float types. It saves us the conversions -- and works well for a wide class of problems, also.

Please also keep in mind, from last class, that String literals need to be "quoted" in order that their contents aren't confused with the code.

  String name = "Greg";
  

And, lastly, char literals need to be within 's' single quotes in order that they aren't confused with Strings or ints or other program elements.

  char c = '5';
  

We're Here To Help!

As always, we're here to help -- please let us know how we can be of service.