Transcript for:
Understanding Java Generics Basics

In this video, we're going to talk all about generics in Java. Generics can be super confusing the first time you see them, with all the Ts and question marks and angle brackets and Ks and Vs. But I promise by the end of this video, you'll know exactly what generics are, why they exist and why they're useful, and how you can use them in your own programs. My name is John, I'm a lead Java software engineer, and I love sharing what I've learned with you in a clear and understandable way. I also have a full Java course available in a link down in the description if you're interested. First, to have a good understanding of what generics do and why they exist, It helps to know the kinds of problems that Java developers were running into before generics existed. Let's say I wanted to create a class where all it would do is hold an integer value that I give it and then print out that integer whenever I wanted to. So we might call something like that integer printer. You can go ahead and click finish and create that. So this class would be pretty simple. We would have an integer field for the thing that we want to print. So we can call it thing to print and we can have a constructor for this class. that takes in this thing to print. So that would be public integer printer. Then it takes in an integer for the thing to print. And that would just set this dot thing to print equal to the thing to print that was passed in. And we can have one method to actually print this out whenever we want to. So that would be public void print. And all that does is just print out the thing to print to the console. So system.out.println thing to print. So if we wanted to use this integer printer class, we would just create an integer printer printer equals new integer printer and pass in the integer that we want to print here in the constructor. So let's say we want to print out the number 23. Now of course we can call printer.print and when we run our program of course it prints out 23. But what if we wanted to do exactly the same thing for a double instead of an integer? Well we couldn't use this class right because it only holds an integer. doesn't hold a double. So what can we do? Well, basically what we have to do is make a copy of this entire class, and we can call it double printer, and then inside that double printer we would basically have all of the exact same code except instead of integer we would have double. So this new class will work great for holding and printing doubles, but what if we now want a class that'll do exactly the same thing but work for strings? Well, of course you have to do the exact same thing again. You have to copy that class, make a new class called string printer, and change all these doubles to strings if you want your class to be able to print strings. So now we have three completely separate versions of essentially the same kind of class. And you can start to see the problem. We would need a whole other class if we wanted to do this with floats or dogs or cats or cars or trucks or whatever type of object that we wanted to print. So we'd end up with a ton of code duplication. And this is with a really simple class. If you had a more complicated class, the code duplication would be even worse. Well, that is where generics come in. Generics offer you the ability to have one class like this that is flexible for many, many different types. So let's go back to our original integer printer and make it generic. So first, instead of calling it integer printer, let's just call it printer because it should be able to print anything. Now, when you want to use generics in a class like this in Java, right here after the class name in the declaration, but before for the curly braces, you have to define what is called a type parameter. And you'll put your type parameter in angle brackets like this. By convention in Java, typically you'll see this type parameter just called T. But technically you can call this type parameter whatever you want. But I'm going to just be using T so you can kind of get used to seeing it because that's the convention that you'll see elsewhere. You can think of it as standing for type. In this class, what this T represents is the type of thing that this printer is going to be able to hold and print. So instead of just having an integer field here, it's going to be of type T. Right now as we're coding this class, we don't know what types of things this printer is going to be asked to print. But this gives the ability to create printers for any types that you want. So of course we also have to change the name of our constructor to just printer to match the class name. And that constructor also, instead of taking in an integer, is going to take in something of type T. t. So now this class is a generic printer for whatever type we want. But now how do we actually use this class if we want to, for example, create an integer printer like we had before? Well back here in our main method instead of an integer printer now of course we're going to just create a printer. But now you can see that we have a warning here that printer is a raw type and references to generic type printer should be parameterized. It wants to know the type of thing that we want to be able to print. And in this example we want to be able to print integers. So how we specify that is in angle brackets here after printer we type in integer. That tells Java I want a printer for integers. And we have a similar warning here. To get rid of that, you just need to put in angle brackets after the class name printer. In older versions of Java, you used to have to put the type in there again, but you don't have to do that anymore. This integer here that we're passing in in the angle brackets is what's going to be used as this T type in this printer object that we are creating. Now we can run our program as we did before, and it still prints out 23. But now what's cool is we can go in and get rid of those two extra classes that we made, the double printer and the string printer. We can just delete those because now we can use our generic printer class to be able to create printers for doubles and strings also. So now all we have to do if we want to create a printer for doubles, we just say printer and pass in double for t, the type that we want this printer to be able to print. We can call it double printer equals new printer. and give it a value like 33.5 and we can call double printer dot print and of course it prints out 33.5 so now we've created one printer class that can print any type that we give it doubles ints longs floats pigs monkeys cars trucks whatever one thing to note though is that generics don't work with primitive types like lowercase int and lowercase long but all you have to do is just use the wrapper classes like integer and everything should just work the same way one place that you've probably already used generics all the time is with Java's collections framework. For example, if you wanted to create an array list, you've probably done this where you've specified in angle brackets the type of thing that you want to have in your list. So if you want to create an array list of cats, you say array list cat. We'll call it cats equals new array list. This gives you an array list that you can only add cats to. So you can say cats dot add new cat. And that works fine, but if you try to do cats.add and pass in a new dog instead, you get an error because we specified that cat was the type of thing that this list was going to hold. And if we try to give it a dog, we get an error. In the same way, up here where we created a printer for doubles, if we try to pass in a string, like hey there, we get a similar error because we told Java that this was going to be a printer for doubles. And here we're trying to give it a string. But you might be thinking, well, hey, John, if we want to be able to create a list of anything we want, why do we have to deal with all these generics? Why can't we just create like an array of just objects and then we can put whatever we want in it? Well, it is true that you can do that and your code will work, but it won't be type safe at all. And let me show you what I mean. Let's say that our array list, instead of holding cats, just holds objects. This is the kind of thing you'd have to do if generics didn't exist. You'd just have to create. an array list that can hold whatever. So in this imaginary world without generics we still want to create a list of cats. So we've added our new cat to our list. But let's say sometime later on in the code we want to be able to get stuff off our list and use it. So if we want to get this first cat off the list and put it in a variable we would want to create a cat, call it my cat, equals cats dot get the first element of the list at index zero. But here we get an error that we have a tight mismatch. You can't convert from object to cat. Java doesn't know that this is supposed to be a list of cat. It just knows it's a list of objects. And so we have to tell Java, yes, I know this is a cat, so you can go ahead and cast it to a cat so I can store it in this variable. But what if instead of adding a cat to the list somebody adds a dog? Well the code all looks the same. There's no compilation errors or anything. But of course if you try to run it you're going to get a class cast exception right here on this line. because you're trying to cast this element in the list as a cat, but it's actually a dog. So just having an ArrayList that can hold whatever type of object causes all types of these type safety issues. And Generics solves that problem for us. So now we can say, yes, I want to create a list of cats. So then in the code, if somebody tries to add a dog to that list, they get an error and it won't even compile. And also, since Java knows that this is a list that can only contain cats, Whenever you get a thing off of that list, it is 100% guaranteed to be a cat and you don't have to do any special casting. That's how generics help us. They offer the ability to create classes that can accommodate tons of different types, but also the structure and the type safety of knowing exactly which type you're using that class with at the moment. But there are even more cool advanced things that you can do with generics. Let's go back to our generic printer class. So right now we can create a printer of whatever class that we want to, right? But what if instead of being a printer for any type, we want this to be a more specialized printer for animals? For example, I have this cat class here that extends the animal class. And I also have... a dog class here that extends the animal class. So if we want this to just be a printer for any type of animal, instead of just saying t, we can say t extends animal. Now back here in our main method, we now get an error when we try to create a printer for integers or doubles, because now this printer only works for animals. So we can create a cat printer, and in the constructor we can pass in a new cat. Or we can create a printer for a dog and pass in a new cat. dog. But what's cool about that is now in our printer class, since we know that whatever is passed in as this type extends animal, any method that is defined in the animal class is available to us in our thing to print variable because we know that this thing to print, this t, is going to be some type of an animal. So now because we know this thing to print is an animal, down here in our print method, we can call thing to print dot eat. because this eat method is available in the animal class. So if we didn't have this extends animal, if we just took in any type at all, we can't call the eat method because this T type isn't guaranteed to be an animal at all. So there's no way to know whether it will have an eat method available on it or not. When you do this type of thing, it's called a bounded generic because you're giving a certain bound a limit on the type that's able to be passed in. You can also use bounds with interfaces. Let's say you wanted to guarantee that this type implemented the serializable interface. You just put in extends serializable. You might think that because this is an interface you should put implements here instead of extends, but no it's just not the proper syntax. It still has to be extends even though it's a little bit weird. Another cool thing you can do is have multiple bounds. Let's say you wanted to make sure that it was a subclass of animal and also implemented the serializable interface. To do that you can just say extends animal and serializable here with an ampersand. And you can list as many things as you want here all separated by ampersands. There's just a couple restrictions. You can only have one class, which makes sense because Java doesn't support multiple inheritance. And you always have to list the class name first with any interfaces after that. If you try to switch it around and put the interface first you'll get an error. In addition to having generic classes you can also have generic methods. Let's say we wanted to make a method that can take in any type of object and print it out with exclamation points. Private static void. Let's call it shout since it'll print things out with exclamation points. It'll take in some kind of type that we're going to call t. We'll call it thingToShout and inside that method all we're going to do is print out that thingToShout with a bunch of exclamation points after it. So right now we're getting an error here because Java is saying hey t isn't a type. What do you want me to do with this? To tell Java that this t is supposed to be a generic type right here. here in the method signature right before the return type, you put that generic type in angle brackets. Again, you can call this whatever as long as it matches here and in the parameters, but by convention you're still just going to see t most of the time. So now here in our main method we can call our shout method with whatever we want. You can send in a string like John, you can send in an int 57, or you can even send in a cat. So this can take in any type of parameter that we want to send and when we run it it'll print them out with exclamation points. Java also supports the ability to have multiple different generic types here. So in addition to taking in this t thing to shout we can also take in say v other thing to shout. All we have to do is add this v to our angle brackets here and separate them with a comma. And now this method will take in two of any sort of type. So we can pass in john and 74 and also go ahead and print out. Other thing to shout. And that works and is now flexible with any two types at all. If you want, you can also have multiple generic types like this in your generic classes. So over here in our printer class, if we wanted to have another field here, let's call it the other thing. And all we have to do is specify that up here in our angle brackets as well, separated by a comma. If you happen to want to return one of these generic values that are being passed into your generic methods. All you have to do is specify that return type as the return type here. So instead of void, if you want to return something of type T, you just put T here as the return type. And then in here of course you just have to return something of type T, which can just be this thing to shout. The other advanced generics topic I want to talk about is wildcards. Let's talk about a situation where you might want to use a wildcard and then we'll show how you can do it. Let's say we wanted to be able to create a method that can take in a list that holds any type of objects and we want to be able to print out that list. So we might call that print list. So how do we specify in the parameters here that we want to be able to take in a list? that contains any type of thing. You might think that we can just say, okay, I want to take in a list of objects. We can call it myList and all we'll do is just print out that list to the console. So now up here we can create a list of integers. Call it int list equals new array list. We can take our int list and add the number three. And then we can try and call our print list method with our int list as the parameter. But if we try and do that, we get an error here that says we can't pass in this list of integers as this parameter that's supposed to be a list of objects. But it might feel like you should be able to do that, right? Of course an integer is a subclass of objects, so shouldn't we be able to pass in just a list of integers? Well even though integer is a subclass of object, a list of integers is not a subclass of a list of objects. So this doesn't work. This is where a wild card will work to solve your problem. So instead of saying that this is a list of objects, we say that this is going to be a list of unknown by passing in a question mark. This question mark is the wildcard. You use a wildcard, a question mark, as the type parameter when you're using a generic when you don't know what exactly that generic type is going to be. So it's saying, hey, Java, I've got this method here and I want it to be able to take in a list of anything, but I don't know what it's going to be a list of. So now you can call this method with. a list of whatever you want. In this case we're doing it with a list of integers, but you can also do it with a list of anything else. Let's say we had a list of cats. Add a new cat to that list, change this to catList here, and pass the catList into our printList method, and it now works with lists of both types with no errors. You can also have bounded wildcards similarly to how we did in our generic class. So instead of being a list of anything at all, we can say this has to be a list of something that extends the animal class. But now you can see up here that we're getting an error when we're trying to call this method with a list of integers, and that's because integer doesn't extend animal. We don't get that same error here when we're calling it with a list of cats because cat does extend animal. If you enjoyed this video or learned something, please let me know by leaving a like and be sure to subscribe so you don't miss each new Java tutorial. And of course, don't stop now. Keep learning by watching one of the other videos below. Thank you so much for watching. I really appreciate you being here with me. I'll see you next time.