Advanced Java Programming
The course is part of this learning path
This training course provides you with a deep dive into generics, type inference, Lambda expressions, and functional interface development.
What you'll learn:
- What generics are and when and why you might choose to implement them
- Type inference and the var keyword
- The basic concept of functional programming
- How to write basic Lambda expressions
- Functional interfaces and when to use them
- The key differences between anonymous classes and lambda expressions
- A basic understanding of the Java programming language
- A basic understanding of software development
- A basic understanding of the software development life cycle
- Software Engineers interested in advancing their Java skills
- Software Architects interested in using advanced features of Java to design and build both applications and frameworks
- Anyone interested in advanced Java application development and associated tooling
- Anyone interested in understanding the advanced areas and features of the Java SDK
Okay, welcome back. In this lecture we'll introduce you to the concept of generics and how you use them within Java. In particular we'll review the following topics, using generics, using generics as arguments in methods, and using wildcards. At the conclusion of this lecture, you should be able to perform each of the items listed above. Take a moment to rate yourself on each of these items on a scale of one through to five. At the conclusion of this lecture, these objectives will be reviewed. You should rate yourself again to see how much benefit you have received from this lecture. Generics were introduced in Java 5 to provide developers with more compile-time type information. It helps us to understand better how our code deals with types. Throughout this lecture we'll look at the use of generics when handling lists or collections of object references. Java provides a full API for working with collections of objections which will be covered in a later course. But for the moment we will look at example implementations of this API to explain how generics will provide the developer and the compiler with more type information. It is important to try to keep in mind the distinction between what type information you have at compile time which is limited even with generics, and what type information you have at runtime which is the full type information of the instances. By using generics, the compiler is capable of throwing exceptions which without generics would only appear at runtime, and this has been official when you're doing development. Writing a list. When you need to develop a class that is capable of maintaining a list of object types, the methods you define within the class would need to accept and return object references in order to make sure that the class can be used for different object types. Keep in mind that in real projects you would most likely not implement this class yourself, but use one of the collection classes instead. When creating an instance of list, any object type can be added to the list. In this example here, we're instantiating dog and a book, and we're adding both to the list. Now how often would you create a list of unrelated object types? More likely you would make a list of just dog types or a list of just book types. Therefore in most cases it just doesn't make sense to add any type of object to a list. It's far more likely that you need to create a list just containing dog references and or a separate list containing just book references. Additionally the get method on a list has been defined to return object references. Since the list may contain any object type, the reference might point to any type of object. Only at runtime it is knowing what the actual type is of the object that is referenced. So every time you obtain a reference from the list, you will have to check its type, otherwise a ClassCastException might occur. Even though in the examples above the content of the list is obvious, this is not always the case, and therefore you would have to cast the result and have detailed knowledge of what object type is at what position within the list. In larger applications the list might be populated in a completely different part of the application, and as a result the developer who has to work with the content does not know what type of objects reside within the list besides them being object types. To overcome these problems, a generic class can be used. In this case a real generic class defines a type using a placeholder at the class level. This placeholder can then be used throughout the class to define the type of an instance variable or parameter or return type. Angle brackets are used to specify the type parameter. When an instance of the generic class is created, that actual type is defined on the reference and on the assignment. To be exact, the type must be defined on the type of the reference, and only has to be defined on the type argument of the constructor when the compiler cannot determine or infer the type that has to be created. In most cases the compiler can figure out what type of object instance needs to be created by looking at the type that was defined by the reference to which the object reference will be assigned. As a result of defining the type during the construction of the object, all placeholders that we defined in the class will now be replaced by the type you just specified. So in the list of book example as used here on the slide, the add method now only accepts instances of book, and additionally the get method only returns book references without any need for a cast. Type parameter naming conventions. Earlier we saw that a generic type is defined using a placeholder. By convention this placeholder is a single uppercase letter. To be more exact, the coding convention defines specific letters depending on the kind of parameter you want to define. E represents an element in a collection. K, a key. N, number. T for type. And V for value. S, U, W, et cetera are used for additional parameter types. So now we can define our classes in a very generic way, allowing developers to create object instances that are specialized for any particular type. But so far we have defined just that placeholder which can be substituted by any Java object. Bounded types allow you to further specify the type of the placeholder by declaring the upper bound of the type. Basically you are specifying that the types for which this can be used can only be of the type specified, this type or any other subtype. In the example shown here when you try to instantiate a list using a type which is not a number type, the compiler will throw in bound mismatched error. Besides the fact that the class now can only be instantiated using a number type, when you instantiate the class without declaring the generic type, the methods within the class that used the generic placeholder will use the upper bound of the generic type, in this example, number. Raw types. When a generic type is used without any type parameter, it is known or referred to as a raw type. When this happens, the compiler will show you a warning. These warnings can be suppressed using the SuppressWarnings annotation. Generic methods. Until now we looked at defining type parameters at the class level. Types that are declared at the class level have to be specified when the class is instantiated and can be used throughout the class. It is also possible to define generic methods. In this case you define a generic type that is local to the method. The class itself does not have to be a generic type. To do so, you define the type placeholder before the return type of the method. The type can now be used as a method parameter, the return type, and as method variables within the method. One example of using generic methods is shown here. A generic type T has been specified that it's used as the type of the parameter and the return type. So when the method is invoked using an instance of dog, the method must return an instance of dog. When the method is invoked using an instance of book, the method will return an instance of book. Generics and subtypes. As you know when defining a method that accepts a parameter of a particular type, this method can be invoked using object instances of this exact type, but also using object instances of any subtype. In our example buy the book and dog are a subtype of item. So they can both be used as a parameter of the buy method. Naturally within the buy method, you are referencing the object using an item reference and can only use the methods of item. With the introduction of generic classes, we need to ask the question, is the list of dogs a subtype of a list of items? In other words can I use an instance of the dog list when invoking the print method? The answer is no. You will not be able to invoke this method using a list of dogs. So now we ask the question, why is a list of item not a supertype of a list of dog? The explanation follows. You created an instance of a list of dogs, and as a result you could only add dogs to this list. Trying to add a book to it would be caught by the compiler. Now let's assume for a moment that a list of item would be a supertype of list of dog, you would then be able to reference your list of dogs instance using a list of item reference. As a result, the add method would now accept any item. As a result, you would now be able to add books to your list of dogs, and this doesn't make sense, hence why it is not allowed. Wildcards. So when you need to define methods that accept generic classes, you will either have to accept the fact that the method only works on an object of exactly the type provided, or use a wildcard. To define a method that works with every list, you have to specify a wildcard, a question mark. What you are saying by using the wildcard is that you really don't care what type of object that you use as a parameter. The drawback of this approach is that within the method, the generic type cannot be determined. So when invoking the get method of the item class, the return type will be object. To define a method that works with every list, you have to specify a wildcard which is the question mark. Also for methods of the class that use the generic type as a parameter, their actual type cannot be determined. So the compiler will expect a parameter of type question mark which is of course a type that does not exist. As a result you will not be able to invoke these methods. Bounded wildcards. For parameter types, the compiler can still not determine the exact type. However, in this case, it will not accept any item type that would potentially allow us again to add books to a dog list. For parameters of the generic type, it will only accept generic instances of type null. Since this type also does not exist, you will still not be able to invoke this method and accidentally pollute the state of your list. Okay before we complete this lecture, pause this video and consider the following questions to test yourself on the content that we have just reviewed. Write down your answers for each question and then resume the video to compare answers. Okay, the answers to the above questions are. One, generics define two new forms of types, parameterized types and type variables. Two, wildcards represent unknown parameter types. They represent any parameter type. They are depicted using a question mark. Three, generics are useful for more than just collections, but that is where they are commonly used. Four, generics are defined as a parameter or return type to a method in the following ways. A, angle brackets, T. B, angle brackets, T, extends, class, interface, or type variable. C, angle brackets, question mark. And D, angle brackets, question mark, extends, class, interface, or type variable. Five, a raw type is a concrete generic type instantiated without a parameterized type.
Jeremy is a Content Lead Architect and DevOps SME here at Cloud Academy where he specializes in developing DevOps technical training documentation.
He has a strong background in software engineering, and has been coding with various languages, frameworks, and systems for the past 25+ years. In recent times, Jeremy has been focused on DevOps, Cloud (AWS, GCP, Azure), Security, Kubernetes, and Machine Learning.
Jeremy holds professional certifications for AWS, GCP, and Kubernetes.