Objects & Classes
Table of Contents
Introduction
For, since all things that exist are only particulars, how come we by general terms; or where find we those general natures they are supposed to stand for? Words become general by being made the signs of general ideas: and ideas become general, by separating from them the circumstances of time and place, and any other ideas that may determine them to this or that particular existence. By this way of abstraction they are made capable of representing more individuals than one; each of which having in it a conformity to that abstract idea, is (as we call it) of that sort." — John Locke, An Essay Concerning Human Understanding (§ 3.3.6)
Abstraction is the most consequential mental power that we have as human beings. It allows us to generate universal ideas from particular phenomena. For example, as I write this paragraph I am wearing clothes. In fact, I'm thinking of a certain article of clothing. As clothing, its function is to cover my body. This article of clothing is often referred to as a t-shirt as its shape resembles the letter T. The t-shirt I'm wearing is grey, it is made of 100% cotton, and has a brand logo affixed to it. There were likely others manufactured with an identical design, but this is the only one covering me at this intersection of time and space.
What we've done above is go from a universal idea to a particular phenomena. There are many sorts of things which could be considered clothing, but there's only one thing that I am currently wearing. Categorizing things is often very convenient. When I write "t-shirt", you know exactly what I mean because you have in your head the general concept of what a "t-shirt" is. The mental image may not immediately have a color, weight, or texture associated with it but it does have a shape as the shape is what the concept represents. You also know that the "t-shirt" is an article of clothing which denotes a concept of "covering oneself". We constantly use words to represent concepts of things and not the things themselves which includes both their attributes and their functionality.
In Java, the concept of a t-shirt would be called a class, and the specific grey cotton one I am wearing right now would be called an object.
Objects in Java
Object-Oriented Programming (OOP) languages leverage the innate human power of abstraction to allow for programmers to define their own types called objects. The idea surrounding objects is simple: all general concepts of things have shared characteristics and behaviours. For example, a car will be a certain color (characteristic) and will be able to accelerate (behavior). However, the color and acceleration of a car depends on the specific instance of the actual car itself. We can define a simple Car object below:
1: class Car{ 2: int color; 3: void accelerate(){ 4: // Speed up... 5: } 6: Car(int c){ 7: color = c; 8: } 9: }
Here we have a Car class definition with one field, color, and one method, accelerate() (I will explain the class syntax in greater detail below). I can instantiate two Car objects like so:
1: Car red_car = new Car(0xFF0000); // Red Car 2: Car blue_car = new Car(0x0000FF); // Blue Car
"Instantiation" refers to creating an instance of a class in the form of an object. The class definition of Car holds the blueprints for the creation of a Car object whose reference is stored in the variable red_car.
Why Use Classes?
Classes are incredibly useful when programming in Java. They allow for programmers to define type-specific logic to methods that would not be aware of it otherwise. For example, we could utilize an array as if it were a derived type like Frac as follows:
1: public class FracArray { 2: static void printFrac(int[] arr){ 3: System.out.printf("%d/%d%n", arr[0], arr[1]); 4: } 5: 6: public static void main(String[] args){ 7: int[] frac1 = {1, 2}; 8: int[] frac2 = {3, 4}; 9: String[] frac3 = {1, 2, 3, 4, 5}; 10: 11: printFrac(frac1); 12: printFrac(frac2); 13: printFrac(frac3); // Does this compile? 14: } 15: }
The above will because printFrac expects an int[] and is provided exactly that three times. However, it has no way of enforcing that the array truly represents a fraction—again, nothing stops a programmer from passing {1, 2, 3, 4, 5} and having only the first two elements used silently. The method has no type information telling it what the array is supposed to represent.
If we instead define a proper Frac class:
1: public class Frac { 2: int n; 3: int d; 4: 5: Frac(int num, int den){ 6: n = num; 7: d = den; 8: } 9: 10: void print(){ 11: System.out.printf("%d/%d%n", n, d); 12: } 13: 14: public static void main(String[] args){ 15: Frac frac1 = new Frac(1, 2); 16: Frac frac2 = new Frac(3, 4); 17: 18: frac1.print(); 19: frac2.print(); 20: } 21: }
Compiling and running:
josephraskind@stargazer:/tmp/class$ javac Frac.java; java Frac 1/2 3/4
Now Frac carries its own type information. The compiler knows exactly what a Frac is, what fields it contains, and what operations are valid on it. Passing anything other than a Frac to a method expecting one is a hard compile-time error rather than a silent misbehavior. The advantage of classes is that they bundle data and behavior together under a named type that the compiler can reason about and enforce.
Defining a Class
The syntax for defining a class is rather simple:
access_modifier class ClassName{ fields; methods() }
Classes can be tagged with "access modifiers" which denote the "visibility" of the class1. After the class keyword is used the name of the class can then by declared. In Java, the typical convention is to use "Upper CamelCase" or "PascalCase" where every word is capitalized (i.e. "buffered input stream" becomes "BufferedInputStream"). The scope symbols, {}, are use to define all of the fields and methods that are within the scope of the class definition.
Instantiating an Object
In Listing 2 you saw how to instantiate an object, but let's break down exactly what's going on. The second half of the assignment statement is the instantiation expression. The instantiation is denoted by the new keyword. new is used to create a new object instance of a defined class. What follows after new must be the name of a valid class and arguments that are supplied to the class' constructor (I will explain what a constructor is in the next chapter). If everything checks out from the compiler's end, when the program is run the class definition will be used to create an instance of the object in memory which represents that blueprint.
Objects contra Primitives
Later on we will discuss how Java differentiates object reference types and primitives. For now, we can stick to a conceptual understanding of their differences. Objects represent complicated derived types which are composed of attributes in the form of primitives and other objects, and behaviours in the form of methods. Simply put, objects are more complex, fully featured types relative to primitives. As an example, lets take the standard library defined String class vs the char primitive:
1: public class CharVsString { 2: public static void main(String[] args) { 3: 4: // char: a primitive, just a single character value 5: char c = 'H'; 6: 7: // char has no methods --- these would not compile: 8: // c.toUpperCase(); 9: // c.isLetter(); 10: 11: // String: an object built from chars, with rich behavior 12: String s = "Hello, World!"; 13: 14: System.out.println(s.length()); // 13 15: System.out.println(s.toUpperCase()); // HELLO, WORLD! 16: System.out.println(s.contains("World")); // true 17: System.out.println(s.replace("World", "Java")); // Hello, Java! 18: System.out.println(s.substring(7, 12)); // World 19: System.out.println(s.charAt(0)); // H --- gives back a char 20: System.out.println(s.isEmpty()); // false 21: System.out.println(s.startsWith("Hello")); // true 22: 23: // char is just a number under the hood 24: System.out.println((int) c); // 72 25: } 26: }
We can see that the String class provides quite a lot of functionality over the simple char primitive.
Exercises
- The chapter draws an analogy between classes and objects using a t-shirt. Think of your own real-world concept and identify: (a) what the class would be, (b) at least three fields that instances of that class would have, and (c) at least two behaviors (methods) that instances could perform. Write your answer in plain English before attempting any code.
- Looking at Listing 1, the
Carclass has one field (color) and one method (accelerate). Extend the analogy: what other fields and methods would a more completeCarclass need? List at least three of each and describe in plain English what each field stores and what each method does. Compile and run the
Fracclass from Listing 4. Then attempt to add the following line tomainand recompile:Frac frac3 = new Frac("hello", "world");
Record the compiler error. In your own words explain why Java refuses this and how this demonstrates the advantage of classes over plain arrays as shown in Listing 3.
Compile and run the
CharVsStringexample from the chapter. Then attempt to add the following lines and recompile:char c = 'H'; c.toUpperCase();
Record the error. Why can you call
toUpperCase()on aStringbut not on achar? What does this tell you about the difference between primitives and objects?- The chapter states that a class is a blueprint and an object is an instance of that blueprint. In Listing 2, two
Carobjects are created from the sameCarclass. What dored_carandblue_carhave in common and how do they differ? Could you create a thirdCarobject from the same class without modifying the class definition? - The chapter uses the word "instantiation" to describe creating an object. Break down the line
Car red_car = new Car(0xFF0000)piece by piece and explain what each part does:Car(left of=),red_car,new,Car(0xFF0000). - Python uses the same dot notation as Java for accessing object behavior — for example
"hello".upper()in Python is equivalent to"hello".toUpperCase()in Java. List threeStringmethods from the chapter example and find their Pythonstrequivalents. Are there any methods in Java'sStringthat Python'sstrdoes not have, or vice versa? - The chapter states that
charis "just a number under the hood" and demonstrates this by casting'H'tointto get72. Using what you know from the data chapter about ASCII encoding, verify this by hand. Then write a short Java program that prints the integer value of at least five different characters and verify each against the ASCII table.
Footnotes:
I explain access modifiers in greater detail in the next chapter