UNPKG

epubjs

Version:

Render ePub documents in the browser, across many devices

165 lines (158 loc) 23.2 kB
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"><head><title>Writing Generic Classes</title><link rel="stylesheet" href="core.css" type="text/css"/><meta name="generator" content="DocBook XSL Stylesheets V1.74.0"/></head><body><div class="sect1" title="Writing Generic Classes"><div class="titlepage"><div><div><h1 class="title"><a id="learnjava3-CHP-8-SECT-6"/>Writing Generic Classes</h1></div></div></div><p>Now that we have (at least some of) the “end user” view of generics, let’s try writing a few classes ourselves. In this section, we’ll talk about how type variables are used in the definition of generic classes, where they may appear, and some of their limitations. We’ll also talk about subclassing generic types.</p><div class="sect2" title="The Type Variable"><div class="titlepage"><div><div><h2 class="title"><a id="learnjava3-CHP-8-SECT-6.1"/>The Type Variable</h2></div></div></div><p><a id="I_indexterm8_id709162" class="indexterm"/> <a id="I_indexterm8_id709168" class="indexterm"/> <a id="idx10395" class="indexterm"/> <a id="idx10411" class="indexterm"/>We’ve already seen the basics of how type variables are used in the declaration of a generic class. One or more type variables are declared in the angle bracket (&lt;&gt;) type declaration and used throughout the body and instance methods of the class. For example:</p><a id="I_8_tt421"/><pre class="programlisting"> <code class="kd">class</code> <code class="nc">Mouse</code> <code class="o">{</code> <code class="o">}</code> <code class="kd">class</code> <code class="nc">Bear</code> <code class="o">{</code> <code class="o">}</code> <code class="kd">class</code> <code class="nc">Trap</code><code class="o">&lt;</code> <code class="n">T</code> <code class="o">&gt;</code> <code class="o">{</code> <code class="n">T</code> <code class="n">trapped</code><code class="o">;</code> <code class="kd">public</code> <code class="kt">void</code> <code class="nf">snare</code><code class="o">(</code> <code class="n">T</code> <code class="n">trapped</code> <code class="o">)</code> <code class="o">{</code> <code class="k">this</code><code class="o">.</code><code class="na">trapped</code> <code class="o">=</code> <code class="n">trapped</code><code class="o">;</code> <code class="o">}</code> <code class="kd">public</code> <code class="n">T</code> <code class="nf">release</code><code class="o">()</code> <code class="o">{</code> <code class="k">return</code> <code class="n">trapped</code><code class="o">;</code> <code class="o">}</code> <code class="o">}</code> <code class="c1">// usage</code> <code class="n">Trap</code><code class="o">&lt;</code><code class="n">Mouse</code><code class="o">&gt;</code> <code class="n">mouseTrap</code> <code class="o">=</code> <code class="k">new</code> <code class="n">Trap</code><code class="o">&lt;</code><code class="n">Mouse</code><code class="o">&gt;();</code> <code class="n">mouseTrap</code><code class="o">.</code><code class="na">snare</code><code class="o">(</code> <code class="k">new</code> <code class="n">Mouse</code><code class="o">()</code> <code class="o">);</code> <code class="n">Mouse</code> <code class="n">mouse</code> <code class="o">=</code> <code class="n">mouseTrap</code><code class="o">.</code><code class="na">release</code><code class="o">();</code></pre><p>Here, we created a generic <code class="literal">Trap</code> class that can hold any type of object. We used the type variable <a id="I_indexterm8_id709234" class="indexterm"/><code class="literal">T</code> to declare an instance variable of the parameter type as well as in the argument type and return type of the two methods.</p><p>The scope of the type variable is the instance portion of the class, including methods and any instance initializer blocks. The static portion of the class is not affected by the generic parameterization, and type variables are not visible in static methods or static initializers. As you might guess, just as all instantiations of the generic type have only one actual class (the raw type), they have only one, shared static context as well. You cannot even invoke a static method through a parameterized type. You must use the raw type or an instance of the object.</p><p>The type variable can also be used in the type instantiation of other generic types used by the class. For example, if we wanted our <code class="literal">Trap</code> to hold more than one animal, we could create a <code class="literal">List</code> for them within our class by referencing the parameter type like so:</p><a id="I_8_tt422"/><pre class="programlisting"> <code class="n">List</code><code class="o">&lt;</code><code class="n">T</code><code class="o">&gt;</code> <code class="n">trappedList</code> <code class="o">=</code> <code class="k">new</code> <code class="n">ArrayList</code><code class="o">&lt;</code><code class="n">T</code><code class="o">&gt;();</code></pre><p>Just to cover all the bases, we should mention that instantiations of generic types on the type variable act just like any other type and can serve in all the places that other instantiations of a type can. For example, a method in our class can take a <code class="literal">List&lt;T&gt;</code> as an argument:</p><a id="I_8_tt423"/><pre class="programlisting"> <code class="kd">public</code> <code class="kt">void</code> <code class="nf">trapAll</code><code class="o">(</code> <code class="n">List</code><code class="o">&lt;</code><code class="n">T</code><code class="o">&gt;</code> <code class="n">list</code> <code class="o">)</code> <code class="o">{</code> <code class="o">...</code> <code class="o">}</code></pre><p>The effective type of the <code class="literal">trapAll()</code> method in a <code class="literal">Trap&lt;Mouse&gt;</code> is then simply:</p><a id="I_8_tt424"/><pre class="programlisting"> <code class="n">trapAll</code><code class="o">(</code> <code class="n">List</code><code class="o">&lt;</code><code class="n">Mouse</code><code class="o">&gt;</code> <code class="n">list</code> <code class="o">)</code> <code class="o">{</code> <code class="o">...</code> <code class="o">}</code></pre><p>We should note that this is <span class="emphasis"><em>not</em></span> what we mean by the term <span class="emphasis"><em>generic method</em></span>. This is just a regular Java method that happens to take a generic type as an argument. We’ll talk about real generic methods, which can <span class="emphasis"><em>infer</em></span> their types from arguments, and assignment contexts later in this chapter. A type variable can also be used to parameterize a generic parent class, as we’ll see in the next section.<a id="I_indexterm8_id709338" class="indexterm"/><a id="I_indexterm8_id709345" class="indexterm"/></p></div><div class="sect2" title="Subclassing Generics"><div class="titlepage"><div><div><h2 class="title"><a id="learnjava3-CHP-8-SECT-6.2"/>Subclassing Generics</h2></div></div></div><p><a id="idx10394" class="indexterm"/> <a id="idx10410" class="indexterm"/> <a id="idx10436" class="indexterm"/>Generic types can be subclassed just like any other class by either generic or nongeneric child classes. A nongeneric subclass must extend a particular instantiation of the parent type, filling in the required parameters to make it concrete:</p><a id="I_8_tt425"/><pre class="programlisting"> <code class="kd">class</code> <code class="nc">DateList</code> <code class="kd">extends</code> <code class="n">ArrayList</code><code class="o">&lt;</code><code class="n">Date</code><code class="o">&gt;</code> <code class="o">{</code> <code class="o">}</code> <code class="n">DateList</code> <code class="n">dateList</code> <code class="o">=</code> <code class="k">new</code> <code class="n">DateList</code><code class="o">();</code> <code class="n">dateList</code><code class="o">.</code><code class="na">add</code><code class="o">(</code> <code class="k">new</code> <code class="n">Date</code><code class="o">()</code> <code class="o">);</code> <code class="n">List</code><code class="o">&lt;</code><code class="n">Date</code><code class="o">&gt;</code> <code class="n">ld</code> <code class="o">=</code> <code class="n">dateList</code><code class="o">;</code></pre><p>Here, we have created a nongeneric subclass, <code class="literal">DateList</code>, of the concrete generic instantiation <code class="literal">ArrayList&lt;Date&gt;</code>. The <code class="literal">DateList</code> is a type of <code class="literal">ArrayList&lt;Date&gt;</code> and inherits the particular instantiation of all of the methods, just as it would from any other parent. We can even assign it back to the parent type if we wish, as shown in this example.</p><p>A generic subtype of a generic class may extend either a concrete instantiation of the class, as in the previous example, or it may share a type variable that it “passes up” to the parent upon instantiation:</p><a id="I_8_tt426"/><pre class="programlisting"> <code class="kd">class</code> <code class="nc">AdjustableTrap</code><code class="o">&lt;</code> <code class="n">T</code> <code class="o">&gt;</code> <code class="kd">extends</code> <code class="n">Trap</code><code class="o">&lt;</code> <code class="n">T</code> <code class="o">&gt;</code> <code class="o">{</code> <code class="kd">public</code> <code class="kt">void</code> <code class="nf">setSize</code><code class="o">(</code> <code class="kt">int</code> <code class="n">i</code> <code class="o">)</code> <code class="o">{</code> <code class="o">...</code> <code class="o">}</code> <code class="o">}</code></pre><p>Here, the type variable <code class="literal">T</code> used to instantiate the <code class="literal">AdjustableTrap</code> class is passed along to instantiate the base class, <code class="literal">Trap</code>. When the user instantiates the <code class="literal">AdjustableTrap</code> on a particular parameter type, the parent class is instantiated on that type as well.<a id="I_indexterm8_id709482" class="indexterm"/><a id="I_indexterm8_id709489" class="indexterm"/><a id="I_indexterm8_id709496" class="indexterm"/></p></div><div class="sect2" title="Exceptions and Generics"><div class="titlepage"><div><div><h2 class="title"><a id="learnjava3-CHP-8-SECT-6.3"/>Exceptions and Generics</h2></div></div></div><p><a id="idx10392" class="indexterm"/> <a id="idx10400" class="indexterm"/> <a id="idx10408" class="indexterm"/>Types appear in the body of classes in another place—the <code class="literal">throws</code> clauses of methods. We can use type variables to define the type of exceptions thrown by methods, but to do so we need to introduce the concept of bounds. We cover bounds more in the next section. In this case, the usage is very simple. We just need to ensure that the type variable we want to use as our exception type is actually a type of <code class="literal">Throwable</code>. We can do that by adding an <code class="literal">extends</code> clause to the declaration of our type variable, like this:</p><a id="I_8_tt427"/><pre class="programlisting"> <code class="o">&lt;</code> <code class="n">T</code> <code class="kd">extends</code> <code class="n">Throwable</code> <code class="o">&gt;</code></pre><p>Here is an example class, parameterized on a type that must be a kind of <code class="literal">Throwable</code>. Its <code class="literal">test()</code> method accepts an instance of that kind of object and throws it as a checked exception:</p><a id="I_8_tt428"/><pre class="programlisting"> <code class="n">ExceptionTester</code><code class="o">&lt;</code> <code class="n">T</code> <code class="kd">extends</code> <code class="n">Throwable</code> <code class="o">&gt;</code> <code class="o">{</code> <code class="kd">public</code> <code class="kt">void</code> <code class="nf">test</code><code class="o">(</code> <code class="n">T</code> <code class="n">exception</code> <code class="o">)</code> <code class="kd">throws</code> <code class="n">T</code> <code class="o">{</code><code class="c1">// throw type is generic param</code> <code class="k">throw</code> <code class="n">exception</code><code class="o">;</code> <code class="o">}</code> <code class="o">}</code> <code class="k">try</code> <code class="o">{</code> <code class="k">new</code> <code class="n">ExceptionTester</code><code class="o">&lt;</code><code class="n">ClassNotFoundException</code><code class="o">&gt;().</code><code class="na">test</code><code class="o">(</code> <code class="k">new</code> <code class="nf">ClassNotFoundException</code><code class="o">()</code> <code class="o">);</code> <code class="o">}</code> <code class="k">catch</code> <code class="o">(</code> <code class="n">ClassNotFoundException</code> <code class="n">e</code> <code class="o">)</code> <code class="o">{</code> <code class="o">...</code> <code class="o">}</code></pre><p>The important part of this example is that the <code class="literal">throws</code> clause of our test method is defined to throw <code class="literal">T</code>, the generic parameter type of the class. This means that we can parameterize the type of exceptions thrown by a class.</p><p>The addition of the bound imposes the restriction that the parameter type used to instantiate the class <code class="literal">T</code> must be a type of <code class="literal">Throwable</code>. And we referenced the type <code class="literal">T</code> in the <code class="literal">throws</code> clause. So, an <code class="literal">ExceptionTester&lt;ClassNotFoundException&gt;</code> can throw a <code class="literal">ClassNotFoundException</code> from its <code class="literal">test()</code> method. Note that this is a checked exception and that fact has not been lost on the compiler. The compiler enforces the checked exception type that it just applied.</p><div class="sect3" title="No generic throwables"><div class="titlepage"><div><div><h3 class="title"><a id="learnjava3-CHP-8-SECT-6.3.1"/>No generic throwables</h3></div></div></div><p>We saw that a type variable can be used to specify the type of <a id="I_indexterm8_id709677" class="indexterm"/><code class="literal">Throwable</code> in the <code class="literal">throws</code> clause of a method. Perhaps ironically, however, we cannot use generics to create new types of exceptions. No generic subtypes of <code class="literal">Throwable</code> are allowed. If you think about this for a moment, you’ll see that in order to be useful, generic <code class="literal">Throwable</code>s would require <code class="literal">try/catch</code> blocks that can differentiate instantiations of <code class="literal">Throwable</code>. And because (once again) there is no runtime representation of generics, this isn’t possible with erasure.<a id="I_indexterm8_id709720" class="indexterm"/><a id="I_indexterm8_id709727" class="indexterm"/><a id="I_indexterm8_id709734" class="indexterm"/></p></div></div><div class="sect2" title="Parameter Type Limitations"><div class="titlepage"><div><div><h2 class="title"><a id="learnjava3-CHP-8-SECT-6.4"/>Parameter Type Limitations</h2></div></div></div><p><a id="idx10393" class="indexterm"/> <a id="idx10409" class="indexterm"/> <a id="idx10431" class="indexterm"/>We have seen the parameter types (type variables) of a generic class used to declare instance variables, method arguments, and return types as well as “passed along” to parameterize a generic superclass. One thing that we haven’t talked about is the question of how or whether we can use the type variable of a generic class to construct instances of the parameter type or work with objects of the type in other concrete ways. We deliberately avoided this issue in our previous “exception tester” example by simply passing our exception object in as an argument. Could we have done away with this argument? The answer, unfortunately, is that due to the limitations of erasure, there really is no parameter type information to work with at runtime. In this section, we’ll look at this problem and explore a workaround.</p><p>Because the type variable <code class="literal">T</code> has faithfully served as our parameter type everywhere else, you might imagine that we could use it to construct an instance of <code class="literal">T</code> using the <code class="literal">new</code> keyword. But we can’t:</p><a id="I_8_tt429"/><pre class="programlisting"> <code class="n">T</code> <code class="n">element</code> <code class="o">=</code> <code class="k">new</code> <code class="n">T</code><code class="o">();</code> <code class="c1">// Error! Invalid syntax.</code></pre><p>Remember that all type information is erased in the compiled class. The raw type does not have any way of knowing the type of object you want to construct at runtime. Nor is there any way to get at the <code class="literal">Class</code> of the parameter type through the type variable, for the same reason. So reflection won’t help us here either. This means that, in general, generics are limited to working with parameter types in relatively hands-off ways (by reference only). This is one reason that generics are more useful for containers than in some other applications. This problem comes up often and there is a solution, although it’s not quite as elegant as we’d like.</p><div class="sect3" title="Using Class&lt;T&gt;"><div class="titlepage"><div><div><h3 class="title"><a id="learnjava3-CHP-8-SECT-6.4.1"/>Using Class&lt;T&gt;</h3></div></div></div><p><a id="idx10391" class="indexterm"/>The only real way to get the type information that we need at runtime is to have the user explicitly pass in a <code class="literal">Class</code> reference, generally as one of the arguments to a method. Then we can explicitly refer to the class using reflection and create instances or do whatever else is necessary. This may sound like a really bad solution, without much type safety and placing a big burden on the developer to do the right thing. Fortunately, we can use a trick of generics to enforce this contract with the user and make it safe. Again, the basic idea is to have one of our methods accept the <code class="literal">Class</code> of the parameter type so that we can use it at runtime. Following our “exception tester” example:</p><a id="I_8_tt430"/><pre class="programlisting"> <code class="kd">public</code> <code class="kt">void</code> <code class="nf">test</code><code class="o">(</code> <code class="n">Class</code> <code class="n">type</code> <code class="o">)</code> <code class="kd">throws</code> <code class="n">T</code> <code class="o">{</code> <code class="o">...</code> <code class="o">}</code></pre><p>This isn’t much better than it was before. Specifically, it doesn’t guarantee that the <code class="literal">Class</code> type passed to the method will match the parameterized type of the class (used in the <code class="literal">throws</code> clause here).</p><p>Fortunately, the <code class="literal">Class</code> class is, itself, now a generic type. Specifically, all instances of the <code class="literal">Class</code> class created by the Java VM are instantiated with their own type as a parameter. The class of the <code class="literal">String</code> type, for example, is now <code class="literal">Class&lt;String&gt;</code>, not just some arbitrary instance of the raw <code class="literal">Class</code> type that happens to know about strings.</p><p>This has two ramifications. First, we can specify a particular instantiation of <code class="literal">Class</code> using the parameter type in our class. And second, since the <code class="literal">Class</code> class is now generic, all of the reflective and instance creation methods can be typed properly and no longer require casts, so we can write our <code class="literal">test()</code> method like this:</p><a id="I_8_tt431"/><pre class="programlisting"> <code class="kd">public</code> <code class="kt">void</code> <code class="nf">test</code><code class="o">(</code> <code class="n">Class</code><code class="o">&lt;</code><code class="n">T</code><code class="o">&gt;</code> <code class="n">type</code> <code class="o">)</code> <code class="kd">throws</code> <code class="n">T</code> <code class="o">{</code> <code class="k">throw</code> <code class="n">type</code><code class="o">.</code><code class="na">newInstance</code><code class="o">();</code> <code class="o">}</code></pre><p>The only <code class="literal">Class</code> instance that can be passed to our <code class="literal">test()</code> method now is <code class="literal">Class&lt;T&gt;</code>, the <code class="literal">Class</code> for the parameter type <code class="literal">T</code>, on which we instantiated <code class="literal">ExceptionTester</code>. So, although the user still has the burden of passing in this seemingly extraneous <code class="literal">Class</code> argument, at least the compiler will ensure that we do it and do it correctly:</p><a id="I_8_tt432"/><pre class="programlisting"> <code class="n">ExceptionTester</code><code class="o">&lt;</code><code class="n">ArithmeticException</code><code class="o">&gt;</code> <code class="n">et</code> <code class="o">=</code> <code class="k">new</code> <code class="n">ExceptionTester</code><code class="o">&lt;</code><code class="n">ArithmeticException</code><code class="o">&gt;();</code> <code class="n">et</code><code class="o">.</code><code class="na">test</code><code class="o">(</code> <code class="n">ArithmeticException</code><code class="o">.</code><code class="na">class</code> <code class="o">);</code> <code class="c1">// no other .class will work</code></pre><p>In this code snippet, attempting to pass any other <code class="literal">Class</code> argument to the <code class="literal">test()</code> method generates a compile-time error.<a id="I_indexterm8_id710033" class="indexterm"/><a id="I_indexterm8_id710040" class="indexterm"/><a id="I_indexterm8_id710047" class="indexterm"/><a id="I_indexterm8_id710054" class="indexterm"/></p></div></div></div></body></html>