第五章 继承

对象包装器与自动装箱

有时, 需要将 int 这样的基本类型转换为对象。所有的基本类型都冇一个与之对应的类。 例如,丨nteger 类对应基本类型 int。通常, 这些类称为包装器 ( wrapper) 这些对象包装器类 拥有很明显的名字:Integer、Long、Float、Double、Short、Byte、Character、Void 和 Boolean (前 6 个类派生于公共的超类 Number)。对象包装器类是不可变的,即一旦构造了包装器,就不 允许更改包装在其中的值。同时, 对象包装器类还是 final, 因此不能定义它们的子类。 假设想定义一个整型数组列表。而尖括号中的类型参数不允许是基本类型,也就是说, 不允许写成 ArrayList<int>。这里就用到了 Integer 对象包装器类。我们可以声明一个 Integer 对象的数组列表。

ArrayList<Integer> list = new ArrayList<>();

 

幸运的是, 有一个很有用的特性,从而更加便于添加 int类型的元素到 ArrayLisKlntegeP 中。下面这个调用

 list.add(3);

将自动地变换成

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

这种变换被称为自动装箱(autoboxing)。

 

相反地, 当将一个 Integer 对象赋给一个 int 值时, 将会自动地拆箱。也就是说, 编译器 将下列语句:

int n = list.get(i);

翻译成

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

甚至在算术表达式中也能够自动地装箱和拆箱。例如,可以将自增操作符应用于一个包装器 引用:

Integer n = 3; 
​
n++;

 

编译器将自动地插人一条对象拆箱的指令, 然后进行自增计算, 最后再将结果装箱。

[注]  自动装箱规范要求 boolean、byte、char 127, 介于 -128 ~ 127 之间的 short 和 int 被包装到固定的对象中。例如,如果在前面的例子中将 a 和 b 初始化为 100,对它们 进行比较的结果一定成立。

 

参数数量可变的方法

在 Java SE 5.0 以前的版本中,每个 Java方法都有固定数量的参数。然而,现在的版本 提供了可以用可变的参数数量调用的方法(有时称为“ 变参” 方法)。

前面已经看到过这样的方法:printfo 例如,下面的方法调用:

System.out.printf("%d", n);

System.out.printf("%d %s\ n, "widgets"); 

在上面两条语句中,尽管一个调用包含两个参数,另一个调用包含三个参数,但它们调用的 都是同一个方法。

printf方法是这样定义的:

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

 

这里的省略号 . . . 是 Java 代码的一部分,它表明这个方法可以接收任意数量的对象。

实际上,printf方法接收两个参数,一个是格式字符串, 另一个是 Object ] 数组, 其中 保存着所有的参数(如果调用者提供的是整型数组或者其他基本类型的值, 自动装箱功能 将把它们转换成对象 )。现在将扫描 Ant 字符串, 并将第 i 个格式说明符与 args[i] 的值匹配 起来。 换句话说,对于 printf 的实现者来说,Object… 参数类型与 Object[ ]完全一样。 编译器需要对 printf的每次调用进行转换, 以便将参数绑定到数组上,并在必要的时候 进行自动装箱:

System.out.printf("%d %s", new Object[] { new Integer(n), "widgets" } );
[注]  允许将一个数组传递给可变参数方法的最后一个参数。例如: System.out.printf("%d %s“ ,new Object[] { new Integer(1), "widgets" } ); 因此, 可以将已经存在且最后一个参数是数组的方法重新定义为可变参数的方法, 而不会破坏任何已经存在的代码。 例如, MessageFormat.format 在 Java SE 5.0 就采用了 这种方式。甚至可以将 main 方法声明为下列形式: public static void main(String... args)。

枚举类

下面是一个典型的例子:

public enum Size { SMALL, MEDIUM, LARGE, EXTRAJARGE }; 

 

实际上, 这个声明定义的类型是一个类, 它刚好有 4 个实例, 在此尽量不要构造新对象。

因此, 在比较两个枚举类型的值时, 永远不需要调用 equals, 而直接使用“ = =” 就 可以了。

如果需要的话, 可以在枚举类型中添加一些构造器、方法和域。当然,构造器只是在构 造枚举常量的时候被调用。下面是一个示例:

public enum Size { 
​
SMALLf("S"), MEDIU(”M"), LARGE("L"), EXTRA_LARGE("XL");
private String abbreviation;
​
private Size(String abbreviation) { this,abbreviation = abbreviation; } 
​
public String getAbbreviation() { return abbreviation; }
} 

 

所有的枚举类型都是 Enum 类的子类。它们继承了这个类的许多方法。其中最有用的一 个是 toString, 这个方法能够返回枚举常量名。例如, Size.SMALL.toString( ) 将返回字符串 “ SMALL”。

程序清单 5-12 演示了枚举类型的工作方式。

[注]  如同 Class 类一样, 鉴于简化的考虑, Enum 类省略了一个类型参数。 例如, 实 际上, 应该将枚举类型 Size}广展为 Enum<Size>。 类型参数在 compareTo 方法中使用。
//程序清单 5-12 enums/EnumTest.java
package enums;
​
import java.util.*;
​
/**
 * This program demonstrates enumerated types.
 * @version 1.0 2004-05-24
 * @author Cay Horstmann
 */
public class EnumTest
{  
   public static void main(String[] args)
   {  
      Scanner in = new Scanner(System.in);
      System.out.print("Enter a size: (SMALL, MEDIUM, LARGE, EXTRA_LARGE) ");
      String input = in.next().toUpperCase();
      Size size = Enum.valueOf(Size.class, input);
      System.out.println("size=" + size);
      System.out.println("abbreviation=" + size.getAbbreviation());
      if (size == Size.EXTRA_LARGE)
         System.out.println("Good job--you paid attention to the _.");      
   }
}
​
enum Size
{
   SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");
​
   private Size(String abbreviation) { this.abbreviation = abbreviation; }
   public String getAbbreviation() { return abbreviation; }
​
   private String abbreviation;
}

 


反射

反射库(reflection library) 提供了一个非常丰富且精心设计的工具集, 以便编写能够动 态操纵 Java 代码的程序。这项功能被大量地应用于 JavaBeans 中, 它是 Java组件的体系结构 。

能够分析类能力的程序称为反射(reflective)。反射机制的功能极其强大,在下面可以看 到, 反射机制可以用来:

•在运行时分析类的能力。

•在运行时查看对象, 例如, 编写一个 toString方法供所有类使用。

•实现通用的数组操作代码。

•利用 Method 对象, 这个对象很像中的函数指针。

反射是一种功能强大且复杂的机制。使用它的主要人员是工具构造者,而不是应用程序 员P

Class 类

在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。 这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。

[提示]  :在启动时, 包含 main 方法的类被加载。它会加载所有需要的类。这些被加栽的类 又要加载它们需要的类, 以此类推。对于一个大型的应用程序来说, 这将会消耗很多时 间, 用户会因此感到不耐烦。可以使用下面这个技巧给用户一种启动速度比较快的幻觉。 不过,要确保包含 main 方法的类没有显式地引用其他的类。首先,显示一个启动画面; 然后,通过调用 Class.forName 手工地加载其他的类。

Class 类实际上是一个泛型类。例如, Employee.class 的类型是 Class<Employee>。 没有说明这个问题的原因是: 它将已经抽象的概念更加复杂化了。在大多数实际问题中, 可以忽略类型参数, 而使用原始的 Class 类。

 

捕获异常

当程序运行过程中发生错误时, 就会“ 抛出异常'抛出异常比终止程序要灵活得多, 这是因为可以提供一个“ 捕获” 异常的处理器(handler) 对异常情况进行处理。

异常有两种类型: 未检查异常和已检查异常。对于已检查异常, 编译器将会检查是否提 供了处理器。 然而,有很多常见的异常, 例如,访问 null 引用, 都属于未检查异常。

将可能抛出已检査异常的一个或多个方法调用代码放在 try块中,然后在 catch 子句中提 供处理器代码。

try { 
​
statements that might throwexceptions 
​
} catch (Exception e) {
​
 handleraction 
​
}

 

下面是一个示例:

try { 
​
String name = . . .; // get class name 
​
Class cl = Class.forName(name); // might throw exception do something with cl 
​
} catch (Exception e) { 
​
e.printStackTrace(); 
​
}

 

如果类名不存在, 则将跳过 try块中的剩余代码,程序直接进人 catch 子句(这里,利用 Throwable 类的 printStackTrace 方法打印出栈的轨迹。Throwable 是 Exception 类的超类) 。如 果 try块中没有抛出任何异常, 那么会跳过 catch 子句的处理器代码。

利用反射分析类的能力

在java.lang.reflect 包中有三个类 Field、Method 和 Constructor 分别用于描述类的域、 方 法和构造器。 这三个类都有一个叫做 getName 的方法, 用来返回项目的名称。Held 类有一 个 getType 方法, 用来返回描述域所属类型的 Class 对象。Method 和 Constructor 类有能够 报告参数类型的方法,Method 类还有一个可以报告返回类型的方法。这 三个类还有一个叫 做 getModifiers 的方法, 它将返回一个整型数值,用不同的位开关描述 public 和 static 这样 的修饰符使用状况。

Class类中的 getFields、 getMethods 和 getConstructors方 法 将 分 别 返 回 类 提 供 的 public 域、 方法和构造器数组, 其中包括超类的公有成员。Class 类的 getDeclareFields、 getDeclareMethods 和 getDeclaredConstructors方法将分别返回类中声明的全部域、 方法和构 造器, 其中包括私有和受保护成员,但不包括超类的成员。

程序清单 5-13 显示了如何打印一个类的全部信息的方法。这个程序将提醒用户输入类 名,然后输出类中所有的方法和构造器的签名, 以及全部域名。假如输入

java.lang.Double

程序将会输出:

public final class java.lang.Double extends java.lang.Number
{
   public java.lang.Double(double);
   public java.lang.Double(java.lang.String);
​
   public boolean equals(java.lang.Object);
   public static java.lang.String toString(double);
   public java.lang.String toString();
   public int hashCode();
   public static int hashCode(double);
   public static double min(double, double);
   public static double max(double, double);
   public static native long doubleToRawLongBits(double);
   public static long doubleToLongBits(double);
   public static native double longBitsToDouble(long);
   public volatile int compareTo(java.lang.Object);
   public int compareTo(java.lang.Double);
   public byte byteValue();
   public short shortValue();
   public int intValue();
   public long longValue();
   public float floatValue();
   public double doubleValue();
   public static java.lang.Double valueOf(java.lang.String);
   public static java.lang.Double valueOf(double);
   public static java.lang.String toHexString(double);
   public static int compare(double, double);
   public static boolean isNaN(double);
   public boolean isNaN();
   public static boolean isFinite(double);
   public static boolean isInfinite(double);
   public boolean isInfinite();
   public static double sum(double, double);
   public static double parseDouble(java.lang.String);
​
   public static final double POSITIVE_INFINITY;
   public static final double NEGATIVE_INFINITY;
   public static final double NaN;
   public static final double MAX_VALUE;
   public static final double MIN_NORMAL;
   public static final double MIN_VALUE;
   public static final int MAX_EXPONENT;
   public static final int MIN_EXPONENT;
   public static final int SIZE;
   public static final int BYTES;
   public static final java.lang.Class TYPE;
   private final double value;
   private static final long serialVersionUID;
}

这个程序可以分析 Java 解释器能够加载的任何类。

//程序清单 5-13 reflection/ReflectionTest.java
package reflection;
​
import java.util.*;
import java.lang.reflect.*;
​
/**
 * This program uses reflection to print all features of a class.
 * @version 1.1 2004-02-21
 * @author Cay Horstmann
 */
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 + ";");
      }
   }
}

 


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

从前面一节中, 已经知道如何查看任意对象的数据域名称和类型:

•获得对应的 Class 对象。

•通过 Class 对象调用 getDeclaredFields。

利用反射机制可以查看在编译时还不清楚的 对象域。

查看对象域的关键方法是 Field类中的 get 方法。如果 f 是一个 Field类型的对象(例如, 通过 getDeclaredFields 得到的对象),obj 是某个包含 f 域的类的对象,f.get(obj) 将返回一个 对象,其值为 obj 域的当前值。这样说起来显得有点抽象,这里看一看下面这个示例的运行。

Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989); 
​
Class cl = harry.getClass0; 
​
// the class object representing Employee 
​
Field f = cl.getDeclaredFieldC'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" 

 

实际上,这段代码存在一个问题。由于 name 是一个私有域, 所以 get 方法将会抛出一个 IllegalAccessException。只有利用 get 方法才能得到可访问域的值。除非拥有访问权限,否则 Java 安全机制只允许査看任意对象有哪些域, 而不允许读取它们的值。

反射机制的默认行为受限于 Java 的访问控制。然而, 如果一个 Java 程序没有受到安 全管理器的控制, 就可以覆盖访问控制。 为了达到这个目的, 需要调用 Field、Method 或 Constructor 对象的 setAccessible 方法。例如,

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

setAccessible方法是 AccessibleObject 类中的一个方法, 它是 Field、 Method 和 Constructor 类的公共超类。这个特性是为调试、持久存储和相似机制提供的。本书稍后将利用它编写一 个通用的 toString方法。

程序清单 5-14 显示了如何编写一个可供任意类使用的通用 toString方法。 其中使用 getDeclaredFileds 获得所有的数据域, 然后使用 setAccessible 将所有的域设置为可访问的。 对 于每个域,获得了名字和值。程序清单 5-14 递归调用 toString方法,将每个值转换成字符串。

//程序清单 5-14 objectAnalyzer/ObjectAnalyzerTest.java
package objectAnalyzer;
​
import java.util.ArrayList;
​
/**
 * This program uses reflection to spy on objects.
 * @version 1.12 2012-01-26
 * @author Cay Horstmann
 */
public class ObjectAnalyzerTest
{
   public static void main(String[] args)
   {
      ArrayList<Integer> squares = new ArrayList<>();
      for (int i = 1; i <= 5; i++)
         squares.add(i * i);
      System.out.println(new ObjectAnalyzer().toString(squares));
   }
}

 

//程序清单 5-15 objectAnalyzer/ObjectAnalyzer.java
package objectAnalyzer;
​
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
​
public class ObjectAnalyzer
{
   private ArrayList<Object> visited = new ArrayList<>();
​
   /**
    * Converts an object to a string representation that lists all fields.
    * @param obj an object
    * @return a string with the object's class name and all field names and
    * values
    */
   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;
   }
}

 

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

java.lang.reflect 包中的 Array 类允许动态地创建数组。例如, 将这个特性应用到 Array 类中的 copyOf方法实现中, 应该记得这个方法可以用于扩展已经填满的数组。

Employee[] a = new Employee[100]: // array is full 
​
a = Arrays.copyOf(a, 2 * a.length);

 

可以通过调用 Array.getLength(a) 获得数组的长度, 也可以通过 Array类的静态 getLength 方法的返回值得到任意数组的长度。而要获得新数组元素类型,就需要进行以下工作:

1 ) 首先获得 a 数组的类对象。

2 ) 确认它是一个数组。

3 ) 使用 Class 类(只能定义表示数组的类对象)的 getComponentType方法确定数组对应 的类型。

程序清单 5-16 显示了两个扩展数组的方法。请注意, 将 badCopyOf 的返回值进行类型 转换将会抛出一个异常。

//程序清单 5-16 arrays/CopyOfTest.java 
package arrays;
​
import java.lang.reflect.*;
import java.util.*;
​
/**
 * This program demonstrates the use of reflection for manipulating arrays.
 * @version 1.2 2012-05-04
 * @author Cay Horstmann
 */
public class CopyOfTest
{
   public static void main(String[] args)
   {
      int[] a = { 1, 2, 3 };
      a = (int[]) goodCopyOf(a, 10);
      System.out.println(Arrays.toString(a));
​
      String[] b = { "Tom", "Dick", "Harry" };
      b = (String[]) goodCopyOf(b, 10);
      System.out.println(Arrays.toString(b));
​
      System.out.println("The following call will generate an exception.");
      b = (String[]) badCopyOf(b, 10);
   }
​
   /**
    * This method attempts to grow an array by allocating a new array and copying all elements.
    * @param a the array to grow
    * @param newLength the new length
    * @return a larger array that contains all elements of a. However, the returned array has 
    * type Object[], not the same type as a
    */
   public static Object[] badCopyOf(Object[] a, int newLength) // not useful
   {
      Object[] newArray = new Object[newLength];
      System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));
      return newArray;
   }
​
   /**
    * This method grows an array by allocating a new array of the same type and
    * copying all elements.
    * @param a the array to grow. This can be an object array or a primitive
    * type array
    * @return a larger array that contains all elements of a.
    */
   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;
   }
}

 

调用任意方法

为了能够看到方法指针的工作过程, 先回忆一下利用 Field 类的 get 方法查看对象域的过 程。与之类似, 在 Method 类中有一个 invoke 方法, 它允许调用包装在当前 Method 对象中 的方法。invoke 方法的签名是:

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

第一个参数是隐式参数, 其余的对象提供了显式参数(在 Java SE 5.0 以前的版本中,必须传递一个对象数组, 如果没有显式参数就传递一个 null)。

程序清单 5-17 是一个打印诸如 Math.sqrt、Math.sin 这样的数学函数值表的程序。打印的 结果如下所示:

public static double Test.square(double)
    1.0000 |     1.0000
    2.0000 |     4.0000
    3.0000 |     9.0000
    4.0000 |    16.0000
    5.0000 |    25.0000
    6.0000 |    36.0000
    7.0000 |    49.0000
    8.0000 |    64.0000
    9.0000 |    81.0000
   10.0000 |   100.0000
public static double java.lang.Math.sqrt(double)
    1.0000 |     1.0000
    2.0000 |     1.4142
    3.0000 |     1.7321
    4.0000 |     2.0000
    5.0000 |     2.2361
    6.0000 |     2.4495
    7.0000 |     2.6458
    8.0000 |     2.8284
    9.0000 |     3.0000
   10.0000 |     3.1623

这是 Math 类中的一个方法, 通过参数向它提供了一个函数名 sqrt 和一个 double 类型的 参数。

程序清单 5-17 给出了通用制表和两个测试程序的全部代码。

//程序清单 5-17 methods/MethodTableTest.java
package methods;
​
import java.lang.reflect.*;
​
/**
 * This program shows how to invoke methods through reflection.
 * @version 1.2 2012-05-04
 * @author Cay Horstmann
 */
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();
         }
      }
   }
}

 


继承的设计技巧

给出一些对设计继承关系很有帮助的建议。

  1. 将公共操作和域放在超类。

  1. 不要使用受保护的域。

  2. 使用继承实现“ is-a” 关系 。

  3. 除非所有继承的方法都有意义, 否则不要使用继承。

  4. 在覆盖方法时, 不要改变预期的行为。

  5. 使用多态, 而非类型信息。

  6. 不要过多地使用反射。

posted on 2020-08-09 21:11  ♌南墙  阅读(118)  评论(0)    收藏  举报