Student: Ok, so how do I “pass around queries like values”? I don’t get that.
Prof: First we have to talk about a new concept called an interface. An interface is a way to describe shared functionality across records. So in our
ImageData example, what do all of the methods we have been writing share?Student: Hmm, I guess we are asking a true/false question.
Prof: Correct, but go into more detail. What are we saying is true or false?
Student: Well, in
matchesExtension we were checking whether the extension matches a certain file type, and in largerThan we are checking whether the size is larger than some value… which is a kind of matching as well. So really, it’s all checking for matches.Prof: Exactly. Here, I will show you an example and we can work through it together. What new keywords do you notice in this code snippet?
Student: Well, there is that
interface word you mentioned: you created an ImageQuery. It looks like a record, but it doesn’t seem to be storing anything. And I think you forgot to write the code for the matches method!Prof: Hmm, you’re right.
ImageQuery isn’t storing any data, is it? And the matches method doesn’t have a return statement… let’s keep going. What is the other detail you notice?Student: You used a new keyword,
implements, and you put it outside the record? When I read it out it says “record LargerThan implements ImageQuery.” And then each of the new records you created has a matches method with the same signature as in the interface.Prof: Good noticing!
Student: But I don’t understand why. Why do you have to make
ImageQuery if you are going to have to write out the method for matches every time anyway? In that case we could just make sure each record has the matches method and we could call matches on whatever we wanted?Prof: Well, there is one other detail at the end. Notice how we are able to declare both of these records as an
ImageQuery. How about you try writing out your idea and seeing what happens.Student: Ok, I bet my way will work.
Student: See, it works perfectly! And no interface needed.
Prof: You’re right, for now. But, let’s see what happens when we get back to the idea of combining queries: check out what happens when I try to add this new record in. We said we wanted to check for relationships between queries, such as an
Or.Student: OK, but it has an error.
Prof: Right. What would you do to fix it? How can you change
Or so that you can construct both bigOrPng and pngOrJpg?Student: I guess I can’t. I have to pick either
HasExtension or LargerThan for the fields of Or, and neither works for both combinat– OHHH! Wait so the interface helps with this?Prof: Right! When we have
interface ImageQuery and records that implement
ImageQuery, we are allowed to use ImageQuery as the type of any value that implements ImageQuery. So the implements lines let us replace the field types of Or with ImageQuery. And then we can construct both versions.Student: That’s so cool! And I bet with that we can do
And and stuff too.Prof: Indeed. Give it a try with
And and Not included as well.Student: Sure, I think I understand the format now.
Student: You know, these interfaces are actually kinda nice. But wait a second, you never explained what
public means? I guess I just started copying your record examples without thinking about it.Prof: Good catch.
public is an access modifier. It says who is allowed to use a method. A public method can be called by code anywhere.Student: So why did I have to write it here? We have been calling methods all the time.
Prof: Methods in an interface must be
public, since the whole point of an interface is that anyone holding an ImageQuery can call matches on it. A record that implements the interface isn’t allowed to make that method less available than the interface promised. Try leaving it off:Student: I see, the compiler complains that it “cannot reduce the visibility of the inherited method”.
Prof: Exactly.
Student: Ok, these interfaces are cool and all. But, I wish I didn’t have to type out
new And(new LargerThan(600, 400), new Or(new HasExtension("png"), new HasExtension("jpg"))). That’s a lot of news to keep track of.Prof: You’re right. Perhaps you want something in the chaining style we talked about earlier? Something like
isBig.and(isPng)? We will go over that next.Student: Yeah, that’d be nice.
Practice
Fill in the blanks to write a new query, Xor, that matches an image when exactly one of its two queries matches (one is true and the other is false).
record ImageData(String keywords, String fileType, int width, int height) {}
interface ImageQuery {
boolean matches(ImageData img);
}
record HasExtension(String ext) implements ImageQuery {
public boolean matches(ImageData img) {
return img.fileType().equals(this.ext);
}
}
record LargerThan(int minWidth, int minHeight) implements ImageQuery {
public boolean matches(ImageData img) {
return img.width() >= this.minWidth && img.height() >= this.minHeight;
}
}
record Xor(ImageQuery left, ImageQuery right) {
public boolean matches(ImageData img) {
return ;
}
}
void main() {
System.out.println("All tests succeeded!");
}
interface ImageQuery {
boolean matches(ImageData img);
}
record HasExtension(String ext) implements ImageQuery {
public boolean matches(ImageData img) {
return img.fileType().equals(this.ext);
}
}
record LargerThan(int minWidth, int minHeight) implements ImageQuery {
public boolean matches(ImageData img) {
return img.width() >= this.minWidth && img.height() >= this.minHeight;
}
}
record Xor(ImageQuery left, ImageQuery right) {
public boolean matches(ImageData img) {
return ;
}
}
void main() {
System.out.println("All tests succeeded!");
}