Unchecked Assignment Java Util Arraylist Size

Cay S. Horstmann and Gary Cornell explain inheritance, which allows you to create new classes that are built on existing classes. When you inherit from an existing class, you reuse (or inherit) its methods and fields and you add new methods and fields to adapt your new class to new situations. This technique is essential in Java programming.

This chapter is from the book 
  • CLASSES, SUPERCLASSES, AND SUBCLASSES
  • : THE COSMIC SUPERCLASS
  • GENERIC ARRAY LISTS
  • OBJECT WRAPPERS AND AUTOBOXING
  • METHODS WITH A VARIABLE NUMBER OF PARAMETERS
  • ENUMERATION CLASSES
  • REFLECTION
  • DESIGN HINTS FOR INHERITANCE

Chapter 4 introduced you to classes and objects. In this chapter, you learn about inheritance, another fundamental concept of object-oriented programming. The idea behind inheritance is that you can create new classes that are built on existing classes. When you inherit from an existing class, you reuse (or inherit) its methods and fields and you add new methods and fields to adapt your new class to new situations. This technique is essential in Java programming.

As with the previous chapter, if you are coming from a procedure-oriented language like C, Visual Basic, or COBOL, you will want to read this chapter carefully. For experienced C++ programmers or those coming from another object-oriented language like Smalltalk, this chapter will seem largely familiar, but there are many differences between how inheritance is implemented in Java and how it is done in C++ or in other object-oriented languages.

This chapter also covers reflection, the ability to find out more about classes and their properties in a running program. Reflection is a powerful feature, but it is undeniably complex. Because reflection is of greater interest to tool builders than to application programmers, you can probably glance over that part of the chapter upon first reading and come back to it later.

Classes, Superclasses, and Subclasses

Let's return to the class that we discussed in the previous chapter. Suppose (alas) you work for a company at which managers are treated differently from other employees. Managers are, of course, just like employees in many respects. Both employees and managers are paid a salary. However, while employees are expected to complete their assigned tasks in return for receiving their salary, managers get bonuses if they actually achieve what they are supposed to do. This is the kind of situation that cries out for inheritance. Why? Well, you need to define a new class, , and add functionality. But you can retain some of what you have already programmed in the class, and all the fields of the original class can be preserved. More abstractly, there is an obvious "is– a" relationship between and . Every manager is an employee: This "is–a" relationship is the hallmark of inheritance.

Here is how you define a class that inherits from the class. You use the Java keyword to denote inheritance.

class Manager extends Employee { added methods and fields }

The keyword indicates that you are making a new class that derives from an existing class. The existing class is called the superclass, base class, or parent class. The new class is called the subclass, derived class, or child class. The terms superclass and subclass are those most commonly used by Java programmers, although some programmers prefer the parent/child analogy, which also ties in nicely with the "inheritance" theme.

The class is a superclass, but not because it is superior to its subclass or contains more functionality. In fact, the opposite is true: subclasses have more functionality than their super classes. For example, as you will see when we go over the rest of the class code, the class encapsulates more data and has more functionality than its superclass .

Our class has a new field to store the bonus, and a new method to set it:

class Manager extends Employee { . . . public void setBonus(double b) { bonus = b; } private double bonus; }

There is nothing special about these methods and fields. If you have a object, you can simply apply the method.

Manager boss = . . .; boss.setBonus(5000);

Of course, if you have an object, you cannot apply the method—it is not among the methods that are defined in the class.

However, you can use methods such as and with objects. Even though these methods are not explicitly defined in the class, they are automatically inherited from the superclass.

Similarly, the fields , , and are inherited from the superclass. Every object has four fields: , , , and .

When defining a subclass by extending its superclass, you only need to indicate the differences between the subclass and the superclass. When designing classes, you place the most general methods into the superclass and more specialized methods in the subclass. Factoring out common functionality by moving it to a superclass is common in object-oriented programming.

However, some of the superclass methods are not appropriate for the subclass. In particular, the method should return the sum of the base salary and the bonus. You need to supply a new method to override the superclass method:

class Manager extends Employee { . . . public double getSalary() { . . . } . . . }

How can you implement this method? At first glance, it appears to be simple—just return the sum of the and fields:

public double getSalary() { return salary + bonus; // won't work }

However, that won't work. The method of the class has no direct access to the private fields of the superclass. This means that the method of the class cannot directly access the field, even though every object has a field called . Only the methods of the class have access to the private fields. If the methods want to access those private fields, they have to do what every other method does—use the public interface, in this case, the public method of the class.

So, let's try this again. You need to call instead of simply accessing the field.

public double getSalary() { double baseSalary = getSalary(); // still won't work return baseSalary + bonus; }

The problem is that the call to simply calls itself, because the class has a method (namely, the method we are trying to implement). The consequence is an infinite set of calls to the same method, leading to a program crash.

We need to indicate that we want to call the method of the superclass, not the current class. You use the special keyword for this purpose. The call

super.getSalary()

calls the method of the class. Here is the correct version of the method for the class:

public double getSalary() { double baseSalary = super.getSalary(); return baseSalary + bonus; }

As you saw, a subclass can add fields, and it can add or override methods of the superclass. However, inheritance can never take away any fields or methods.

Finally, let us supply a constructor.

public Manager(String n, double s, int year, int month, int day) { super(n, s, year, month, day); bonus = 0; }

Here, the keyword has a different meaning. The instruction

super(n, s, year, month, day);

is shorthand for "call the constructor of the superclass with , , , , and as parameters."

Because the constructor cannot access the private fields of the class, it must initialize them through a constructor. The constructor is invoked with the special syntax. The call using must be the first statement in the constructor for the subclass.

If the subclass constructor does not call a superclass constructor explicitly, then the default (no-parameter) constructor of the superclass is invoked. If the superclass has no default constructor and the subclass constructor does not call another superclass constructor explicitly, then the Java compiler reports an error.

Having redefined the method for objects, managers will automatically have the bonus added to their salaries.

Here's an example of this at work: we make a new manager and set the manager's bonus:

Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15); boss.setBonus(5000);

We make an array of three employees:

Employee[] staff = new Employee[3];

We populate the array with a mix of managers and employees:

staff[0] = boss; staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1); staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);

We print out everyone's salary:

for (Employee e : staff) System.out.println(e.getName() + " " + e.getSalary());

This loop prints the following data:

Carl Cracker 85000.0 Harry Hacker 50000.0 Tommy Tester 40000.0

Now and each print their base salary because they are objects. However, is a object and its method adds the bonus to the base salary.

What is remarkable is that the call

e.getSalary()

picks out the correct method. Note that the declared type of is , but the actual type of the object to which refers can be either or .

When refers to an object, then the call calls the method of the class. However, when refers to a object, then the method of the class is called instead. The virtual machine knows about the actual type of the object to which refers, and therefore can invoke the correct method.

The fact that an object variable (such as the variable ) can refer to multiple actual types is called polymorphism. Automatically selecting the appropriate method at runtime is called dynamic binding. We discuss both topics in more detail in this chapter.

Listing 5-1 contains a program that shows how the salary computation differs for and objects.

Listing 5-1. ManagerTest.java

1. import java.util.*; 2. 3. /** 4. * This program demonstrates inheritance. 5. * @version 1.21 2004-02-21 6. * @author Cay Horstmann 7. */ 8. public class ManagerTest 9. { 10. public static void main(String[] args) 11. { 12. // construct a Manager object 13. Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15); 14. boss.setBonus(5000); 15. 16. Employee[] staff = new Employee[3]; 17. 18. // fill the staff array with Manager and Employee objects 19. 20. staff[0] = boss; 21. staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1); 22. staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15); 23. 24. // print out information about all Employee objects 25. for (Employee e : staff) 26. System.out.println("name=" + e.getName() + ",salary=" + e.getSalary()); 27. } 28. } 29. 30. class Employee 31. { 32. public Employee(String n, double s, int year, int month, int day) 33. { 34. name = n; 35. salary = s; 36. GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); 37. hireDay = calendar.getTime(); 38. } 39. 40. public String getName() 41. { 42. return name; 43. } 44. 45. public double getSalary() 46. { 47. return salary; 48. } 49. 50. public Date getHireDay() 51. { 52. return hireDay; 53. } 54. 55. public void raiseSalary(double byPercent) 56. { 57. double raise = salary * byPercent / 100; 58. salary += raise; 59. } 60. 61. private String name; 62. private double salary; 63. private Date hireDay; 64. } 65. 66. class Manager extends Employee 67. { 68. /** 69. * @param n the employee's name 70. * @param s the salary 71. * @param year the hire year 72. * @param month the hire month 73. * @param day the hire day 74. */ 75. public Manager(String n, double s, int year, int month, int day) 76. { 77. super(n, s, year, month, day); 78. bonus = 0; 79. } 80. 81. public double getSalary() 82. { 83. double baseSalary = super.getSalary(); 84. return baseSalary + bonus; 85. } 86. 87. public void setBonus(double b) 88. { 89. bonus = b; 90. } 91. 92. private double bonus; 93. }

Inheritance Hierarchies

Inheritance need not stop at deriving one layer of classes. We could have an class that extends , for example. The collection of all classes extending from a common superclass is called an inheritance hierarchy, as shown in Figure 5-1. The path from a particular class to its ancestors in the inheritance hierarchy is its inheritance chain.

There is usually more than one chain of descent from a distant ancestor class. You could form a subclass or that extends , and they would have nothing to do with the class (or with each other). This process can continue as long as is necessary.

Polymorphism

A simple rule enables you to know whether or not inheritance is the right design for your data. The "is–a" rule states that every object of the subclass is an object of the superclass. For example, every manager is an employee. Thus, it makes sense for the class to be a subclass of the class. Naturally, the opposite is not true—not every employee is a manager.

Another way of formulating the "is–a" rule is the substitution principle. That principle states that you can use a subclass object whenever the program expects a superclass object.

For example, you can assign a subclass object to a superclass variable.

Employee e; e = new Employee(. . .); // Employee object expected e = new Manager(. . .); // OK, Manager can be used as well

In the Java programming language, object variables are polymorphic. A variable of type can refer to an object of type or to an object of any subclass of the class (such as , , , and so on).

We took advantage of this principle in Listing 5-1:

Manager boss = new Manager(. . .); Employee[] staff = new Employee[3]; staff[0] = boss;

In this case, the variables and refer to the same object. However, is considered to be only an object by the compiler.

That means, you can call

boss.setBonus(5000); // OK

but you can't call

staff[0].setBonus(5000); // ERROR

The declared type of is , and the method is not a method of the class.

However, you cannot assign a superclass reference to a subclass variable. For example, it is not legal to make the assignment

Manager m = staff[i]; // ERROR

The reason is clear: Not all employees are managers. If this assignment were to succeed and were to refer to an object that is not a manager, then it would later be possible to call and a runtime error would occur.

Dynamic Binding

It is important to understand what happens when a method call is applied to an object. Here are the details:

  1. The compiler looks at the declared type of the object and the method name. Let's say we call , and the implicit parameter is declared to be an object of class . Note that there may be multiple methods, all with the same name, , but with different parameter types. For example, there may be a method and a method . The compiler enumerates all methods called in the class and all methods called in the superclasses of .

    Now the compiler knows all possible candidates for the method to be called.

  2. Next, the compiler determines the types of the parameters that are supplied in the method call. If among all the methods called there is a unique method whose parameter types are a best match for the supplied parameters, then that method is chosen to be called. This process is called overloading resolution. For example, in a call , the compiler picks and not . The situation can get complex because of type conversions to , to , and so on). If the compiler cannot find any method with matching parameter types or if multiple methods all match after applying conversions, then the compiler reports an error.

    Now the compiler knows the name and parameter types of the method that needs to be called.

  3. If the method is , , , or a constructor, then the compiler knows exactly which method to call. (The modifier is explained in the next section.) This is called static binding. Otherwise, the method to be called depends on the actual type of the implicit parameter, and dynamic binding must be used at runtimeruntime. In our example, the compiler would generate an instruction to call with dynamic binding.
  4. When the program runs and uses dynamic binding to call a method, then the virtual machine must call the version of the method that is appropriate for the actual type of the object to which refers. Let's say the actual type is , a subclass of . If the class defines a method , that method is called. If not, 's superclass is searched for a method , and so on.

    It would be time consuming to carry out this search every time a method is called. Therefore, the virtual machine precomputes for each class a method table that lists all method signatures and the actual methods to be called. When a method is actually called, the virtual machine simply makes a table lookup. In our example, the virtual machine consults the method table for the class and looks up the method to call for . That method may be or , where is some superclass of . There is one twist to this scenario. If the call is , then the compiler consults the method table of the superclass of the implicit parameter.

Let's look at this process in detail in the call in Listing 5-1. The declared type of is . The class has a single method, called , with no method parameters. Therefore, in this case, we don't worry about overloading resolution.

Because the method is not , , or , it is dynamically bound. The virtual machine produces method tables for the and classes. The table shows that all methods are defined in the class itself:

Employee: getName() -> Employee.getName() getSalary() -> Employee.getSalary() getHireDay() -> Employee.getHireDay() raiseSalary(double) -> Employee.raiseSalary(double)

Actually, that isn't the whole story—as you will see later in this chapter, the class has a superclass from which it inherits a number of methods. We ignore the methods for now.

The method table is slightly different. Three methods are inherited, one method is redefined, and one method is added.

Manager: getName() -> Employee.getName() getSalary() -> Manager.getSalary() getHireDay() -> Employee.getHireDay() raiseSalary(double) -> Employee.raiseSalary(double) setBonus(double) -> Manager.setBonus(double)

At runtime, the call is resolved as follows:

  1. First, the virtual machine fetches the method table for the actual type of . That may be the table for , , or another subclass of .
  2. Then, the virtual machine looks up the defining class for the signature. Now it knows which method to call.
  3. Finally, the virtual machine calls the method.

Dynamic binding has a very important property: it makes programs extensible without the need for modifying existing code. Suppose a new class is added and there is the possibility that the variable refers to an object of that class. The code containing the call need not be recompiled. The method is called automatically if happens to refer to an object of type .

Preventing Inheritance: Final Classes and Methods

Occasionally, you want to prevent someone from forming a subclass from one of your classes. Classes that cannot be extended are called final classes, and you use the modifier in the definition of the class to indicate this. For example, let us suppose we want to prevent others from subclassing the class. Then, we simply declare the class by using the modifier as follows:

final class Executive extends Manager { . . . }

You can also make a specific method in a class . If you do this, then no subclass can override that method. (All methods in a class are automatically .) For example:

class Employee { . . . public final String getName() { return name; } . . . }

There is only one good reason to make a method or class : to make sure that the semantics cannot be changed in a subclass. For example, the and methods of the class are . This indicates that the designers of the class have taken over responsibility for the conversion between the class and the calendar state. No subclass should be allowed to mess up this arrangement. Similarly, the class is a class. That means nobody can define a subclass of . In other words, if you have a reference, then you know it refers to a and nothing but a .

Some programmers believe that you should declare all methods as unless you have a good reason that you want polymorphism. In fact, in C++ and C#, methods do not use polymorphism unless you specifically request it. That may be a bit extreme, but we agree that it is a good idea to think carefully about final methods and classes when you design a class hierarchy.

In the early days of Java, some programmers used the keyword in the hope of avoiding the overhead of dynamic binding. If a method is not overridden, and it is short, then a compiler can optimize the method call away—a process called inlining. For example, inlining the call replaces it with the field access . This is a worthwhile improvement—CPUs hate branching because it interferes with their strategy of prefetching instructions while processing the current one. However, if can be overridden in another class, then the compiler cannot inline it because it has no way of knowing what the overriding code may do.

Fortunately, the just-in-time compiler in the virtual machine can do a better job than a traditional compiler. It knows exactly which classes extend a given class, and it can check whether any class actually overrides a given method. If a method is short, frequently called, and not actually overridden, the just-in-time compiler can inline the method. What happens if the virtual machine loads another subclass that overrides an inlined method? Then the optimizer must undo the inlining. That's slow, but it happens rarely.

Casting

Recall from Chapter 3 that the process of forcing a conversion from one type to another is called casting. The Java programming language has a special notation for casts. For example,

double x = 3.405; int nx = (int) x;

converts the value of the expression into an integer, discarding the fractional part.

Just as you occasionally need to convert a floating-point number to an integer, you also need to convert an object reference from one class to another. To actually make a cast of an object reference, you use a syntax similar to what you use for casting a numeric expression. Surround the target class name with parentheses and place it before the object reference you want to cast. For example:

Manager boss = (Manager) staff[0];

There is only one reason why you would want to make a cast—to use an object in its full capacity after its actual type has been temporarily forgotten. For example, in the class, the array had to be an array of objects because some of its entries were regular employees. We would need to cast the managerial elements of the array back to to access any of its new variables. (Note that in the sample code for the first section, we made a special effort to avoid the cast. We initialized the variable with a object before storing it in the array. We needed the correct type to set the bonus of the manager.)

As you know, in Java every object variable has a type. The type describes the kind of object the variable refers to and what it can do. For example, refers to an object (so it can also refer to a object).

The compiler checks that you do not promise too much when you store a value in a variable. If you assign a subclass reference to a superclass variable, you are promising less, and the compiler will simply let you do it. If you assign a superclass reference to a subclass variable, you are promising more. Then you must use a cast so that your promise can be checked at runtimeruntime.

What happens if you try to cast down an inheritance chain and you are "lying" about what an object contains?

Manager boss = (Manager) staff[1]; // ERROR

When the program runs, the Java runtime system notices the broken promise and generates a . If you do not catch the exception, your program terminates. Thus, it is good programming practice to find out whether a cast will succeed before attempting it. Simply use the operator. For example:

if (staff[1] instanceof Manager) { boss = (Manager) staff[1]; . . . }

Finally, the compiler will not let you make a cast if there is no chance for the cast to succeed. For example, the cast

Date c = (Date) staff[1];

is a compile-time error because is not a subclass of .

To sum up:

  • You can cast only within an inheritance hierarchy.
  • Use to check before casting from a superclass to a subclass.

Actually, converting the type of an object by performing a cast is not usually a good idea. In our example, you do not need to cast an object to a object for most purposes. The method will work correctly on both objects of both classes. The dynamic binding that makes polymorphism work locates the correct method automatically.

The only reason to make the cast is to use a method that is unique to managers, such as . If for some reason you find yourself wanting to call on objects, ask yourself whether this is an indication of a design flaw in the superclass. It may make sense to redesign the superclass and add a method. Remember, it takes only one uncaught to terminate your program. In general, it is best to minimize the use of casts and the operator.

Abstract Classes

As you move up the inheritance hierarchy, classes become more general and probably more abstract. At some point, the ancestor class becomes so general that you think of it more as a basis for other classes than as a class with specific instances you want to use. Consider, for example, an extension of our class hierarchy. An employee is a person, and so is a student. Let us extend our class hierarchy to include classes and . Figure 5-2 shows the inheritance relationships between these classes.

Figure 5-2 Inheritance diagram for and its subclasses

Why bother with so high a level of abstraction? There are some attributes that make sense for every person, such as the name. Both students and employees have names, and introducing a common superclass lets us factor out the method to a higher level in the inheritance hierarchy.

Now let's add another method, , whose purpose is to return a brief description of the person, such as

an employee with a salary of $50,000.00 a student majoring in computer science

It is easy to implement this method for the and classes. But what information can you provide in the class? The class knows nothing about the person except the name. Of course, you could implement to return an empty string. But there is a better way. If you use the keyword, you do not need to implement the method at all.

public abstract String getDescription(); // no implementation required

For added clarity, a class with one or more abstract methods must itself be declared abstract.

abstract class Person { . . . public abstract String getDescription(); }

In addition to abstract methods, abstract classes can have fields and concrete methods. For example, the class stores the name of the person and has a concrete method that returns it.

abstract class Person { public Person(String n) { name = n; } public abstract String getDescription(); public String getName() { return name; } private String name; }

Abstract methods act as placeholders for methods that are implemented in the subclasses. When you extend an abstract class, you have two choices. You can leave some or all of the abstract methods undefined. Then you must tag the subclass as abstract as well. Or you can define all methods. Then the subclass is no longer abstract.

For example, we will define a class that extends the abstract class and implements the method. Because none of the methods of the class are abstract, it does not need to be declared as an abstract class.

A class can even be declared as even though it has no abstract methods.

Abstract classes cannot be instantiated. That is, if a class is declared as , no objects of that class can be created. For example, the expression

new Person("Vince Vu")

is an error. However, you can create objects of concrete subclasses.

Note that you can still create object variables of an abstract class, but such a variable must refer to an object of a nonabstract subclass. For example:

Person p = new Student("Vince Vu", "Economics");

Here is a variable of the abstract type that refers to an instance of the nonabstract subclass .

Let us define a concrete subclass that extends the abstract class:

class Student extends Person { public Student(String n, String m) { super(n); major = m; } public String getDescription() { return "a student majoring in " + major; } private String major; }

The class defines the method. Therefore, all methods in the class are concrete, and the class is no longer an abstract class.

The program shown in Listing 5-2 defines the abstract superclass and two concrete subclasses, and . We fill an array of references with employee and student objects:

Person[] people = new Person[2]; people[0] = new Employee(. . .); people[1] = new Student(. . .);

We then print the names and descriptions of these objects:

for (Person p : people) System.out.println(p.getName() + ", " + p.getDescription());

Some people are baffled by the call

p.getDescription()

Isn't this call an undefined method? Keep in mind that the variable never refers to a object because it is impossible to construct an object of the abstract class. The variable always refers to an object of a concrete subclass such as or . For these objects, the method is defined.

Could you have omitted the abstract method altogether from the superclass and simply defined the methods in the and subclasses? If you did that, then you wouldn't have been able to invoke the method on the variable . The compiler ensures that you invoke only methods that are declared in the class.

Abstract methods are an important concept in the Java programming language. You will encounter them most commonly inside interfaces. For more information about interfaces, turn to Chapter 6.

Listing 5-2. PersonTest.java

1. import java.util.*; 2. 3. /** 4. * This program demonstrates abstract classes. 5. * @version 1.01 2004-02-21 6. * @author Cay Horstmann 7. */ 8. public class PersonTest 9. { 10. public static void main(String[] args) 11. { 12. Person[] people = new Person[2]; 13. 14. // fill the people array with Student and Employee objects 15. people[0] = new Employee("Harry Hacker", 50000, 1989, 10, 1); 16. people[1] = new Student("Maria Morris", "computer science"); 17. 18. // print out names and descriptions of all Person objects 19. for (Person p : people) 20. System.out.println(p.getName() + ", " + p.getDescription()); 21. } 22. } 23. 24. abstract class Person 25. { 26. public Person(String n) 27. { 28. name = n; 29. } 30. 31. public abstract String getDescription(); 32. 33. public String getName() 34. { 35. return name; 36. } 37. 38. private String name; 39. } 40. 41. class Employee extends Person 42. { 43. public Employee(String n, double s, int year, int month, int day) 44. { 45. super(n); 46. salary = s; 47. GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); 48. hireDay = calendar.getTime(); 49. } 50. 51. public double getSalary() 52. { 53. return salary; 54. } 55. 56. public Date getHireDay() 57. { 58. return hireDay; 59. } 60. 61. public String getDescription() 62. { 63. return String.format("an employee with a salary of $%.2f", salary); 64. } 65. 66. public void raiseSalary(double byPercent) 67. { 68. double raise = salary * byPercent / 100; 69. salary += raise; 70. } 71. 72. private double salary; 73. private Date hireDay; 74. } 75. 76. class Student extends Person 77. { 78. /** 79. * @param n the student's name 80. * @param m the student's major 81. */ 82. public Student(String n, String m) 83. { 84. // pass n to superclass constructor 85. super(n); 86. major = m; 87. } 88. 89. public String getDescription() 90. { 91. return "a student majoring in " + major; 92. } 93. 94. private String major; 95. }

Protected Access

As you know, fields in a class are best tagged as , and methods are usually tagged as . Any features declared won't be visible to other classes. As we said at the beginning of this chapter, this is also true for subclasses: a subclass cannot access the private fields of its superclass.

There are times, however, when you want to restrict a method to subclasses only or, less commonly, to allow subclass methods to access a superclass field. In that case, you declare a class feature as . For example, if the superclass declares the field as instead of private, then the methods can access it directly.

However, the class methods can peek inside the field of objects only, not of other objects. This restriction is made so that you can't abuse the protected mechanism and form subclasses just to gain access to the protected fields.

In practice, use fields with caution. Suppose your class is used by other programmers and you designed it with protected fields. Unknown to you, other programmers may inherit classes from your class and then start accessing your protected fields. In this case, you can no longer change the implementation of your class without upsetting the other programmers. That is against the spirit of OOP, which encourages data encapsulation.

Protected methods make more sense. A class may declare a method as if it is tricky to use. This indicates that the subclasses (which, presumably, know their ancestors well) can be trusted to use the method correctly, but other classes cannot.

A good example of this kind of method is the method of the class—see Chapter 6 for more details.

Here is a summary of the four access modifiers in Java that control visibility:

  1. Visible to the class only ().
  2. Visible to the world ().
  3. Visible to the package and all subclasses ().
  4. Visible to the package—the (unfortunate) default. No modifiers are needed.

When the compiler detects potential type-safety issues arising from mixing raw types with generic code, it issues unchecked warnings, including unchecked cast warnings, unchecked method invocation warnings, unchecked generic array creation warnings, and unchecked conversion warnings [Bloch 2008]. It is permissible to use the annotation to suppress unchecked warnings when, and only when, the warning-emitting code is guaranteed to be type safe. A common use case is mixing legacy code with new client code. The perils of ignoring unchecked warnings are discussed extensively in OBJ03-J. Do not mix generic with nongeneric raw types in new code.

According to the Java API, Annotation Type documentation [API 2014],

As a matter of style, programmers should always use this annotation on the most deeply nested element where it is effective. If you want to suppress a warning in a particular method, you should annotate that method rather than its class.

The annotation can be used in the declaration of variables and methods as well as an entire class. It is, however, important to narrow its scope so that only those warnings that occur in the narrower scope are suppressed.

Noncompliant Code Example

In this noncompliant code example, the annotation's scope encompasses the whole class:

This code is dangerous because all unchecked warnings within the class are suppressed. Oversights of this nature can lead to a at runtime.

Compliant Solution

Limit the scope of the annotation to the nearest code that generates a warning. In this case, it may be used in the declaration for the :

Noncompliant Code Example ()

This noncompliant code example is from an old implementation of :

When the class is compiled, it emits an unchecked cast warning:

This warning cannot be suppressed for just the statement because it is not a declaration [JLS 2011]. As a result, the programmer suppresses warnings for the entire method. This can cause issues when functionality that performs type-unsafe operations is added to the method at a later date [Bloch 2008].

Compliant Solution ()

When it is impossible to use the annotation in an appropriate scope, as in the preceding noncompliant code example, declare a new variable to hold the return value and adorn it with the annotation.

Applicability

Failure to reduce the scope of the annotation can lead to runtime exceptions and break type-safety guarantees.

This rule cannot be statically enforced in full generality; however, static analysis could be possible for some special cases.

Bibliography


@SuppressWarnings("unchecked") class Legacy { Set s = new HashSet(); public final void doLogic(int a, char c) { s.add(a); s.add(c); // Type-unsafe operation, ignored } }
class Legacy { @SuppressWarnings("unchecked") Set s = new HashSet(); public final void doLogic(int a,char c) { s.add(a); // Produces unchecked warning s.add(c); // Produces unchecked warning } }
@SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { if (a.length < size) { // Produces unchecked warning   return (T[]) Arrays.copyOf(elements, size, a.getClass()); }  // ... }
// Unchecked cast warning ArrayList.java:305: warning: [unchecked] unchecked cast found : Object[], required: T[] return (T[]) Arrays.copyOf(elements, size, a.getClass());
// ... @SuppressWarnings("unchecked") T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass()); return result; // ...

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *