Web/main/_posts/2019-10-17-java_5.md

174 lines
11 KiB
Markdown
Raw Normal View History

---
layout: blog
title: "Java Tutorial, Part 5: Things I Left Out So Far"
summary: In this Java tutorial for beginners, we cover some shorthands, some more data types, the difference between pass-by-reference and pass-by-value, null, as well as arrays and lists.
tags: [Java Tutorials]
discuss: https://twitter.com/Ellpeck/status/1184894859133509632
---
2019-10-17 20:11:03 +02:00
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:
```java
// 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:
```java
// "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:
```java
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:
```java
// 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++;
}
}
```
2019-10-20 15:58:40 +02:00
As you can see, the program now prints out 11 instead of 10. That's because of something Java uses called *pointers*[^1]. 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.
2019-10-17 20:11:03 +02:00
# `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`.
```java
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:
```java
Thing noThing = null;
System.out.println(noThing.i); // crashes
```
Just like any other objects, you can use `null` in comparisons:
```java
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:
```java
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:
```java
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
Lists[^2] work much in the same way, with the main difference being the way you identify them. Lists use something called *generic types*[^3] 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`:
```java
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 itself[^4]. 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:
```java
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](https://gist.github.com/Ellpeck/3bdc69845d0d4c0e9511511dd06cfdc1) or [look at my solution](https://gist.github.com/Ellpeck/595b929ce666fca6fb0ed792ca98d71e).
Happy coding!
***
2019-10-17 20:11:03 +02:00
[^1]: 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.
[^2]: Java has multiple types of lists (including, but not limited to `ArrayList` and `LinkedList`), but for the purpose of this tutorial, we'll only be looking at `ArrayList` since it's the most commonly used one and it also goes hand in hand with arrays.
[^3]: More on those in a later tutorial, probably. They're pretty useful.
[^4]: 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.