10 KiB
After that complicated stuff we did in the last tutorial, how about we take it down a notch in this one and talk about some additional things that I haven't mentioned this far, but that will still be very useful to you as a programmer. Most of these things won't really have much connection to each other, but I'll give you an example at the end of this tutorial that combines some of them into a single use case.
Shorthands
Java has some shorthands to make addition and other mathematical operations faster. As there isn't really that much to explain (they're really just vocabulary), here's a list of them:
// These two statements are equivalent
// (This also works with all other math operators)
i = i + 2;
i += 2;
// When adding or subtracting 1, you can just do this:
i++;
i--;
Primitive Types
Primitive types (also called native types) are variable types in a language that are baked into the very foundation of the language. That means they don't derive from a class, but they just exist as part of the language itself. The two primitive types you already know are int
(32 bit integers) and boolean
(true
and false
). In Java, primitive types always start with a lowercase letter (instead of an uppercase letter for classes).
Along with int
and boolean
, there are some more primitive types that could come in useful when programming. Here are the ones I haven't mentioned yet, quickly summed up:
// "char" represents a single character.
// Characters use single quotes ' rather than double quotes " (which are reserved for strings).
char character = 'A';
char fourthChar = "Hello World".charAt(4); // o
// "double" represents a 64bit floating point number
// which is a number with a decimal point
// It's called a double because it has double the precision of float (see below)
double d = 3.14;
// "float" represents a 32bit floating point number (less precise than double).
// To differentiate floats from doubles and ints, you have to append an "F".
float f = 12.5F;
// "long" represents a 64bit integer (which can store a lot higher numbers than int)
// To differentiate longs from ints, you have to append an "L".
long l = 9223372036854775807L;
// "short" represents a 16bit integer
short s = 32767;
// "byte" represents an 8bit integer
byte b = 127;
What about String
?
Oh boy. Strings are quite a mess in Java, because they behave like primitive types (more on that later), but they really are classes. Also, you can just initialize a string using quotes ""
instead of having to call new String()
, which makes them behave differently than any other class in the language. This complication is also the reason that you can't just compare two strings using ==
, but you have to use .equals()
instead. Meh.
Pass-by-reference vs. pass-by-value
An important thing to know about primitive types compared to objects is the behavior they show when being passed into a method that accepts parameters. Let's look at the following code as an example:
public class Main {
public static void main(String[] args) {
int i = 10;
addOne(i);
System.out.println(i); // still 10, what gives?
}
private static void addOne(int i){
i++;
}
}
As you can see, the addOne
method adds one to the parameter passed into it. But despite that, the code in our main
method still prints out the number 10. This happens because primitive types (and String
, hurr durr) use what is called pass-by-value: When calling addOne
, its i
variable is just set to the value of the i
variable from our main
method. It doesn't know about the actual variable we pass in, just its value.
This behavior, however, is different when using Objects. Let's rewrite the code above to use a custom class that contains an int
field:
// Thing.java
public class Thing {
public int i;
public Thing(int i) {
this.i = i;
}
}
// Main.java
public class Main {
public static void main(String[] args) {
Thing thing = new Thing(10);
addOne(thing);
System.out.println(thing.i); // 11
}
private static void addOne(Thing thing) {
thing.i++;
}
}
As you can see, the program now prints out 11 instead of 10. That's because of something Java uses called pointers1: Our thing
variable in the main
method doesn't really store the actual instance, but instead, it stores the location of that instance in memory. So when passing that variable into the addOne
method, all we pass is the information "The thing we're trying to modify sits in this part of memory, so modify the data there, please." That's why, when we then change the Thing
's i
variable, it's also modified outside of the method.
null
Any variables that can store objects rather than primitive types (our thing
variable from before, for example) can have a state where they don't point to an object, but instead, point to nothing. This "nothing" is called null
.
Thing noThing = null;
Using null
can be useful in a lot of cases; however, it can also be quite dangerous: Trying to call a method or interact with a field of a variable that is null
causes the program to crash:
Thing noThing = null;
System.out.println(noThing.i); // crashes
Just like any other objects, you can use null
in comparisons:
if (noThing == null) {
System.out.println("I don't have a thing!");
}
Arrays and Lists
Arrays and Lists are two data types that you'll be using a lot of, because they're super useful. Their names kind of already give away what they do, but in case they don't mean anything to you: Arrays and lists are two ways to store multiple pieces of data easily without having to manage each piece on its own. This can be useful for stuff like students in a classroom, where you won't really know for certain beforehand how many students will be there at any given day. The difference between Arrays and Lists is that the former has a fixed size, while the latter has a dynamic size.
Arrays
Let's take a look at arrays first. An array has a fixed type, meaning that the objects in it can only be of the type you specify when creating the array. The variable type of an array of strings, for example, is written as String[]
. To initialize an array, you have two options: Either you create an empty array based on a fixed size (where every slot will contain null
to start with), or you create an array that already has stuff in it:
int[] numbers = new int[10]; // empty array with 10 slots
String[] names = new String[]{"Justus", "Peter", "Bob"};
You can query and modify the data in an array using array brackets []
like so:
String justus = names[0]; // get the 0th entry
names[1] = "Peter Shaw"; // modify the 1st entry
Note that, in Java, the first slot in an array has an index of 0, which means that the last slot of an array always has the index length-1
. Every array has a length
field which stores the amount of slots an array has, meaning you can query the length of numbers
by writing numbers.length
.
Lists
Lists2 work much in the same way, with the main difference being the way you identify them. Lists use something called generic types3 to allow you to also give them a fixed type. The variable type of a list of strings, for example, is written as ArrayList<String>
, using angle brackets <>
(which are really just "greater than" and "less than" signs). To create a list, simply call its constructor using new
:
ArrayList<Integer> numbers = new ArrayList<>();
ArrayList<String> names = new ArrayList<>();
Note that, to create a list containing a primitive type, you need to specify its wrapper class instead of the native type itself4. The wrapper class for int
is Integer
, and the wrapper classes for the other types are just their names with an uppercase first letter (Boolean
, for example).
Also note that, when copying or writing this code in your IDE, it will automatically add import
statements to the top of the class. All they do is make classes from other locations available to the current class, but you don't have to worry about them too much.
You can query and modify the data in a list using the methods from the ArrayList
class:
names.add("Justus"); // add entries
names.add("Peter");
names.add("Bob");
String justus = names.get(0); // get the 0th entry
names.set(1, "Peter Shaw"); // modify the 1st entry
// the length of a list can be read using the size() method
int length = names.size();
Note that, when trying to access an index of a list or an array that is either less than 0 or greater than or equal to its length, your program will crash, so be careful.
Conclusion
So today, you learned some additional useful things about Java that I didn't really get a chance to mention at an earlier point. So let's write some code that makes use of them! If you want, you can try to solve the following problem, which is an extension of the problem from the previous tutorial:
Let's imagine you're managing a small car dealership. You want to have a way of managing all of the cars you have in stock. This includes keeping track of their brand names, their horsepower, their license plate texts and their mileage. Currently, you have 15 parking spots for cars you can sell, 4 of which are already occupied with cars. Additionally, you keep track of a list of all the customers you have had so far (namely, you store their first and last names as well as the city they live in). At the current time, you've already had 3 customers. Additionally, you want to have a way of adding a new car to the first available parking spot, as well as a way to store the data of a new customer easily.
If you're stuck, you can get some hints or look at my solution.
Happy coding!
-
Java's pointers work a lot differently from pointers in lower-level languages like C, because they're implicit: You don't create or manage them yourself. They're still called pointers though, so yea. ↩︎
-
Java has multiple types of lists (including, but not limited to
ArrayList
andLinkedList
), but for the purpose of this tutorial, we'll only be looking atArrayList
since it's the most commonly used one and it also goes hand in hand with arrays. ↩︎ -
More on those in a later tutorial, probably. They're pretty useful. ↩︎
-
Which is also just another annoying property of Java that could've been implemented a lot better (like in C#), but oh well, it is what it is. ↩︎