CoreJava Volume 1 Chapter 5 - Inheritance

Classes, Superclasses, and Subclasses

  • Defining Subclasses
    • public class Manager extends Employee
      {
          added methods and fields
      }

      The kewyord extends indicates that you are makingn a new class taht 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

      • Subclasses have more functionality than their superclasses

        • Subclasses have methods or fields that their superclasses do not have

        • Subclasses inherit the non-private fields from their superclasses

  • Overriding Methods

    • Some of the superclass methods are not appropriate for the Manager subclass. In particualr, the getSalary method should return the sum of the base salary and hte bonus. You need to supply a new method to override the superclass method

      • public class Manager extends Employee
        {
            ...
            public double getSalary()
            {
                return salary + bonus; // won't work
            }
            ...
        }

        The above function won't work. This is because only the Employee methods have direct access to the private fields of the Employee class. This means that the getSalary method of the Manager class cannot directly access the salary field

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

        The above function still does not work. This is because getSalary method simply calls itself, because the Manager class has a getSalary method. The consequence is an infinite chain of calls to the same method, leading to a program crash

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

        Therefore, we need to indicate that we want to call the getSalary method of the Employee superclass, not the current class. You use the special keyword super for this purpose

    • Therefore, a subclass can add fields, and it can add methods or override the methods of the superclass

  • Subclass Constructor

    • public Manager(String name, double salary, int year, int month, int day)
      {
          super(name, salary, year, month, day);
          bonus = 0;
      }

      The instruction super(name, salary, year, month, day); is a shorthand for "call the constructor of the Employee superclass with n, s, year, month, and day as parameters

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

  • If the subclass constructor does not call a superclass constructor explicitly, the no-argument constructor of the superclasss is invoked. If the superclass does not have a no-argument constructor and the subclass constructor does not call another superclass constructor explicitly, the Java compiler reports an error

  • this vs super

    • this

      • to denote a reference to the implicit parameter

      • to call another constructor of the same class

    • super

      • to invoke a superclas method

      • to invoke a superclass constructor

  • Polymorphism

    • The fact that an object variable can refer to multiple actual types is called polymorphism

    • Dynamic binding

      • Automatically selecting the appropriate method at runtime is called dynamic binding

  • Inheritance hierarchies

    • The collection of all classes extending to a common superclass is called an inheritance hierarchy

    • The path from a particular class to its ancestor in the inheritance hierarchy is its inheritance chain

      • There is usually more than one chain of descent from a distant ancstor class

  • Polymorphism

    • "is-a" rule: every object of the subclass is an objecct of the superclas

    • polymorphic: in the Java programming language, object variables are polymorphic

      • A variable of type Employee can refer to an object of type Employee or to an object of any subclass of the Employee class

        • Manager boss = new Manager(. . .);
          Employee[] staff = new Employee[3];
          staff[0] = boss;
          
          boss.setBonus(5000); // OK
          staff[0].setBonus(5000); // ERROR

          In the above case, the variables staff[0] and boss refer to the same object. Howeevr, staff[0[ is considered to be only an Employee object by the compiler. Thus, the declared type of staff[0] is Employee, and the setBonus method is not a method of the Employee class

      • However, you cannot assign a superclass referecne to a subclass variable

      • In Java, arrays of subclass references can be converted to arrays of superclass references without a cast

        Manager[] managers = new Manager[10];
        
        Employee[] staff = managers; // OK
        
        staff[0] = new Employee("Harry Hacker", ...); // Allowed
        • The last line is not recommended because starff[0] and managers[0] refer to the same reference, so if we manage to smuggle a mere employee into the management ranks. Calling managers[0].setBonus(1000) would try to access a nonexistent instance field and would corrupt neighboring memory

        • To make sure no such corruption can occur, all arrays remembe rthe element type with which they were created, and they monitor that only compatible references are stored into them. For example, the array created as new Manager[10] remembers that it is an array of managers. Attempting to store an Employee reference causes an ArrayStoreException

  • Understanding Method Calls

    • Suppose we call x.f(args), and the implicit parameter x is declared to be an object of class C

      • The compiler looks at the declared type of the object and the method name. Note taht there may be multiple methods, all with the same name, f, but with different parameter types. The compiler enumerates all methods called f in the class C and all acessible methods called f in the superclasses of C 

      • Next, the compiler determines the types of the arguments supplied in the method call. If among all the methods called f there is a unique method whose parameter types are a best match for the supplied arguments, that method is chosen to be called. This process is called overloading resolution

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

      • If the method is private, static, final, or a constructor, then the compiler knows exactly which method to call. This is called static binding. Otherwise, the method to be called depends on the actual type of hte implicit parrameter, and dynamic binding must be used at runtime

      • Whe the program runs and uses dynamic binding to call a method, the virtual machine must call the version of the method that is appropriate for the actual type of hte object to which x refers. Let's say the actual type is D, a subclass of C. If the class D defines a method f(String), that method is called. If not, D's superclass is searched for a method f(String), and so on

        • It would be time-consuming to carry out this search every time a method is called. Instead, the virtual machine procomputes for each class a method table that lists all method signatures and the acutal methods to be called. When a method is actaully called, the virtual machine simply makes a table lookup

          • Recall that Manager is the subclass of Employee class.The Employee class has a single method, called getSalary, wiht no method parameters. Therefore,in this case, we don't worry about overloading resolution (e.getSalary())

          • The getSalary method is not private, static, or final, so it is dynamically bound. The virtual machine produces method tables for the Employee and Manager classes

          • For the Manager method table, three methos are inherited, one method is redefined, and one method is added

        • At runtime, the call e.getSalary() is resolved as follows:

          • First, the virtual machine fetches the method table for the acutal type of e. That may be the table for Employee, Manager, or another subclass of Employee

          • Then, the virtual machine looks up the defining calss for the getSalary() signature. Now it knows which method to call

          • 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

        • Note: when you override a method, the sublcass method must be at least as visible as the superclass method. In particular, if the superclass method is public, the subclass method must also be declared public

    • Method's signature

      • The name and parameter type list for a method is called the method's signature (probably because these two things are what required for a compiler to select a method)

      • The return type is not part of the signature

        • For example, suppose the Employee class has a method

          • public Employee getBuddy() { ... } 

            the Manager subclass can override this method as

          • public Manager getBuddy() { . . . } // OK to change return type

            We say that two getBuddy methods have covariant return types

  • Preventing Inheritance: Final Classes and Methods

    • Ocassionally, you want to prevent someone from forming a subclass of aone your classes. Classes that cannot be extended are called final classes, and you use the final modifier in the definition of the class to indicate this

      • public final class Executive extends Manager
        {
            ...
        }

        If we want to prevent others from subclassing the Executive class

    • You can also make a specific method ina class final. If you do this, then no subclass can override that method

    • The reason to make class/method final: to make sure its semantics cannot be changed in a subclass

      • inlining: If a method is not overriden, and it is short, then a compiler can optimize the method call away - a process called inlining

        • For example, inlining the call e.getName() replaces it with the field acess e.name

  • Casting

    • The reason to use cast

      • To use an object in its full capacity after its actual type has been temporarily forgotten

    • If you "lie" about what an object contains (casting an object to its subclass when it is not), when the program runs, the Java runtime system notices the broken promise and generates a ClassCastException

      • If you do not catch the exception, your program terminates

      • Use the instanceof operator to check

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

           

    • Finally, the compiler will not let you make a cast if htere is no chance for hte cast to succeed

      • String c = (String) staff[1];

    • In summary

      • You can cast only within an inheritance hierarchy

      • Use instanceof to check before casting from a superclass to a subclass

        • x instanceof C

          does not generate an exception if x is null. It simply returns false

      • In general, it is best to minimize the use of casts and instanceof 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

    • For example, we have a Person class which is the superclass of Employee and Student. Now we have a method getName and another method getDescription

      • It is easy to implement this method for the Employee and Student classes, but since we know nothing about the person except the name, it is not easy for us to implement this method for Person class

      • If we use the abstract keyword, we do not need to implement this 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

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

        Q: with the initialization and the block initalizaiton, which occurs first during the object creation?

      • In addition to abstract methods, abstract classes can have fields and concrete methods

    • Abstract methods act as placehoders 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, and the subclass is no longer abstract

    • Abstarct classes cannot be instantiated

      • Note htat you can still craete object variables of an abstract class, but such a variable must refer to an object of a nonabstract subclass

        • public class PersonTest
          {
              public static void main (String[] args) 
              {
                  var people = new Person[2];
              
                  // fill the people array with Student and Employee objects
                  people[0] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
                  people[1] = new Student("Maria Morris", "computer science");
          
                  // print out names and descriptions of all Person objects
                  for (Person p : people) 
                      System.out.println(p.getName() + ", " + p.getDescription())
              }
          }

          Keep in mind that the variable p never refers to a Person object because it is impossible to construct an object of the abstract Person class. The variable p always refer to an object of a concrete subclass such as Employee or student. For these objects, the getDescription method is defined

  • Protected Access

    • There are times when you want

      Object obj = new Employee("Harry Hacker", 35000);

       to restrict a method to subclasses only to allow subclass methods to access a superclass field. In that case, you declare a class feature as pretected

    • In Java, a protected field is accessible by any class in the same package. Now consider an Administrator subclass in a different package. The methods of hte Administrator class can peek inside the hireDay field of Administrator objects only, not of other Employee objects

    • Access control modifier

      • private: accessible in the class only

      • public: accssible by the world

      • protected: accessible in the package and all subclasses

      • by default: accessible in the package

      • Q: What is the relationship between package and jar file?

Object: The Cosmic Superclass

  • The Object class is the ultimate ancestor - every class in Java extends Object
  • Variables of Typoe Object
    • You can use a variable of type Object to refer to oebjects of any type
      • Object obj = new Employee("Harry Hacker", 35000);

         

    • In Java, only the values of primitive types are not objects

    • All array types, no matter whether they are arrays of objects or arrays of primitive types, are class types that extend the Object class

  • The equals Method

    • The equals method, as implemented in the Object class, determines whether two object referrences are identical

    • Compare two objects are equal based on their states

      • public class Employee
        {
        . . .
            public boolean equals(Object otherObject)
            {
                // a quick test to see if the objects are identical
                if (this == otherObject) return true;
                // must return false if the explicit parameter is null
                if (otherObject == null) return false;
                // if the classes don't match, they can't be equal
                if (getClass() != otherObject.getClass())
                    return false;
                // now we know otherObject is a non-null Employee
                Employee other = (Employee) otherObject;
                // test whether the fields have identical values
                    return name.equals(other.name)
                    && salary == other.salary
                    && hireDay.equals(other.hireDay);
            }
        }

        To guard against the possibility that name or hireDay are null, use the Objects.equals method. The call Ojbects.equals(a, b) returns true if both arguments are null, false if only one is null 

      • Utilize the superclass equals method to check if the fields of the super class are equal

        • public class Manager extends Employee
          {
              . . .
              public boolean equals(Object otherObject)
              {
                  if (!super.equals(otherObject)) return false;
                  // super.equals checked that this and otherObject belong to the same class
                  Manager other = (Manager) otherObject;
                  return bonus == other.bonus;
              }
          }

           

  • Equality Testing and Inheritance

    • The properties of equals method

      • reflexive: for any non-null reference x, x.equals(x) should retur ntrue

      • symmetric

        • using instanceof operator may violate this principle, given that we compare a superclass object against a subclass object

      • transitive

      • consistent

      • For any non-bull reference x, x.equals(null) should return false

    • Two scenarios with reagard to subclass and superclass

      • If subclasses can have their own notion of equality,  then the symmetry requirement forces you to use the getClass test

      • If the notion of equality is fixed in the superclass, then you can use the instanceof test and allow objects of different subclasses to equal one another

        • In the example with employees and managers, we consider two objects to be equal when they have matching fields. If we have two Manager objects with the same name, salary, and hire date, but with different bonuses, we want them to be different. Therefore, we use the getClass test

        • But suppose we used an employee ID for equality testing. This notion of equality makes sense for all subclasses. Then we could use the instanceof test, and we should have declared Employee.eqals as final

    • The instruction for writing the prefect equals method

      • Name the explicit parameter otherObject - later, you will need to cast it to another variable that you should call  other

      • Test whether this happens to be isentical to otherObject

        • if (this == otherObject) return true;

          This statement is just an obtimization

      • Test whether otherBoject is null an return false if it is

      • Compare the classes of this and othe Object. If the semantics of equals can change in subclases, use the getClass test

        • if (getClass() != otherObject.getClass()) return false;

           

        • If the same semantics holds for all subclasses, you can use an instanceof test:

          • if (getClass() != otherObject.getClass()) return false;

             

      • Cast otherObject to a variable of your class type

      • Now compare the fields, as required by your notion of equality. Return true if all fields match, false otherwise

    • Common mistake

      • public class Employee
        {
            public boolean equals(Employee other)
            {
                return other != null
                && getClass() == other.getClass()
                && Objects.equals(name, other.name)
                && salary == other.salary
                && Objects.equals(hireDay, other.hireDay);
            }
            . . .
        }

        This method declares the explicit parameter type as Employee. As a result, it does not override the equals method of the object class but defines a completely unrelated method

      • You can pretect yourself against this type of error by taggin methods that are intended to override superclass methods with @Override:

        • @Override public boolean equals(Object other)
          
          @Override public boolean equals(Employee other)

          An erorr is reported for the second line because this method doesn't override any method from the Object superclass

  • The hashCode Method

    • A hash code is an integer that is derived from an object. If x and y are two distinct objects, there should be a high probability that x.hashCode() and y.hashCode() are different

    • The hashCode method is defined in the Object class. Therefore, every object has a default hash code. That hash code is derived from the object's memory address

      • Note that for strings, the hash codes are based on the contents of the string, so the strings with the same content have the same hash codes. The string builders have different hash codes even if they have the same contents because their hash codes are based on their memory address

    • The hashCode method should return an integer

    • Your definitions of equls and hashCode must be compatible: if x.equals(y) is true, then x.hashCode must return the same value as y.hashCode()

      • For example, if you define Employee.equals to compare employee IDs, then the hashCode mthod needs to hash the IDs, not employee names or memory addresses

  • The toString Method

    • Another important method in Object is the toString method that returns a string representing the value of this object

    • Most toString methods follow this format: the name of the class, then the field values enclosed in square brackets

      • public String toString()
        {
            return getClass().getName()
            + "[name=" + name
            + ",salary=" + salary
            + ",hireDay=" + hireDay
            + "]";
        }

         

    • The toString method is ubiquitous for an importatnt reason: whenever an object is concatenated with a string by the "+" opeartor, the Java compiler automatically invokes the toString method to obtain a string representation of the object
      • Note that arrays inherit the toString method fromObject, with the added wtist htat the array types is printed in an archaic format
        • int[] luckyNumbers = {2, 3, 5, 7, 11, 13};
          String s = ""+ luckyNumbers;

          yields the string "[I@1a46e30"]

        • The remedy is to call the static Arrays.toString method instead. To correctly print multidimensinoal arrays, use Arrays.deepToString

    • The toString method is great for logging

 

 

 

Generic Array Lists

  • In Java, you can set the size of an array at runtime
    • int actualSize = ...;
      var staff = new Employee[actualSize];

       

    • If you want to deal with the common situation to modify an array, you can use another Java class, called ArrayList. The ArrayList class is similar to an array, but it automatically adjusts its capacity as you add and remove elements

      • ArrayList is a generac class with a type parameter. To sepcify the type of hte element objects that the array list holds, you append a  class name enclosed in angle brackets, such as ArrayList<Employee>

  • Declaring Array Lists

    • ArrayList<Employee> staff = new ArrayList<Employee>();
      
      var staff = new ArrayList<Employee>();
      
      ArrayList<Employee> staff = new ArrayList<>();

      The last one is called the "diamond" syntax because the empty brackets <> resembles a diamond. Use the diamond syntax together with the new operator. The compiler checks what happens tow the new value. If it is assigned to a variable, passed into a method, or teturned from a method, then the compiler checks the generic type of hte variable, parameter, or method. It then places that type into the <>. In our example, the new ArrayList<>() is assigned to a variable of type ArrayList<Employee>. Therefore, the generic type is Employee

      • Note: if you declare an AraryList with var, do not use the diamond syntax

    • Use add method to add new elements to an array list

      • If you call add and the internal array is full, the array list automatically creates a bigger array and copies all the objects from the samller to the bigger array

    • You can also pass initial capacity to the AraryList constructor

      • ArrayList<Employee> staff = new ArrayList<>(100);

         

    • Allocating ArrayList vs Allocating array

      • ArrayList<Employee> staff = new ArrayList<>(100);
        
        Employee arr[] = new Employee[100];

        There is an important distinction between the capacity of an array list and the size of an array. If you allocate an array with 100 entries, then the array has 100 slots, ready for use. An array list with a capacity of 100 elements has the potential of holding 100 elements, but at the begining, even after its initial construction, an array list holds no elements at all

    • The size method returns the actual number of elements in the array list

    • Once you are reasonably sure that the array list is at its permanent size, you can call the trimToSize method. This method adjusts the size of the memory block to use exactly as much storage space as is required to hold the current number of elements. The garbage collector will reclaim any excess memory

  • Acessing Array List Elements

    • get and set method

      • get

        • When there were no generic classes, the get method of the raw ArrayList class had no choice but to return an object. Consequently, callers of get had to cast the returned value to the desired type

          • Employee e = (Employee) staff.get(i);

             

        • The raw ArrayList is a bit dangerous. Its add and set methods accept objects of any type

  • Compatibility between Typed and Raw Array Lists

    • Q: What is the difference between raw array lists and typed ones?

    • You can passed a typed array list to the method that require a raw one without any cast

      • public class EmployeeDB
        {
            public void update(ArrayList list) { . . . }
            public ArrayList find(String query) { . . . }
        }

        You can pass a typed array list to the update method without any casts

      • Conversely, when you assign a raw ArrayList to a typed onem you get a warning

        • ArrayList<Employee> result = employeeDB.find(query); // yields
          warning

          To see the text of the warning, compile with the option - Xlint:unchecked

        • Using cast does not make the warnning go away

        • ArrayList<Employee> result = (ArrayList<Employee>) employeeDB.find(query);
          // yields another warning

           

    • For compatibility, the compiler translates all typed array lists into raw ArrayList objects after checking that the type rules were not violated. In a running program, all array lists are the same -- htere are no type parameters in the virtual machine. Thus, the casts (ArrayList)  and (ArrayList<Employee>) carry out identical runtime checks

      • Q: What is the purpose of introducing the typed parameter?

Object Wrappers and Autoboxing

  • Occasionally, you need to convert a primitive type like int to an object. All primitive types have class encounterparts -- wrapper classes
    • The wrapper classes are immutable -- you cannot change a warpped value after the wrapper has been constructed
    • They are also final, so you cannot subclass them
    • var list = new ArrayList<Integer>();

      An ArrayList<Integer> is far less efficient than an int[] array because each value is separately wrapped inside an object

  • There is a feature that makes it easy to add an element of type int to an ArrayList<Integer>. The call

    • list.add(3); // automatically translated to list.add(Integer.valueOf(3))

      This conversion is called autoboxing

    • Conversely, when you assign an Inetger object to an int value, it is automatically unboxed

    • The compiler automatically inserts instructions to unbox the object, increment the resulting value, and box it back

  • The == oprator between two wrapper class objects may result failure even if the two values inside the objects are the same

  • Wrapper class references can be null, it is possible for autoboxing to throw a NullPointerException

  • Also, if you mix Integer and Double types in a conditional expression, then the Integer value is unboxed, promoted to double, and boxed in Double

    • Integer n = 1;
      Double x = 2.0;
      System.out.println(true ? n : x); // prints 1.0

       

  • Note that boxing and unboxing is a courtesy of the compiler, not the virtual machine

    • The compiler inserts the necessary calls when it generate the bytecodes of a class

    • The virtual machine simply executes those bytecodes

  • The designers of Java found the wrappers a convenient place to  put certain basic methods, such as those for converting strings of digits to numbers

    • int x = Integer.parseInt(s);

      This had nothing to do with Integer objects - parseInt is a static method. But the Integer class was a good place to put it

Methods with a Variable Number of Parameters

  • It is possible to provide methods that can be called with a variable number of parameters (These are sometimes called "varargs" methods)
    • System.out.printf("%d", n);
      
      System.out.printf("%d %s", n, "widgets");

      both call the same method, even though on ecall has two parameters and the other has three

    • The printf method is defined like this

      public class PrintStream
      {
          public PrintStream printf(String fmt, Object... args) { return format(fmt, args); 
      }

      Here, the ellipsis ... is part of the Java code. It denotes that the method can receive an arbitrary number of objects

      • The printf method actaually receives two parameters: the format string and an Object[] array that holds all other parameters

      • For the implementor of printf, the Object ... parameter type is exactly the same as Object[]

      • The compiler needs to transform each call to printf, bundling the parameters into an array and autoboxing as necessary

        • System.out.printf("%d %s", new Object[] { new Integer(n), "widgets" });

           

    • You can define your own methods with variable parameters, and you can specify any type for the paraemters, even a primitive type

      • public static double max(double... values)
        {
            double largest = Double.NEGATIVE_INFINITY;
            for (double v : values) if (v > largest) largest = v;
            return largest;
        }

         

    • It is legal to pass an array as the last parameter of a method with variable parameters. Therefore, you can redefine an existing function whose last parameter is an array to a method with variable parameters, without breaking any existing code

      • If you like, you can even declare the main method as 

        • public static void main(String... args)

           

Enumeration Classes

  • public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE }

    The type defined by this declaration is actually a class. The class has exactly four instances -- it is not possible to construct new objects

    • Therefore, you never need to use equals for values of enumerated types. Simply use == to compare them

    • You can add constructors, methods, and fields to an enumerated type. The constructors ar eonly invoked when the enumerated constants are constructed

      • public enum Size
        {
            SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");
            private String abbreviation;
            private Size(String abbreviation) { this.abbreviation = abbreviation; }
            public String getAbbreviation() { return abbreviation; }
        }

        The constructor of an enumeration is always private. It is a syntax error to declare an enum constructor as public or portected

    • All enumerated types are subclasses of the class Enum. They inherit a numbe of methods from that class. The most useful one is toString, which returns the name of the enumrated constant

    • The converse of toString is the static valueOf method

      • Size s = Enum.valueOf(Size.class, "SMALL");

        sets s to Size.SMALL

    • Each enumerated type has a static values method that returns an array of all values of the enumeration

      • Size[] values = Size.values(); // returns the array with elements Size.SMALL, Size.MEDIUM, Size.LARGE, and Size.EXTRA_LARGE.

         

    • The ordinal method yields the position of an enumerated constant in the enum declaration, counting from zero

Reflection

  • Overview
    • The reflection library gives you a very rich and elaborate toolset to write prorams that manipulate Java code dynamically. Using reflectioon, Java can support user interface builders, object-relational mappers, and many other development tools that denamically inquire about the capabilities of classes
    • A program that can analyze the capabilities of classes is called reflective. You can use it to
      • Analyze the capabilites of classes at runtime
      • Inspect objects at truntime -- for example, to write a single toString method that works for all classes
      • Implement generic array manupulation code
      • Take advantage of Method objects tha work just like function pointers in languages such as C++
  • The Class Class
    • While your program is running, the Java runtime system always amaintains what is called runtime type identification on all objects. This information keeps track of the class to which each object belongs. Runtime type informatino is used by the virtual machine to select the correct methods to exectute
      • You can access this informatino by working with a special Java class. The class that holds this information is called Calss
      • The getClass() method in the Object class returns an instance of Class type
        • Employee e;
          . . .
          Class cl = e.getClass();
          
          System.out.println(e.getClass().getName() + " " + e.getName()); //output: Employee Harry Hacker if e is a employee

           

        • You can obtain a Class object corresponding to a class name by using hte static forName method

          • String className = "java.util.Random";
            Class cl = Class.forName(className);

            Use this method if the class name is stored in a string that varies at runtime

    • Note: At startup, the class containing your main method is loaded. It loads all classes that it needs. Each of those loaded classes loads the classes that it needs, and so on

    • A third method for obtaining an object of type Class is a convenient shorthand. If T is any Java type, then T.class is the matching class object

      • Class cl1 = Random.class; // if you import java.util.*;
        Class cl2 = int.class;
        Class cl3 = Double[].class;

        Note that a Class object really describes a type, which may or may not be a class. For example, int is not a class, but int.class is nevertheless an object of type Class

      • The Class class is actually a generic class. For example, Employee.class is of type Class<Employee>

    • The virtual machine manages a unique Class object for each type. Therefore, you an use the == operator to compare class objects

      • if (e.getClass() == Employee.class) . . .

        This test passes if e is an instance of Employee. Unlike the condition e instanceof Employee, this test fails if e is an instance of a subclass such as Manager

    • If you have an object of ytpe Class, you can use it to construct instances of the class. Call the getConstructor method to get an object of type Constructor, then use the newInstance method to construct an instance

      • var className = "java.util.Random";
        
        Class c1 = Class.forName(className);
        Object obj = c1.getConstructor.newInstance();

         

  • A Primer on Declaring Exceptions

    • Throwing an exception is more flexible than terminating the program because you can provide a handler that "catches" the exception and deals with it

    • Two types of exceptions

      • unchecked exceptions

        • The compiler does not expect that you provide a handler. You should spend your mental energy on avoiding these mistakes rather than coding handlers for them

      • checked exceptions

        • The compiler checks that you are aware of the exception and are prepared to deal with the consequences

        • Not all erros are avaoidable. If an exception can occur despite your best efforts, them most Java APIs will throw a checked exception. On eexample is the Class.forName method. There is no way for you to ensure that a class with the given name exists

          • Simple solution: Whenever a method contains a statement that might throw a checked exception, add a throws clause to the method name

            • public static void doSomethingWithClass(String name)
              throws ReflectiveOperationException
              {
                  Class cl = Class.forName(name); // might throw exception
                  do something with cl

              Any method that calls this method also needs a throw declaration. This include the main method

          • You only need to supply a throws clause for checked exceptions

  • Resources

    • Classes often have associated data files

      • Image and sound files

      • Text files with message strings and button labels

      • Such an associated files is called  a resource

    • The Class class provides a useful service for locating resource files. Here are the necessary steps:

      • Get the Class object of the class that has a resource -- for example, ResourceTest.class

      • Some methods, such as the getImage method of the ImageIcon class, accept URLs that describe resource locatoins. Then you call

        • URL url = cl.getResource("about.gif");

           

      • Otherwise, use the getResourceAsStream emthod to obtain an input stream for reading the data in the file

      • The point is that the Java virtual machine knows how to locate a class, so it can then search for the associated resource in the same location.  For example, suppose the ResourceTest class is in a package resources. Then the ResourceTest.class file is located in a resources directory, and you place an icon file into the same directory

        • Instead of placing a resource file inside the same directory as the class file, you can provide a relative or absolute path asuch as

          • data/about.txt
            /corejava/title.txt

             

      • Compile, build a JAR file, and execute it

        • javac resource/ResourceTest.java
          jar cvfe ResourceTest.jar resources.ResourceTest \
          resources/*.class resources/*.gif resources/data/*.txt 
          java -jar ResourceTest.jar

           

  • Using Reflection to Analyze the Capabilities of Classes

    • The three classes Field, Method, and Constructor in the java.lang.reflect package describe the field, methods, and constructos of a class, respectively. All three classes have a method called getName that returns the name of the item

      • Field has a method getType that returns an object, again of type Class, that describes the field type

      • The Method and Constructor classes have methods to report the ytpes of hte parameter, and the Method class also reports the reutrn type

      • All three of these classes also havea method called getModifiers that reutrn an integer

        • You can then use the static methods in the Modifer classin the java.lang.reflect package to analyze the integer that getModifier returns

  • Using Reflection to Analyze Objects at Runtime

    • It is easy to look at the contents of a specificfield of an object whose name and type are known whenyou write a program. But reflection lets you look at fields of objects that were not known at compile time

      • The key method to achieve this is the get method in the Field class

        • var harry = new Employee("Harry Hacker", 50000, 10, 1, 1989);
          Class cl = harry.getClass();
          // the class object representing Employee
          Field f = cl.getDeclaredField("name");
          // the name field of the Employee class
          Object v = f.get(harry);
          // the value of the name field of the harry object, i.e.,
          // the String object "Harry Hacker"

          If f is an object of type Field and obj is an object of the class o which f is a field, then f.get(obj) returns an object whose value is the curretn value of the field of obj

          • Actaullym there is a problem with this code. Since the name field is private field, the get and set methods will throw an IllegalAccessException. You can only use get and set with accessible fields

          • The default behacior of the relefection mechanism is to respect Java access control. However, you can override access  control by invoking the setAccessible method on Field, Method, or Constructor object

            • f.setAccessible(true); // now OK to call f.get(harry)

              The setAccessible method is a method of the AccessibleObject class, the common superclass of the Field, Method, and Constructor classes

            • The call to setAccessible throws an exception if the access is not granted. The access can be denied by the module system or a security manager

    • Generic toString method that works for any class

      • The generic toStringmethod uses getDeclaredFeilds to obtain all data fields and the setAccessible convenience method to make all fields accessbile. For each field, it obtains the name and the value. Each value is turned into a string by recursively invoking toString

      • The generic toString method needs to address a couple of complexities. Cycles of references could cause an infinite recursion. Therefore, the ObjectAnalyzer keeps track of objects that were already visited

      • You can use this generic toString method to implement the toString methods of your own classes

        • public String toString()
          {
              return new ObjectAnalyzer().toString(this);
          }

          However, before getting too excited about never having to implement toString again, rembember that the days of unconstrolled access to internals are numbered

  • Using Reflection to Write Generic Array Code

    • The Array class in the java.lang.reflect package allows you to create array dynamically

      • First attempt, converting an Employee[] array to an Object[] array

        • public static Object[] badCopyOf(Object[] a, int newLength) // not useful
          {
              var newArray = new Object[newLength];
              System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));
              return newArray;
          }

          The problem with this is that this code returns an array of objects, and an array of objects cannot be cast ot an array of employees. The virtual machine gnerate a ClassCastException at runtime

        • The point is that, as we mentioned earlier, a Java array remembers the type of its entries—that is, the element type used in the new expression that created it. It is legal to cast an Employee[] temporarily to an Object[] array and then cast it back, but an array that started its life as an Object[] array can never be cast into an Employee[] array

      • Second attempt, to actually carry this out, we need to get the length and the component type of the new array

        • We obtain the length by calling Array.getLength(a)

        • To get the component type of the new array

          • First, get the class object of a

          • Confirm that it is indeed an array

          • Use the getComponentType method of the Class class to find the right type for the array

          • public static Object goodCopyOf(Object a, int newLength)
            {
                Class cl = a.getClass();
                if (!cl.isArray()) return null;
                Class componentType = cl.getComponentType();
                int length = Array.getLength(a);
                Object newArray = Array.newInstance(componentType, newLength);
                System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
                return newArray
            }
            
            

             

            int[] a = { 1, 2, 3, 4, 5 };
            a = (int[]) goodCopyOf(a, 10);

             

  • Invoking Arbitrary Methods and Constructors

    • Recall that you can inspect a field of an object with the get method of the Field class. Similarly, the Method calss has an invoke method that lets you  call the method that is wrapped in the current Method object

      • Object invoke(Object obj, Object... args)

        The first parameter is the implicit parameter, and the ramaining objects provide the explicit parameters. For a static method, the first parameter is ignored -- you can set it to null

        • if m1 represents the getName method of the Employee class, the following code show how you can call it

          • String n = (String) m1.invoke(harry);

            if the returen type is primitive type, the invoke method will return the wrapper type instead

          • double s = (Double) m2.invoke(harry);

             

        • To obtain a Method object

          • call getDeclaredMethods and search through the returned array of Method objects until you find the method you want

          • call getMethod method of the Class class

            • Method getMethod(String name, Class... parameterTypes)
              
              Method m1 = Employee.class.getMethod("getName");
              Method m2 = Employee.class.getMethod("raiseSalary", double.class);

               

        • Use a similar approach for invoking arbitrary constructors

          • Class cl = Random.class; // or any other class with a constructor that
            // accepts a long parameter
            Constructor cons = cl.getConstructor(long.class);
            Object obj = cons.newInstance(42L);

             

    • The prameters and tuerne values of invoke are necessarily of type Object. That means you must cast back and forth a lot. As a result, the compiler is deprived of the chance to check your code, so errors surface only during testing, when they are more tedious to find and fix

Design Hints for Inheritance

  • Place common operations and fields in the superclass
  • Don't use protected fields
    • The set of sublcasses is unbounded -- anyone can form a subclass of yoru classes and then write code that directly accesses protected instance fields, thereby breaking encapsulation
    • In Java, all calsses in the same package have access to protected fields, whether or not they are subclasses
  • Use inheritance to model the "is-a" relationship
  • Don't use inheritance unless all inherited methods make sense
  • Don't change the expcted behavior when you override a method
  • Don't overuse reflection
    • Reflection can be very useful in system programming, but not appropriate in applications
  • Use polymorphism, not ytpe information
    • if (x is of type 1)
          action1(x);
      else if (x is of type 2)
          action2(x);

      Whenever you find code of the above form, think polymorphism

      • Do action1 and action2 represent a common concept? If so, make the concept a method of a common superclass or interface of both types. Then, you can simply call x.action() 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章