五、继承

类,超类和子类

  有些人认为 super 与 this 引用是类似的概念, 实际上,这样比较并不太恰当。这是 因为 super 不是一个对象的引用, 不能将 super 赋给另一个对象变量, 它只是一个指示编 译器调用超类方法的特殊关键字。

   使用 super 调用构造器的语句必须是子类构造器的第一条语句。 如果子类的构造器没有显式地调用超类的构造器, 则将自动地调用超类默认(没有参数 ) 的构造器。 如果超类没有不带参数的构造器, 并且在子类的构造器中又没有显式地调用超类 的其他构造器’ 则 Java 编译器将报告错误。

  关键字 this 有两个用途: 一是引用隐式参数,二是调用该类其他的构 造器 , 同样,super 关键字也有两个用途:一是调用超类的方法,二是调用超类的构造器。

 一个对象变量(例如, 变量 e ) 可以指示多种实际类型的现象被称为多态( polymorphism)。 在运行时能够自动地选择调用哪个方法的现象称为动态绑定( dynamic binding)。在 Java 中, 不需要将方法声明为虚拟方法。动态绑定是默认的处理方式。如 果不希望让一个方法具有虚拟特征, 可以将它标记为 final。

  Java不支持多继承。

  “ is-a” 规则的另一种表述法是置换法则。它表明程序中出现超类对象的任何地方都可以 用子类对象置换。

  然而,不能将一个超类的引用赋给子类变量。

  在 Java 中,子类数组的引用可以转换成超类数组的引用, 而不需要采用强制类型 转换。例如, 下面是一个经理数组 Manager[] managers = new Manager[10]; 将它转换成 Employee[] 数组完全是合法的: Employee[] staff = managers; // OK

   

      不过,返回类型不是签名的一部分, 因此,在覆盖方法时, 一定要保证返回类型 的兼容性。 允许子类将覆盖方法的返回类型定义为原返回类型的子类型

    在覆盖一个方法的时候,子类方法不能低于超类方法的可见性

   有时候,可能希望阻止人们利用某个类定义子类。不允许扩展的类被称为 final 类。类中的特定方法也可以被声明为 final。如果这样做,子类就不能覆盖这个方法( final 类中的所有方法自动地成为 final 方法,不包括域 )。

强制类型转换 

//Manager是Employee类的子类
Employee staff=new Employee();
Manager boss=(Manager)staff;

    将一个值存人变量时, 编译器将检查是否允许该操作。将一个了-类的引用赋给一个超类 变量, 编译器是允许的。但将一个超类的引用赋给一个子类变量, 必须进行类型转换, 这样 才能够通过运行时的检査。在进行类型转换之前,先查看一下是否能够成功地转换。这个过程简单地使用 instanceof 操作符就可以实现。 例如:

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

  如果 x 为 null , 进行下列测试

      x instanceof C

  不会产生异常, 只是返回 false。之所以这样处理是因为 null 没有引用任何对象 。除了抽象方法之外,抽象类还可以包含具体数据和具体方法。抽象方法充当着占位的角色, 它们的具体实现在子类中。扩展抽象类可以有两种选择。 一种是在抽象类中定义部分抽象类方法或不定义抽象类方法,这样就必须将子类也标记为抽 象类;另一种是定义全部的抽象方法,这样一来,子类就不是抽象的了。类即使不含抽象方法,也可以将类声明为抽象类。 抽象类不能被实例化。需要注意, 可以定义一个抽象类的对象变量, 但是它只能引用非抽象子类的对象。

受保护访问

  然而,在有些时候,人们希望超类中的某些方法允许被子类访问, 或允许子类的方法访 问超类的某个域。为此, 需要将这些方法或域声明为 protected。:事实上,Java 中的受保护部分对所有子类及同一个包中的所有其他类都可见。

抽象类(abstract)

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

Object:所有类的超类

  如果没有明确地指出超类,Object 就被认为是这个类的超类。

 在 Java 中,只有基本类型 ( primitive types) 不是对象, 例如,数值、 字符和布尔类型的 值都不是对象。 所有的数组类塱,不管是对象数组还是基本类型的数组都扩展了 Object 类。

Employee[] staff = new Employee[10];
obj = staff; // OK
obj = new int[10]; // OK

equals方法

  对于任意非空引用 x, x.equals(null) 应该返回 false。

  下面可以从两个截然不同的情况看一下这个问题:

  • 如果子类能够拥有自己的相等概念, 则对称性需求将强制采用 getClass 进行检测
  • 如果由超类决定相等的概念,那么就可以使用 intanceof进行检测, 这样可以在不同 子类的对象之间进行相等的比较。

  

  对于数组类型的域,可以使用静态的 Arrays.equals 方法检测相应的数组元素是否相等。

 hashcode方法

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

请注意, 字符串 s 与 t 拥有相同的散列码, 这是因为字符串的散列码是由内容导出 的。而字符串缓冲 sb 与 tb却有着不同的散列码, 这是因为在 StringBuffer 类中没有定义 hashCode 方法,它的散列码是由 Object 类的默认 hashCode 方法导出的对象存储地址。

    首先, 最好使用 null 安全的方法 Objects.hashCode。 如果其参 数为 null,这个方法会返回 0。Equals 与 hashCode 的定义必须一致:如果 x.equals(y) 返回 true, 那么 x.hashCode( ) 就必 须与 y.hashCode( ) 具有相同的值。例如, 如果用定义的 Employee.equals 比较雇员的 ID,那 么 hashCode 方法就需要散列 ID,而不是雇员的姓名或存储地址。如果存在数组类型的域, 那么可以使用静态的 Arrays.hashCode 方法计算一个散列码, 这个散列码由数组元素的散列码组成。

  

   tostring()方法

    在调用 x.toString( ) 的地方可以用 ""+x 替代。这条语句将一个空串与 x 的字符串 表示相连接。这里的 x 就是 x.toString( 。) 与 toString 不同的是,如果 x 是基本类型,这 条语句照样能够执行。 Object 类定义了 toString 方法, 用来打印输出对象所属的类名和散列码。

  

泛型数组列表

  解决动态更改数组问题。。在 Java 中, 解决这个问题最简单的方法是使用 Java 中另外一个被称为 ArrayList 的类。它使用起来有点像数组,但在添加或删除元素时, 具有自动调节数组容量的 功能,而不需要为此编写任何代码。

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

  这被称为“ 菱形” 语法,因为空尖括号 <>就像是一个菱形。可以结合 new 操作符使用菱形 语法。编译器会检查新值是什么。如果赋值给一个变量,或传递到某个方法,或者从某个方 法返回,编译器会检査这个变量、 参数或方法的泛型类型,然后将这个类型放在<>中。

定义数量

ArrayList<Emplyee> staff=new ArrayList<Emplyee>(100);
staff.ensureCapacity(100);

  数组列表的容量与数组的大小有一个非常重要的区别。如果为数组分配 100 个元素 的存储空间,数组就有 100 个空位置可以使用。 而容量为 100 个元素的数组列表只是拥 有保存 100 个元素的潜力 ( 实际上, 重新分配空间的话,将会超过100 ), 但是在最初, 甚至完成初始化构造之后,数组列表根本就不含有任何元素。 

  ArrayList a和b,a=b。Java 中, 这条赋值语句的操作结果是让 a 和 b 引用同一个数组列表。

  

 访问数组列表元素

   使用 add 方法为数组添加新元素, 而不要使用 set 方法, 它只能替换数组中已经存在 的元素内容。

   将ArrayList中的数组元素拷贝到一个数组中

ArrayList<int> list=new ArrayList<int>();
int[] a=new int[list.size()];
list.toArray(a);

 

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

  会报错。可以用@SuppressWamings("unchecked") 标注来标记 这个变量能够接受类型转换,

对象包装器与自动装箱

    有时, 需要将 int 这样的基本类型转换为对象。 所有的基本类型都冇一个与之对应的类。 例如,Integer 类对应基本类型 int。通常, 这些类称为包装器 ( wrapper ) 这些对象包装器类 拥有很明显的名字:Integer、Long、Float、Double、Short、Byte、Character 、Void 和 Boolean (前 6 个类派生于公共的超类 Number)。对象包装器类是不可变的,即一旦构造了包装器,就不 允许更改包装在其中的值。同时, 对象包装器类还是 final , 因此不能定义它们的子类。

  ArrayList list = new ArrayList<>();

  幸运的是, 有一个很有用的特性, 从而更加便于添加 int 类型的元素到 ArrayList<lnteger> 中。下面这个调用 list.add(3); 将自动地变换成 list.add (Integer.value0f(3)); 这种变换被称为自动装箱(autoboxing)。

  相反地, 当将一个 Integer 对象赋给一个 int 值时, 将会自动地拆箱。也就是说, 编译器 将下列语句: int n = list.get(i); 翻译成 int n = list.get(i).intValue();

  在 两个包装器对象比较时调用 equals 方法。

  关于自动装箱还有几点需要说明。首先, 由于包装器类引用可以为 null, 所以自动装箱 有可能会抛出一个 NullPointerException 异常:

  最后强调一下,装箱和拆箱是编译器认可的,而不是虚拟机。

Integer n = null;
System.out.printing * n); // Throws NullPointerException

参数数量可变的方法

  的省略号 . . . 是 Java 代码的一部分,它表明这个方法可以接收任意数量的对象。相当于数组。因此, 可以将已经存在且最后一个参数是数组的方法重新定义为可变参数的方法, 而不会破坏任何已经存在的代码。

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

枚举类

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

  实际上, 这个声明定义的类型是一个类, 它刚好有 4 个实例, 在此尽量不要构造新对象。 因此, 在比较两个枚举类型的值时, 永远不需要调用 equals, 而直接使用“ = =” 就 可以了。

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

public enum Size
{
SMALLfS"), MEDIUMC'M"), LARGEfL"), EXTRA_LARGE("XL");
private String abbreviation;
private Size(String abbreviation) { this,abbreviation = abbreviation; }
public String getAbbreviation() { return abbreviation; }
}

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

  toString 的逆方法是静态方法 valueOf。例如, 语句:

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

每个枚举类型都有一个静态的 values 方法, 它将返回一个包含全部枚举值的数组。 例 如,如下调用

Size[] values = Size.values();

反射

  能够分析类能力的程序称为反射(reflective )。

Class类

  在程序运行期间,Java 运行时系统始终为所有的对象维护一个被称为运行时的类型标识。 这个信息跟踪着每个对象所属的类。 虚拟机利用运行时类型信息选择相应的方法执行。 然而, 可以通过专门的 Java 类访问这些信息。保存这些信息的类被称为 Class, 这 个 名 字很容易让人混淆。Object 类中的 getClass( ) 方法将会返回一个 Class 类型的实例。

  方法:

Integer a;
//返回类的名字,名字也包括包名
String str=a.getClass().getName();
Class c=Class.forName("Integer");

 获得 Class类对象的第三种方法非常简单。如果 T 是任意的 Java 类型(或 void 关键字) T.class 将代表匹配的类对象。例如:

Class dl = Random.class; // if you import java.util
Class cl = int.class;

  一个 Class 对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如, int 不是类, 但 int.class 是一个 Class 类型的对象。  Class 类实际上是一个泛型类。鉴于历史原 getName 方法在应用于数组类型的时候会返回一个很奇怪的名字.虚拟机为每个类型管理一个 Class 对象。 因此,可以利用 =运算符实现两个类对象比较 的操作.还有一个很有用的方法 newlnstance( ), 可以用来动态地创建一个类的实例例如, e.getClass0.newlnstance();newlnstance方法调用默认的构造器(没有参数的构 造器)初始化新创建的对象。如果这个类没有默认的构造器, 就会抛出一个异常.

  异常有两种类型: 未检查异常和已检查异常。 对于已检查异常, 编译器将会检查是否提 供了处理器。 然而,有很多常见的异常, 例如,访问 null 引用, 都属于未检查异常。编译 器不会査看是否为这些错误提供了处理器。毕竟,应该精心地编写代码来避免这些错误的发 生, 而不要将精力花在编写异常处理器上。

  

利用反射分析类的能力

  

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

  查看对象域的关键方法是 Field类中的 get 方法。如果 f 是一个 Field 类型的对象(例如, 通过 getDeclaredFields 得到的对象,) obj 是某个包含 f 域的类的对象,f.get(obj) 将返回一个 对象,其值为 obj 域的当前值

Employee harry = new Employee("Harry Hacker", 35000, 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"

由于 name 是一个私有域, 所以 get 方法将会抛出一个 IllegalAccessException.。然而, 如果一个 Java 程序没有受到安 全管理器的控制, 就可以覆盖访问控制。 为了达到这个目的, 需要调用 Field、 Method 或 Constructor 对象的 setAccessible 方法。例如, f.setAtcessible(true); // now OK to call f.get(harry); setAccessible 方法是 AccessibleObject 类中的一个方法, 它是 Field、 Method 和 Constructor 类的公共超类.

例子: 

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));
   }
}
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;
   }
}

结果:

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

其中最关键的是 Array类中的静态方法 newlnstance, 它能够构造新数组。在调用它时必须提供两个参数,一个是数组的元素类型,一个是数组的 长度。

Object newArray = Array.newlnstance(componentType , newLength);

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

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

2 ) 确认它是一个数组。

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

为了能够实现上述操作,应该将 goodCopyOf 的参数声明为 Object 类型,.而不要声明为 对象型数组(Object[])。整型数组类型 int[] 可以被转换成 Object,但不能转换成对象数组。

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;
   }
}

调用任意方法

  反射机制允许你调用任意方法。Method类有一个invoke方法,它允许调用包装在当前 Method 对象中 的方法。invoke 方法的签名是: Object invoke(Object obj, Object... args)

对于静态方法,第一个参数可以被忽略, 即可以将它设置为 null。: 建议 Java 开发者不要使用 Method 对象的 回调功能。使用接口进行回调会使得代码的执行速度更快, 更易于维护。

例子:

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();
         }
      }
   }
}

 

猜你喜欢

转载自blog.csdn.net/qq_39326472/article/details/87517650