5 继承

利用继承,人们可以基于已存在的类构造一个新类,并复用这些类的方法和域。

反射,是指在程序运行期间发现更多的类及属性的能力。

5.1 类、超类和子类

class Manager extends Employee

关键词extends表面正在构造的新类派生于一个已存在的类。已存在的类称为超类superclass,基类base class或父类parent class;新类称为子类subclass、派生类derived class或孩子类child class。

子类比超类拥有的功能更加丰富。

一个变量可以指示多种实际类型的现象被称为多态,在运行时能够自动地选择调用哪个方法的现象称之为动态绑定。

public class Manager extends Employee {
    private double bonus;

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

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

    public void setBonus(double b) {
        bonus = b;
    }
}

5.1.1 继承层次

由一个公共类派生出的所有类的集合被称为继承层次。由某个特定类到其祖先的路径被称为该类的继承链。

5.1.2 多态

is-a,置换法则,超类出现的地方都可以用子类对象去置换。

Java中,对象变量是多态的,一个employee变量可以引用一个 Employee对象,也可以引用一个Employee类的任何一个子类的对象。

但是,不能把一个超类的引用赋给子类变量。

5.1.3 动态绑定

重载解析:在所有同名的方法中查到一个与参数类型完全匹配,就选用这个方法。

如果是private,final,static方法,编译器将可以准确知道应该调用哪个方法,称为静态绑定。

5.1.4 阻止继承:final类和方法 

不允许扩展的类称之为final类。

类中特定的方法也可以被声明为final,子类就不能覆盖这个方法。final类中所有方法自动地成为final方法。

将类和方法声明为final的主要目的是,确保它们不在子类中改变语义。

5.1.5 强制类型转换

Manager boss = (Manager) staff[0]

强制类型转换的唯一原因是,在暂时忽略了对象的实际类型之后,使用对象的全部功能。

  • 只能在继承层次内进行类型转换
  • 在将超类转换成子类之前,应该使用instanceof进行检查。

5.1.6 抽象类

包含一个或多个抽象方法的类必须被声明为抽象类。

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

除了抽象方法之外,抽象类还可以包含具体数据和具体方法。

类即使不包含抽象方法,也可以将类声明为抽象类。

抽象类不能被实例化。

可以定义一个抽象类的变量对象,但是它只能引用非抽象子类的对象。

5.1.7 受保护方法

超类中的某些方法允许被子类访问,或允许子类的方法访问超类的某个域。为此,需要将这些方法和域声明为protected。

归纳一下Java当中控制可见性的4个访问修饰符:

  • 仅对本类可见——private
  • 对所有类可见——public
  • 对本包和所有子类可见——protected
  • 对本包可见——不需要修饰符

5.2 Object:所有类的超类

在Java中,只有基本类型不是对象,所有的数组类型都扩展于Object类。

5.2.1 euals方法

Object类中的equals方法用于检测一个对象是否等于另外一个对象。如果两个对象具有相同的引用,它们一定是相等的。然而,经常需要检测两个对象状态的相等性,如果两个对象状态相等,就认为两个对象是相等的。

5.2.2 相等测试与继承

Java语言规范要求equals方法具有下面的特性

  • 自反性
  • 对称性
  • 传递性
  • 一致性

编写一个完美equals方法的建议

  1. 显式参数命名为otherObject,稍后将它转换为一个叫做other的变量
  2. 检测this与otherObject是否引用同一个对象
    ifthis == otherObject) return true;

     

  3. 检测otherObject是否为null,如果为null,返回false。这项检测很有必要。
  4. 比较this与otherObject是否属于同一个类,用getClass检测
  5. 将otherObject转换为相应的类类型变量。
  6. 开始对所有需要进行比较的域进行比较了,用==比较基本类型域,用equals比较对象域。

5.2.3 hashCode方法

散列码是一个对象导出的整型值。散列码是没有规律的。

hashCode方法定义在Objcet类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。

字符串的散列码是由其内容导出的,StringBuffer类中没有定义hashCode方法,它的散列码是由Object类默认的hashCode方法导出的对象存储地址。

5.2.4 toString方法

toString方法用于返回对象值的字符串。

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

只要一个对象与一个字符串通过“+”连接起来,Java编译会自动调用toString方法,以便获得这个对象的字符串描述。

5.3泛型数组列表

ArrayList类是一个采用类型参数的泛型类。

ArrayList<Employee>staff = new ArrayList<>();
staff.add(new Employee("kate", ...);
staff.add(new Employee("Ann",...);
staff.ensureCapacity(100);
staff.size();
staff.trimToSize();

5.3.1 访问数组列表元素

  • 不必指出数组的大小
  • 使用add将任意多的元素添加到数组中
  • 使用size()替代length计算元素的数目
  • 使用a.get(i)替代a[i]访问元素

5.3.2 类型化与原始数组列表的兼容性

可以将类型化数组传递给原始数组列表参数,但是反之则将会得到一个警告。

5.4 对象包装器与自动装箱

自动装箱

list.add(3);
list.add(Integer.valueof(3);

自动拆箱

int n = list.get(i);
int n = list.get(i).intValue();

装箱与拆箱是编译器认可的,而不是虚拟机。

int x = Integer.parseInt(s);

5.5 参数数量可变的方法

实际上,printf方法接收两个参数,一个是格式字符串,一个是Object[]数组,其中保存着所有参数。

用户也可以自定义自己的可变参数的方法,实质上是将可变参数转成Object[]数组传递给方法。

5.6 枚举类

比较两个枚举类的时候,永远不要调用equals方法,直接使用==就可以了。

toString的逆方法是静态方法valueof。

5.7 反射

反射库提供了能够动态操作Java代码的程序。这些功能被大量用于JavaBeans当中。

能够分析类能力的程序称为反射。反射机制可以用来

  • 在运行中分析类的能力
  • 在运行中查看对象
  • 实现通用的数组操作代码
  • 利用method对象。

 5.7.1 Class类

程序运行中,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。保存这些信息的类,被称为Class类。

Employee e;
Class cl = e.getClass();
System.out.println(e.getClass().getName()+ "" + e.getName());

String className = "java.util.Date";
class cl = Class.forName(className);

Class cl = int.class;
Class cl1 = Date.class;

Class对象实际上表示的是一种类型,而这个类型未必一定是一个类,比如int不是类,但int.class是一个Class类型的对象。

虚拟机为每个类型管理一个Class对象。可以利用“==”运算符实现两个类对象比较的操作。

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

e.getClass().newInstance();

Object m = Class.forName(s).newInstance();

5.7.2 捕获异常

异常有两种类型,已检查异常与未检查异常。对于已检查异常,编译器将会检查是否提供了处理器。ClassforName方法就是一个抛出已检查异常的例子。

try catch

5.7.3 利用反射来分析类的能力

反射机制的最重要的内容——检查类的结构

Field,Method和Constructor分别用于描述类的域,方法和构造器。

Modifier类。

Class类中的方法getFields, getMethods, getConstructors, getDecalredFields, getDeclaredMethods, getDeclaredConstructors。

public class ReflectionTest
{
   public static void main(String[] args)
   {
      // read class name from command line args or user input
      String name;
      if (args.length > 0) name = args[0];
      else
      {
         Scanner in = new Scanner(System.in);
         System.out.println("Enter class name (e.g. java.util.Date): ");
         name = in.next();
      }

      try
      {
         // print class name and superclass name (if != Object)
         Class cl = Class.forName(name);
         Class supercl = cl.getSuperclass();
         String modifiers = Modifier.toString(cl.getModifiers());
         if (modifiers.length() > 0) System.out.print(modifiers + " ");
         System.out.print("class " + name);
         if (supercl != null && supercl != Object.class) System.out.print(" extends "
               + supercl.getName());

         System.out.print("\n{\n");
         printConstructors(cl);
         System.out.println();
         printMethods(cl);
         System.out.println();
         printFields(cl);
         System.out.println("}");
      }
      catch (ClassNotFoundException e)
      {
         e.printStackTrace();
      }
      System.exit(0);
   }

   /**
    * Prints all constructors of a class
    * @param cl a class
    */
   public static void printConstructors(Class cl)
   {
      Constructor[] constructors = cl.getDeclaredConstructors();

      for (Constructor c : constructors)
      {
         String name = c.getName();
         System.out.print("   ");
         String modifiers = Modifier.toString(c.getModifiers());
         if (modifiers.length() > 0) System.out.print(modifiers + " ");         
         System.out.print(name + "(");

         // print parameter types
         Class[] paramTypes = c.getParameterTypes();
         for (int j = 0; j < paramTypes.length; j++)
         {
            if (j > 0) System.out.print(", ");
            System.out.print(paramTypes[j].getName());
         }
         System.out.println(");");
      }
   }

   /**
    * Prints all methods of a class
    * @param cl a class
    */
   public static void printMethods(Class cl)
   {
      Method[] methods = cl.getDeclaredMethods();

      for (Method m : methods)
      {
         Class retType = m.getReturnType();
         String name = m.getName();

         System.out.print("   ");
         // print modifiers, return type and method name
         String modifiers = Modifier.toString(m.getModifiers());
         if (modifiers.length() > 0) System.out.print(modifiers + " ");         
         System.out.print(retType.getName() + " " + name + "(");

         // print parameter types
         Class[] paramTypes = m.getParameterTypes();
         for (int j = 0; j < paramTypes.length; j++)
         {
            if (j > 0) System.out.print(", ");
            System.out.print(paramTypes[j].getName());
         }
         System.out.println(");");
      }
   }

   /**
    * Prints all fields of a class
    * @param cl a class
    */
   public static void printFields(Class cl)
   {
      Field[] fields = cl.getDeclaredFields();

      for (Field f : fields)
      {
         Class type = f.getType();
         String name = f.getName();
         System.out.print("   ");
         String modifiers = Modifier.toString(f.getModifiers());
         if (modifiers.length() > 0) System.out.print(modifiers + " ");         
         System.out.println(type.getName() + " " + name + ";");
      }
   }
}
View Code

5.7.4 在运行时使用反射分析对象

反射机制的默认行为受限于Java的访问控制,为了覆盖访问控制,可以调用Field, Method, Constructor对象的setAccessible方法。

编写一个可供任意类使用的通用的toString方法。

public String toString(Object obj)
   {
      if (obj == null) return "null";
      if (visited.contains(obj)) return "...";
      visited.add(obj);
      Class cl = obj.getClass();
      if (cl == String.class) return (String) obj;
      if (cl.isArray())
      {
         String r = cl.getComponentType() + "[]{";
         for (int i = 0; i < Array.getLength(obj); i++)
         {
            if (i > 0) r += ",";
            Object val = Array.get(obj, i);
            if (cl.getComponentType().isPrimitive()) r += val;
            else r += toString(val);
         }
         return r + "}";
      }

      String r = cl.getName();
      // inspect the fields of this class and all superclasses
      do
      {
         r += "[";
         Field[] fields = cl.getDeclaredFields();
         AccessibleObject.setAccessible(fields, true);
         // get the names and values of all fields
         for (Field f : fields)
         {
            if (!Modifier.isStatic(f.getModifiers()))
            {
               if (!r.endsWith("[")) r += ",";
               r += f.getName() + "=";
               try
               {
                  Class t = f.getType();
                  Object val = f.get(obj);
                  if (t.isPrimitive()) r += val;
                  else r += toString(val);
               }
               catch (Exception e)
               {
                  e.printStackTrace();
               }
            }
         }
         r += "]";
         cl = cl.getSuperclass();
      }
      while (cl != null);

      return r;
   }
}
View Code

5.7.5 使用反射编写泛型数组代码

5.7.6 调用任意方法

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

String n = m1.invoke(harry);
double s = (Double)m2.invoke(harry);

Method m1 = Employee.class.getMethod("getName");
Method m2 = Employee.class.getMethod("getSalary", double.class);

invoke的参数和返回值必须是Object类型的,这就意味着必须进行多次的类型转换,同时要比直接调用慢一点,建议仅在必要时使用Method对象。

public class MethodTableTest
{
   public static void main(String[] args) throws Exception
   {
      // get method pointers to the square and sqrt methods
      Method square = MethodTableTest.class.getMethod("square", double.class);
      Method sqrt = Math.class.getMethod("sqrt", double.class);

      // print tables of x- and y-values

      printTable(1, 10, 10, square);
      printTable(1, 10, 10, sqrt);
   }

   /**
    * Returns the square of a number
    * @param x a number
    * @return x squared
    */
   public static double square(double x)
   {
      return x * x;
   }

   /**
    * Prints a table with x- and y-values for a method
    * @param from the lower bound for the x-values
    * @param to the upper bound for the x-values
    * @param n the number of rows in the table
    * @param f a method with a double parameter and double return value
    */
   public static void printTable(double from, double to, int n, Method f)
   {
      // print out the method as table header
      System.out.println(f);

      double dx = (to - from) / (n - 1);

      for (double x = from; x <= to; x += dx)
      {
         try
         {
            double y = (Double) f.invoke(null, x);
            System.out.printf("%10.4f | %10.4f%n", x, y);
         }
         catch (Exception e)
         {
            e.printStackTrace();
         }
      }
   }
}

5.8 继承设计的技巧

  1. 将公共操作和域放在超类
  2. 不要使用受保护的域
  3. 使用基础实现is-a关系
  4. 除非所有继承都有意义,否则不要使用继承
  5. 在覆盖方法时,不要改变预期的行为
  6. 使用多态,而非类型信息
  7. 不要过多的使用反射

 

posted @ 2020-04-21 10:56  ayor  阅读(139)  评论(0)    收藏  举报