☰ Contents Classes that Share Behavior

Growing ImageData

Student: Now I want to expand my implementation of ImageData. With this new ImageQuery interface, that is much easier to do. How about I give each image a file size? I’ll call it sizeKb for size in kilobytes. I don’t really know how big a kilobyte is, but I checked my File Explorer on my laptop and it shows a size in KB next to a bunch of images I downloaded from Google.
Prof: Good idea, and nice job looking at real-world examples to base your new fields off of. But, before we add new fields, maybe we should look to see if there is anything we already have that we haven’t added a record for?
Student: Oh, there is that String keywords field that we haven’t been using. I’ll add a record for checking keywords as well.
Prof: Good idea. So what two new records are you going to create?
Student: For the file size, I’ll make a SmallerThanKb record that takes a kilobyte limit and checks if the file size is smaller than it. For the keywords I’ll make a HasKeyword record that takes a String word input and checks if it matches the keywords. Strings are compared with .equals(), so I’ll use that.
Student: That’s strange. The image’s keywords are "cat sleeping", and I asked for "cat", but the assertion failed. Is .equals() broken?
Prof: Nope! .equals() did exactly what you told it. It checks whether two Strings are exactly the same, and "cat sleeping" is not exactly "cat".
Student: Oh, so should I change String keywords to be String[] keywords instead? Then I could use a loop to check if any of the String values are equivalent.
Prof: You could do that, but there is another option without changing the ImageData record. You want to know whether the keywords contain the word. Strings have a method for that: keywords.contains(word) returns true if the word appears anywhere inside the String.
Student: So I swap .equals() for .contains(). I still think String[] would be better… did you purposely make it a String so that you could teach me contains()?
Prof: Perhaps. Now let’s put the two new records to work. Remember how you didn’t like typing all those news, and wished you could write isBig.and(isPng)? Look at the ImageQuery interface in the program below. It has three new methods, and each starts with a word you haven’t seen: default.
Student: What does default mean? The matches method had no body in the interface. Every record that implements ImageQuery had to have their own method body.
Prof: That’s the difference. A default method (a method with default) does have a body inside the interface, and every record that implements ImageQuery gets it without having to write it themselves. So we write it once, in the interface, instead of in HasKeyword, SmallerThanKb, Not and every other record.
Student: So this is whichever query I call .and() on? In cat.and(light), this is cat and other is light?
Prof: Exactly. Here, try it out yourself.
Prof: Great job! Now there is still technically a bug with our implementation, but it mostly has to do with the limits of using contains. We don’t have to worry about fixing it here, but it’s always important to think about edge cases. Can you think of what some issues with using contains might be?
Student: No not really… It looks like it’s working perfectly fine.
Prof: Imagine that we want to check if the ImageData keywords contain "cat", and the image is of a bunch of lecture halls at UC San Diego. Maybe it has String keywords set as building names like "peterson mosaic catalyst".
Student: Oh! I see the problem. contains would count the cat in catalyst as a match, so a user looking for cats would get images of buildings instead.
Prof: Yep, these are the kinds of edge cases you have to keep an eye out for. Another one to keep an eye out for is that contains is case-sensitive. So Cat doesn’t contain cat. Think a bit about how you might fix these edge cases, but now we are going to move on to using interfaces in another context.

Practice

An image’s keywords are the String "cat sleeping". Which of these is true?
  1. keywords.equals("cat")
    .equals() needs the Strings to match exactly, and "cat sleeping" is not "cat".
  2. keywords.contains("sleeping cat")
    contains() looks for that exact sequence of characters, in order. "cat sleeping" has "cat" before "sleeping", not after it, so "sleeping cat" never appears.
  3. keywords.contains("dog")
    There is no "dog" anywhere inside "cat sleeping".
  4. keywords.contains("cat")
    "cat" appears inside "cat sleeping".

The student’s String[] idea works too. Let’s build it step by step. Each keyword is now its own String, so .equals() asks the right question: is this one keyword exactly the word we want?

  1. Change keywords to hold many Strings instead of one.
  2. Loop over every keyword in the image.
  3. If a keyword equals the word, we found a match, so we can answer true right away.
  4. If the loop finishes and nothing matched, the answer is false.
record ImageData( keywords, String fileType, int width, int height, int sizeKb) {}

interface ImageQuery {
boolean matches(ImageData img);
}

record HasKeyword(String word) implements ImageQuery {
public boolean matches(ImageData img) {
for ( keyword : img.keywords()) {
if (keyword.(this.word)) {
return ;
}
}
return ;
}
}

void main() {
System.out.println("All tests succeeded!");
}